diff options
335 files changed, 7131 insertions, 2580 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index f49cdbf403f0..4710322db283 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -1777,7 +1777,10 @@ 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. + * 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. * * @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 1f4ef0470ebd..388bbf1b26a0 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobScheduler.java @@ -107,7 +107,9 @@ 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. + * 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. * * <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 @@ -131,7 +133,7 @@ public abstract class JobScheduler { * job. If a job with the same ID is already scheduled, it will be replaced with the * new {@link JobInfo}, but any previously enqueued work will remain and be dispatched the * next time it runs. If a job with the same ID is already running, the new work will be - * enqueued for it. + * enqueued for it without stopping the job. * * <p>The work you enqueue is later retrieved through * {@link JobParameters#dequeueWork() JobParameters.dequeueWork}. Be sure to see there diff --git a/apex/jobscheduler/framework/java/android/app/job/JobService.java b/apex/jobscheduler/framework/java/android/app/job/JobService.java index e5b07429a5c6..7ed4b62ae7e4 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobService.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobService.java @@ -31,6 +31,17 @@ import android.os.IBinder; * in blocking any future callbacks from the JobManager - specifically * {@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 + * 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> */ 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 c86353c84467..afe36b5fa25a 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -542,6 +542,22 @@ class JobConcurrencyManager { return mRunningJobs.contains(job); } + /** + * Returns true if a job that is "similar" to the provided job is currently running. + * "Similar" in this context means any job that the {@link JobStore} would consider equivalent + * and replace one with the other. + */ + @GuardedBy("mLock") + private boolean isSimilarJobRunningLocked(JobStatus job) { + for (int i = mRunningJobs.size() - 1; i >= 0; --i) { + JobStatus js = mRunningJobs.valueAt(i); + if (job.getUid() == js.getUid() && job.getJobId() == js.getJobId()) { + return true; + } + } + return false; + } + /** Return {@code true} if the state was updated. */ @GuardedBy("mLock") private boolean refreshSystemStateLocked() { @@ -699,6 +715,21 @@ class JobConcurrencyManager { continue; } + 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; + } + // Find an available slot for nextPending. The context should be one of the following: // 1. Unused // 2. Its job should have used up its minimum execution guarantee so it @@ -707,8 +738,6 @@ class JobConcurrencyManager { ContextAssignment selectedContext = null; final int allWorkTypes = getJobWorkTypes(nextPending); final boolean pkgConcurrencyOkay = !isPkgConcurrencyLimitedLocked(nextPending); - final boolean isTopEj = nextPending.shouldTreatAsExpeditedJob() - && nextPending.lastEvaluatedBias == JobInfo.BIAS_TOP_APP; final boolean isInOverage = projectedRunningCount > STANDARD_CONCURRENCY_LIMIT; boolean startingJob = false; if (idle.size() > 0) { @@ -1177,6 +1206,15 @@ 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 (worker.getPreferredUid() != nextPending.getUid()) { if (backupJob == null && !isPkgConcurrencyLimitedLocked(nextPending)) { int allWorkTypes = getJobWorkTypes(nextPending); @@ -1260,6 +1298,15 @@ 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 (isPkgConcurrencyLimitedLocked(nextPending)) { continue; } 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 358c32722b8b..cd70e88b18aa 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -1208,12 +1208,22 @@ 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 - cancelJobImplLocked(toCancel, jobStatus, JobParameters.STOP_REASON_CANCELLED_BY_APP, - JobParameters.INTERNAL_STOP_REASON_CANCELED, "job rescheduled by app"); + 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); } else { startTrackingJobLocked(jobStatus, null); + canExecuteImmediately = true; } if (work != null) { @@ -1256,7 +1266,12 @@ public class JobSchedulerService extends com.android.server.SystemService // list and try to run it. mJobPackageTracker.notePending(jobStatus); mPendingJobQueue.add(jobStatus); - maybeRunPendingJobsLocked(); + 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(); + } } else { evaluateControllerStatesLocked(jobStatus); } @@ -1377,8 +1392,10 @@ 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 void cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, + private boolean cancelJobImplLocked(JobStatus cancelled, JobStatus incomingJob, @JobParameters.StopReason int reason, int internalReasonCode, String debugReason) { if (DEBUG) Slog.d(TAG, "CANCEL: " + cancelled.toShortString()); cancelled.unprepareLocked(); @@ -1389,7 +1406,7 @@ public class JobSchedulerService extends com.android.server.SystemService } mChangedJobList.remove(cancelled); // Cancel if running. - mConcurrencyManager.stopJobOnServiceContextLocked( + boolean wasRunning = mConcurrencyManager.stopJobOnServiceContextLocked( cancelled, reason, internalReasonCode, debugReason); // If this is a replacement, bring in the new version of the job if (incomingJob != null) { @@ -1397,6 +1414,7 @@ public class JobSchedulerService extends com.android.server.SystemService startTrackingJobLocked(incomingJob, cancelled); } reportActiveLocked(); + return wasRunning; } void updateUidState(int uid, int procState) { @@ -1755,7 +1773,7 @@ public class JobSchedulerService extends com.android.server.SystemService // same job ID), we remove it from the JobStore and tell the JobServiceContext to stop // running the job. Once the job stops running, we then call this method again. // TODO: rework code so we don't intentionally call this method twice for the same job - Slog.w(TAG, "Job didn't exist in JobStore"); + Slog.w(TAG, "Job didn't exist in JobStore: " + jobStatus.toShortString()); } if (mReadyToRock) { for (int i = 0; i < mControllers.size(); i++) { @@ -3813,6 +3831,7 @@ public class JobSchedulerService extends com.android.server.SystemService // Double indent for readability pw.increaseIndent(); pw.increaseIndent(); + pw.println(job.toShortString()); job.dump(pw, true, nowElapsed); pw.decreaseIndent(); pw.decreaseIndent(); 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 80f3fea1907c..c90291e5f264 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppIdleHistory.java @@ -979,6 +979,7 @@ public class AppIdleHistory { dumpBucketExpiryTimes(idpw, appUsageHistory, totalElapsedTime); idpw.print(" lastJob="); TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw); + idpw.print(" lastInformedBucket=" + appUsageHistory.lastInformedBucket); if (appUsageHistory.lastRestrictAttemptElapsedTime > 0) { idpw.print(" lastRestrictAttempt="); TimeUtils.formatDuration( 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 c3d6b738fc1e..1891e06a9420 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -1155,6 +1155,12 @@ public class AppStandbyController final int appId = getAppId(packageName); if (appId < 0) return; + final int minBucket = getAppMinBucket(packageName, appId, userId); + if (idle && minBucket < AppIdleHistory.IDLE_BUCKET_CUTOFF) { + Slog.e(TAG, "Tried to force an app to be idle when its min bucket is " + + standbyBucketToString(minBucket)); + return; + } final long elapsedRealtime = mInjector.elapsedRealtime(); final boolean previouslyIdle = isAppIdleFiltered(packageName, appId, @@ -1166,12 +1172,10 @@ public class AppStandbyController final boolean stillIdle = isAppIdleFiltered(packageName, appId, userId, elapsedRealtime); // Inform listeners if necessary + maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket, + REASON_MAIN_FORCED_BY_USER, false); if (previouslyIdle != stillIdle) { - maybeInformListeners(packageName, userId, elapsedRealtime, standbyBucket, - REASON_MAIN_FORCED_BY_USER, false); - if (!stillIdle) { - notifyBatteryStats(packageName, userId, idle); - } + notifyBatteryStats(packageName, userId, stillIdle); } } @@ -1934,6 +1938,8 @@ public class AppStandbyController } mAppIdleHistory.setAppStandbyBucket( packageName, userId, elapsedRealtime, newBucket, newReason); + maybeInformListeners(packageName, userId, elapsedRealtime, newBucket, + newReason, false); } } @@ -2490,6 +2496,8 @@ public class AppStandbyController public void handleMessage(Message msg) { switch (msg.what) { case MSG_INFORM_LISTENERS: + // TODO(230875908): Properly notify BatteryStats when apps change from active to + // idle, and vice versa StandbyUpdateRecord r = (StandbyUpdateRecord) msg.obj; informListeners(r.packageName, r.userId, r.bucket, r.reason, r.isUserInteraction); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 4681d4943256..2c0b6e9ee89d 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -800,6 +800,7 @@ package android.content.pm { method public boolean hasRequestForegroundServiceExemption(); method public boolean isPrivilegedApp(); method public boolean isSystemApp(); + method public void setEnableOnBackInvokedCallback(boolean); field public static final int PRIVATE_FLAG_PRIVILEGED = 8; // 0x8 field public int privateFlags; } @@ -919,7 +920,7 @@ package android.content.pm { field public int id; field public String lastLoggedInFingerprint; field public long lastLoggedInTime; - field public String name; + field @Nullable public String name; field public boolean partial; field public boolean preCreated; field public int profileBadge; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index c7c654a0b071..3b1943bf86f6 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -2537,8 +2537,8 @@ public class AppOpsManager { * restriction} for a certain app-op. */ private static RestrictionBypass[] sOpAllowSystemRestrictionBypass = new RestrictionBypass[] { - null, //COARSE_LOCATION - null, //FINE_LOCATION + new RestrictionBypass(true, false, false), //COARSE_LOCATION + new RestrictionBypass(true, false, false), //FINE_LOCATION null, //GPS null, //VIBRATE null, //READ_CONTACTS @@ -2547,7 +2547,7 @@ public class AppOpsManager { null, //WRITE_CALL_LOG null, //READ_CALENDAR null, //WRITE_CALENDAR - new RestrictionBypass(true, false), //WIFI_SCAN + new RestrictionBypass(false, true, false), //WIFI_SCAN null, //POST_NOTIFICATION null, //NEIGHBORING_CELLS null, //CALL_PHONE @@ -2561,10 +2561,10 @@ public class AppOpsManager { null, //READ_ICC_SMS null, //WRITE_ICC_SMS null, //WRITE_SETTINGS - new RestrictionBypass(true, false), //SYSTEM_ALERT_WINDOW + new RestrictionBypass(false, true, false), //SYSTEM_ALERT_WINDOW null, //ACCESS_NOTIFICATIONS null, //CAMERA - new RestrictionBypass(false, true), //RECORD_AUDIO + new RestrictionBypass(false, false, true), //RECORD_AUDIO null, //PLAY_AUDIO null, //READ_CLIPBOARD null, //WRITE_CLIPBOARD @@ -2582,7 +2582,7 @@ public class AppOpsManager { null, //MONITOR_HIGH_POWER_LOCATION null, //GET_USAGE_STATS null, //MUTE_MICROPHONE - new RestrictionBypass(true, false), //TOAST_WINDOW + new RestrictionBypass(false, true, false), //TOAST_WINDOW null, //PROJECT_MEDIA null, //ACTIVATE_VPN null, //WALLPAPER @@ -2614,7 +2614,7 @@ public class AppOpsManager { null, // ACCEPT_HANDOVER null, // MANAGE_IPSEC_HANDOVERS null, // START_FOREGROUND - new RestrictionBypass(true, false), // BLUETOOTH_SCAN + new RestrictionBypass(false, true, false), // BLUETOOTH_SCAN null, // USE_BIOMETRIC null, // ACTIVITY_RECOGNITION null, // SMS_FINANCIAL_TRANSACTIONS @@ -3331,6 +3331,9 @@ public class AppOpsManager { * @hide */ public static class RestrictionBypass { + /** Does the app need to be system uid to bypass the restriction */ + public boolean isSystemUid; + /** Does the app need to be privileged to bypass the restriction */ public boolean isPrivileged; @@ -3340,12 +3343,14 @@ public class AppOpsManager { */ public boolean isRecordAudioRestrictionExcept; - public RestrictionBypass(boolean isPrivileged, boolean isRecordAudioRestrictionExcept) { + public RestrictionBypass(boolean isSystemUid, boolean isPrivileged, + boolean isRecordAudioRestrictionExcept) { + this.isSystemUid = isSystemUid; this.isPrivileged = isPrivileged; this.isRecordAudioRestrictionExcept = isRecordAudioRestrictionExcept; } - public static RestrictionBypass UNRESTRICTED = new RestrictionBypass(true, true); + public static RestrictionBypass UNRESTRICTED = new RestrictionBypass(false, true, true); } /** diff --git a/core/java/android/app/LocaleConfig.java b/core/java/android/app/LocaleConfig.java index 436eac37b4fb..7c83d5850f76 100644 --- a/core/java/android/app/LocaleConfig.java +++ b/core/java/android/app/LocaleConfig.java @@ -45,7 +45,9 @@ import java.util.Set; * referenced in the manifest via {@code android:localeConfig} on * {@code <application>}. * - * For more information, see TODO(b/214154050): add link to guide + * <p>For more information, see + * <a href="https://developer.android.com/about/versions/13/features/app-languages#use-localeconfig"> + * the section on per-app language preferences</a>. * * @attr ref android.R.styleable#LocaleConfig_Locale_name * @attr ref android.R.styleable#AndroidManifestApplication_localeConfig diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 974d20a7eab3..e82073380394 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -7770,10 +7770,11 @@ public class Notification implements Parcelable * user will always see the normal notification view. * * <p> - * If the app is targeting Android P and above, it is required to use the {@link Person} - * class in order to get an optimal rendering of the notification and its avatars. For - * conversations involving multiple people, the app should also make sure that it marks the - * conversation as a group with {@link #setGroupConversation(boolean)}. + * If the app is targeting Android {@link android.os.Build.VERSION_CODES#P} and above, it is + * required to use the {@link Person} class in order to get an optimal rendering of the + * notification and its avatars. For conversations involving multiple people, the app should + * also make sure that it marks the conversation as a group with + * {@link #setGroupConversation(boolean)}. * * <p> * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior. @@ -7846,8 +7847,8 @@ public class Notification implements Parcelable * @param user Required - The person displayed for any messages that are sent by the * user. Any messages added with {@link #addMessage(Notification.MessagingStyle.Message)} * who don't have a Person associated with it will be displayed as if they were sent - * by this user. The user also needs to have a valid name associated with it, which will - * be enforced starting in Android P. + * by this user. The user also needs to have a valid name associated with it, which is + * enforced starting in Android {@link android.os.Build.VERSION_CODES#P}. */ public MessagingStyle(@NonNull Person user) { mUser = user; @@ -7904,9 +7905,9 @@ public class Notification implements Parcelable /** * Sets the title to be displayed on this conversation. May be set to {@code null}. * - * <p>Starting in {@link Build.VERSION_CODES#R, this conversation title will be ignored if a - * valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. In this - * case, {@link ShortcutInfo#getLongLabel()} (or, if missing, + * <p>Starting in {@link Build.VERSION_CODES#R}, this conversation title will be ignored + * if a valid shortcutId is added via {@link Notification.Builder#setShortcutId(String)}. + * In this case, {@link ShortcutInfo#getLongLabel()} (or, if missing, * {@link ShortcutInfo#getShortLabel()}) will be shown as the conversation title * instead. * @@ -8065,7 +8066,7 @@ public class Notification implements Parcelable } /** - * Gets the list of {@code Message} objects that represent the notification + * Gets the list of {@code Message} objects that represent the notification. */ public List<Message> getMessages() { return mMessages; diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING index a8ae1913b964..32207af22dc1 100644 --- a/core/java/android/app/TEST_MAPPING +++ b/core/java/android/app/TEST_MAPPING @@ -110,19 +110,6 @@ "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - // TODO(b/225076204): Remove the following four test cases after fixing the test fail. - { - "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess" - }, - { - "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success" - }, - { - "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess" - }, - { - "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success" } ], "file_patterns": ["(/|^)VoiceInteract[^/]*"] diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 315bd71f8c0c..0a2b42121545 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -1811,10 +1811,6 @@ public class DevicePolicyManager { * #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link * #PROVISIONING_MODE_MANAGED_PROFILE} and {@link #PROVISIONING_MODE_FULLY_MANAGED_DEVICE}. * - * <p>Also, if this flag is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity - * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link - * #EXTRA_PROVISIONING_SERIAL_NUMBER} extras. - * * <p>This flag can be combined with {@link #FLAG_SUPPORTED_MODES_PERSONALLY_OWNED}. In * that case, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity will have * the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link @@ -1834,6 +1830,10 @@ public class DevicePolicyManager { * activity to have the {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra * contain only {@link #PROVISIONING_MODE_MANAGED_PROFILE}. * + * <p>Also, if this flag is set, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity + * will not receive the {@link #EXTRA_PROVISIONING_IMEI} and {@link + * #EXTRA_PROVISIONING_SERIAL_NUMBER} extras. + * * <p>This flag can be combined with {@link #FLAG_SUPPORTED_MODES_ORGANIZATION_OWNED}. In * that case, the admin app's {@link #ACTION_GET_PROVISIONING_MODE} activity will have the * {@link #EXTRA_PROVISIONING_ALLOWED_PROVISIONING_MODES} array extra contain {@link @@ -13249,8 +13249,9 @@ 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> 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. 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> 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/app/admin/DevicePolicyResources.java b/core/java/android/app/admin/DevicePolicyResources.java index 08056847e12e..c8033fafbc4e 100644 --- a/core/java/android/app/admin/DevicePolicyResources.java +++ b/core/java/android/app/admin/DevicePolicyResources.java @@ -863,12 +863,6 @@ public final class DevicePolicyResources { PREFIX + "REMOVE_AND_UNINSTALL_DEVICE_ADMIN"; /** - * Title for selecting device admin apps - */ - public static final String SELECT_DEVICE_ADMIN_APPS = - PREFIX + "SELECT_DEVICE_ADMIN_APPS"; - - /** * Message when there are no available device admin apps to display */ public static final String NO_DEVICE_ADMINS = PREFIX + "NO_DEVICE_ADMINS"; @@ -910,11 +904,6 @@ public final class DevicePolicyResources { PREFIX + "ACTIVE_DEVICE_ADMIN_WARNING"; /** - * Title for screen to set a profile owner - */ - public static final String SET_PROFILE_OWNER_TITLE = PREFIX + "SET_PROFILE_OWNER_TITLE"; - - /** * Simplified title for dialog to set a profile owner */ public static final String SET_PROFILE_OWNER_DIALOG_TITLE = @@ -1173,7 +1162,8 @@ public final class DevicePolicyResources { /** * Header for items under the personal user */ - public static final String PERSONAL_CATEGORY_HEADER = PREFIX + "CATEGORY_PERSONAL"; + public static final String PERSONAL_CATEGORY_HEADER = + PREFIX + "PERSONAL_CATEGORY_HEADER"; /** * Text to indicate work notification content will be shown on the lockscreen. @@ -1196,8 +1186,7 @@ public final class DevicePolicyResources { /** * Text for toggle to enable auto-sycing work data */ - public static final String AUTO_SYNC_WORK_DATA = PREFIX - + "AUTO_SYNC_WORK_DATA"; + public static final String AUTO_SYNC_WORK_DATA = PREFIX + "AUTO_SYNC_WORK_DATA"; /** * Summary for "More security settings" section when a work profile is on the device. @@ -1213,10 +1202,17 @@ public final class DevicePolicyResources { + "LOCK_SETTINGS_NEW_PROFILE_LOCK_TITLE"; /** + * Title for screen asking the user to update the type of screen lock (such as a + * pattern, PIN, or password) that they need to enter to use their work apps + */ + public static final String LOCK_SETTINGS_UPDATE_PROFILE_LOCK_TITLE = PREFIX + + "LOCK_SETTINGS_UPDATE_PROFILE_LOCK_TITLE"; + + /** * Title for section listing information that can be seen by organization */ public static final String INFORMATION_SEEN_BY_ORGANIZATION_TITLE = PREFIX - + "information_seen_by_organization_title"; + + "INFORMATION_SEEN_BY_ORGANIZATION_TITLE"; /** * Title for section listing changes made by the organization. @@ -1229,6 +1225,30 @@ public final class DevicePolicyResources { */ public static final String ENTERPRISE_PRIVACY_FOOTER = PREFIX + "ENTERPRISE_PRIVACY_FOOTER"; + + /** + * Title for spell checker settings for work. + */ + public static final String SPELL_CHECKER_FOR_WORK = + PREFIX + "SPELL_CHECKER_FOR_WORK"; + + /** + * Title for personal dictionary for work settings. + */ + public static final String PERSONAL_DICTIONARY_FOR_WORK = + PREFIX + "PERSONAL_DICTIONARY_FOR_WORK"; + + /** + * Summary for switch preference to indicate it is disabled by the admin + */ + public static final String DISABLED_BY_ADMIN_SWITCH_SUMMARY = + PREFIX + "DISABLED_BY_ADMIN_SWITCH_SUMMARY"; + + /** + * Summary for switch preference to indicate it is enabled by the admin + */ + public static final String ENABLED_BY_ADMIN_SWITCH_SUMMARY = + PREFIX + "ENABLED_BY_ADMIN_SWITCH_SUMMARY"; } /** diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 4d56c1dcaa80..907db7df68d5 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -513,10 +513,23 @@ public abstract class Context { * restart. There is no guarantee this will be respected, as the system * tries to balance such requests from one app vs. the importance of * keeping other apps around. + * + * @deprecated Repurposed to {@link #BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE}. */ + @Deprecated public static final int BIND_VISIBLE = 0x10000000; /** + * @hide Flag for {@link #bindService}: Treat the binding as hosting a foreground service + * and also visible to the user. That is, the app hosting the service will get its process state + * bumped to the {@link android.app.ActivityManager#PROCESS_STATE_FOREGROUND_SERVICE}, + * and it's considered as visible to the user, thus less likely to be expunged from memory + * on low memory situations. This is intented for use by processes with the process state + * better than the {@link android.app.ActivityManager#PROCESS_STATE_TOP}. + */ + public static final int BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE = 0x10000000; + + /** * @hide * Flag for {@link #bindService}: Consider this binding to be causing the target * process to be showing UI, so it will be do a UI_HIDDEN memory trim when it goes diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index 2961b5505794..24c383692c09 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -2752,4 +2752,21 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { mKnownActivityEmbeddingCerts.add(knownCert.toUpperCase(Locale.US)); } } + + /** + * Sets whether the application will use the {@link android.window.OnBackInvokedCallback} + * navigation system instead of the {@link android.view.KeyEvent#KEYCODE_BACK} and related + * callbacks. Intended to be used from tests only. + * + * @see #isOnBackInvokedCallbackEnabled() + * @hide + */ + @TestApi + public void setEnableOnBackInvokedCallback(boolean isEnable) { + if (isEnable) { + privateFlagsExt |= PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; + } else { + privateFlagsExt &= ~PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK; + } + } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 44dc28d2b0fa..52e64e80e6d4 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2618,6 +2618,15 @@ public class PackageParser { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + targetCode)) { + Slog.w(TAG, "Package requires development platform " + targetCode + + ", returning current version " + Build.VERSION.SDK_INT); + return Build.VERSION.SDK_INT; + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + targetCode @@ -2689,6 +2698,15 @@ public class PackageParser { return Build.VERSION_CODES.CUR_DEVELOPMENT; } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + minCode)) { + Slog.w(TAG, "Package requires min development platform " + minCode + + ", returning current version " + Build.VERSION.SDK_INT); + return Build.VERSION.SDK_INT; + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { outError[0] = "Requires development platform " + minCode diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java index 76e9fcb07f22..d6e13ac90f82 100644 --- a/core/java/android/content/pm/UserInfo.java +++ b/core/java/android/content/pm/UserInfo.java @@ -18,6 +18,7 @@ package android.content.pm; import android.annotation.IntDef; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.annotation.UserIdInt; import android.compat.annotation.UnsupportedAppUsage; @@ -170,7 +171,7 @@ public class UserInfo implements Parcelable { @UnsupportedAppUsage public int serialNumber; @UnsupportedAppUsage - public String name; + public @Nullable String name; @UnsupportedAppUsage public String iconPath; @UnsupportedAppUsage diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java index 6d74b819301d..bde71bb90bf7 100644 --- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java +++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java @@ -315,6 +315,15 @@ public class FrameworkParsingPackageUtils { return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + minCode)) { + Slog.w(TAG, "Parsed package requires min development platform " + minCode + + ", returning current version " + Build.VERSION.SDK_INT); + return input.success(Build.VERSION.SDK_INT); + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, @@ -367,16 +376,29 @@ public class FrameworkParsingPackageUtils { return input.success(targetVers); } - if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) { - return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); - } - // If it's a pre-release SDK and the codename matches this platform, it // definitely targets this SDK. if (matchTargetCode(platformSdkCodenames, targetCode)) { return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); } + // STOPSHIP: hack for the pre-release SDK + if (platformSdkCodenames.length == 0 + && Build.VERSION.KNOWN_CODENAMES.stream().max(String::compareTo).orElse("").equals( + targetCode)) { + Slog.w(TAG, "Parsed package requires development platform " + targetCode + + ", returning current version " + Build.VERSION.SDK_INT); + return input.success(Build.VERSION.SDK_INT); + } + + try { + if (allowUnknownCodenames && UnboundedSdkLevel.isAtMost(targetCode)) { + return input.success(Build.VERSION_CODES.CUR_DEVELOPMENT); + } + } catch (IllegalArgumentException e) { + return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, "Bad package SDK"); + } + // Otherwise, we're looking at an incompatible pre-release SDK. if (platformSdkCodenames.length > 0) { return input.error(PackageManager.INSTALL_FAILED_OLDER_SDK, diff --git a/core/java/android/hardware/ISensorPrivacyManager.aidl b/core/java/android/hardware/ISensorPrivacyManager.aidl index a392afdacbcb..9cf329ca3d3d 100644 --- a/core/java/android/hardware/ISensorPrivacyManager.aidl +++ b/core/java/android/hardware/ISensorPrivacyManager.aidl @@ -50,5 +50,7 @@ interface ISensorPrivacyManager { void suppressToggleSensorPrivacyReminders(int userId, int sensor, IBinder token, boolean suppress); + boolean requiresAuthentication(); + void showSensorUseDialog(int sensor); }
\ No newline at end of file diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java index 0460e5831e33..99b58c97dc93 100644 --- a/core/java/android/hardware/SensorPrivacyManager.java +++ b/core/java/android/hardware/SensorPrivacyManager.java @@ -327,6 +327,8 @@ public final class SensorPrivacyManager { @NonNull private boolean mToggleListenerRegistered = false; + private Boolean mRequiresAuthentication = null; + /** * Private constructor to ensure only a single instance is created. */ @@ -761,6 +763,23 @@ public final class SensorPrivacyManager { } /** + * @return whether the device is required to be unlocked to change software state. + * + * @hide + */ + @RequiresPermission(Manifest.permission.OBSERVE_SENSOR_PRIVACY) + public boolean requiresAuthentication() { + if (mRequiresAuthentication == null) { + try { + mRequiresAuthentication = mService.requiresAuthentication(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return mRequiresAuthentication; + } + + /** * If sensor privacy for the provided sensor is enabled then this call will show the user the * dialog which is shown when an application attempts to use that sensor. If privacy isn't * enabled then this does nothing. diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index b05e6d131957..a90eb88bc109 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1058,7 +1058,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * must select one unique size from this metadata to use (e.g., preview and recording streams * must have the same size). Otherwise, the high speed capture session creation will fail.</p> * <p>The min and max fps will be multiple times of 30fps.</p> - * <p>High speed video streaming extends significant performance pressue to camera hardware, + * <p>High speed video streaming extends significant performance pressure to camera hardware, * to achieve efficient high speed streaming, the camera device may have to aggregate * multiple frames together and send to camera device for processing where the request * controls are same for all the frames in this batch. Max batch size indicates @@ -1143,7 +1143,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Range of boosts for {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} supported * by this camera device.</p> * <p>Devices support post RAW sensitivity boost will advertise - * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controling + * {@link CaptureRequest#CONTROL_POST_RAW_SENSITIVITY_BOOST android.control.postRawSensitivityBoost} key for controlling * post RAW sensitivity boost.</p> * <p>This key will be <code>null</code> for devices that do not support any RAW format * outputs. For devices that do support RAW format outputs, this key will always @@ -1323,7 +1323,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>Maximum flashlight brightness level.</p> * <p>If this value is greater than 1, then the device supports controlling the * flashlight brightness level via - * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}. + * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }. * If this value is equal to 1, flashlight brightness control is not supported. * The value for this key will be null for devices with no flash unit.</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> @@ -1335,7 +1335,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>Default flashlight brightness level to be set via - * {android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel}.</p> + * {@link android.hardware.camera2.CameraManager#turnOnTorchWithStrengthLevel }.</p> * <p>If flash unit is available this will be greater than or equal to 1 and less * or equal to <code>{@link CameraCharacteristics#FLASH_INFO_STRENGTH_MAXIMUM_LEVEL android.flash.info.strengthMaximumLevel}</code>.</p> * <p>Setting flashlight brightness above the default level @@ -1376,7 +1376,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * camera device.</p> * <p>This list will include at least one non-zero resolution, plus <code>(0,0)</code> for indicating no * thumbnail should be generated.</p> - * <p>Below condiditions will be satisfied for this size list:</p> + * <p>Below conditions will be satisfied for this size list:</p> * <ul> * <li>The sizes will be sorted by increasing pixel area (width x height). * If several resolutions have the same area, they will be sorted by increasing width.</li> @@ -1982,7 +1982,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * the camera device. Using more streams simultaneously may require more hardware and * CPU resources that will consume more power. The image format for an output stream can * be any supported format provided by android.scaler.availableStreamConfigurations. - * The formats defined in android.scaler.availableStreamConfigurations can be catergorized + * The formats defined in android.scaler.availableStreamConfigurations can be categorized * into the 3 stream types as below:</p> * <ul> * <li>Processed (but stalling): any non-RAW format with a stallDurations > 0. @@ -2324,7 +2324,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * but clients should be aware and expect delays during their application. * An example usage scenario could look like this:</p> * <ul> - * <li>The camera client starts by quering the session parameter key list via + * <li>The camera client starts by querying the session parameter key list via * {@link android.hardware.camera2.CameraCharacteristics#getAvailableSessionKeys }.</li> * <li>Before triggering the capture session create sequence, a capture request * must be built via @@ -2379,7 +2379,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link android.hardware.camera2.CameraCharacteristics#getKeys } that require camera clients * to acquire the {@link android.Manifest.permission#CAMERA } permission before calling * {@link android.hardware.camera2.CameraManager#getCameraCharacteristics }. If the - * permission is not held by the camera client, then the values of the repsective properties + * permission is not held by the camera client, then the values of the respective properties * will not be present in {@link android.hardware.camera2.CameraCharacteristics }.</p> * <p>This key is available on all devices.</p> * @hide @@ -2759,7 +2759,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </table> * <p>For applications targeting SDK version 31 or newer, if the mobile device declares to be * media performance class 12 or higher by setting - * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger, + * {@link android.os.Build.VERSION_CODES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger, * the primary camera devices (first rear/front camera in the camera ID list) will not * support JPEG sizes smaller than 1080p. If the application configures a JPEG stream * smaller than 1080p, the camera device will round up the JPEG image size to at least @@ -2833,7 +2833,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * </table> * <p>For applications targeting SDK version 31 or newer, if the mobile device doesn't declare * to be media performance class 12 or better by setting - * {@link android.os.Build.VERSION_CDOES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger, + * {@link android.os.Build.VERSION_CODES.MEDIA_PERFORMANCE_CLASS } to be 31 or larger, * or if the camera device isn't a primary rear/front camera, the minimum required output * stream configurations are the same as for applications targeting SDK version older than * 31.</p> @@ -3485,14 +3485,16 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * to output different resolution images depending on the current active physical camera or * pixel mode. With multi-resolution input streams, the camera device can reprocess images * of different resolutions from different physical cameras or sensor pixel modes.</p> - * <p>When set to TRUE: - * * For a logical multi-camera, the camera framework derives + * <p>When set to TRUE:</p> + * <ul> + * <li>For a logical multi-camera, the camera framework derives * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap} by combining the * android.scaler.physicalCameraMultiResolutionStreamConfigurations from its physical - * cameras. - * * For an ultra-high resolution sensor camera, the camera framework directly copies + * cameras.</li> + * <li>For an ultra-high resolution sensor camera, the camera framework directly copies * the value of android.scaler.physicalCameraMultiResolutionStreamConfigurations to - * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap}.</p> + * {@link CameraCharacteristics#SCALER_MULTI_RESOLUTION_STREAM_CONFIGURATION_MAP android.scaler.multiResolutionStreamConfigurationMap}.</li> + * </ul> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * <p><b>Limited capability</b> - * Present on all camera devices that report being at least {@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED HARDWARE_LEVEL_LIMITED} devices in the @@ -3513,7 +3515,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * capture, video record for encoding the camera output for the purpose of future playback, * and video call for live realtime video conferencing.</p> * <p>With this flag, the camera device can optimize the image processing pipeline - * parameters, such as tuning, sensor mode, and ISP settings, indepedent of + * parameters, such as tuning, sensor mode, and ISP settings, independent of * the properties of the immediate camera output surface. For example, if the output * surface is a SurfaceTexture, the stream use case flag can be used to indicate whether * the camera frames eventually go to display, video encoder, @@ -3535,7 +3537,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } * capability is documented in the camera device * {@link android.hardware.camera2.CameraDevice#createCaptureSession guideline}. The - * application is strongly recommended to use one of the guaranteed stream combintations. + * application is strongly recommended to use one of the guaranteed stream combinations. * If the application creates a session with a stream combination not in the guaranteed * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session, * the camera device may ignore some stream use cases due to hardware constraints @@ -5209,7 +5211,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * EXTERIOR_* value.</p> * <p>If a camera has INTERIOR_OTHER or EXTERIOR_OTHER, or more than one camera is at the * same location and facing the same direction, their static metadata will list the - * following entries, so that applications can determain their lenses' exact facing + * following entries, so that applications can determine their lenses' exact facing * directions:</p> * <ul> * <li>{@link CameraCharacteristics#LENS_POSE_REFERENCE android.lens.poseReference}</li> diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 40565b06a8f2..eb8c73aced39 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -409,7 +409,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>The value of {@link CameraCharacteristics#LENS_POSE_TRANSLATION android.lens.poseTranslation} is relative to the origin of the - * automotive sensor coodinate system, which is at the center of the rear axle.</p> + * automotive sensor coordinate system, which is at the center of the rear axle.</p> * * @see CameraCharacteristics#LENS_POSE_TRANSLATION * @see CameraCharacteristics#LENS_POSE_REFERENCE @@ -683,7 +683,7 @@ public abstract class CameraMetadata<TKey> { * captured at the same rate as the maximum-size YUV_420_888 resolution is.</p> * <p>If the device supports the PRIVATE_REPROCESSING capability, then the same guarantees * as for the YUV_420_888 format also apply to the {@link android.graphics.ImageFormat#PRIVATE } format.</p> - * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranted to have a value between 0 + * <p>In addition, the {@link CameraCharacteristics#SYNC_MAX_LATENCY android.sync.maxLatency} field is guaranteed to have a value between 0 * and 4, inclusive. {@link CameraCharacteristics#CONTROL_AE_LOCK_AVAILABLE android.control.aeLockAvailable} and {@link CameraCharacteristics#CONTROL_AWB_LOCK_AVAILABLE android.control.awbLockAvailable} * are also guaranteed to be <code>true</code> so burst capture with these two locks ON yields * consistent image output.</p> @@ -843,7 +843,7 @@ public abstract class CameraMetadata<TKey> { * {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoSizes }.</li> * <li>The FPS ranges are selected from {@link android.hardware.camera2.params.StreamConfigurationMap#getHighSpeedVideoFpsRanges }.</li> * </ul> - * <p>When above conditions are NOT satistied, + * <p>When above conditions are NOT satisfied, * {@link android.hardware.camera2.CameraDevice#createConstrainedHighSpeedCaptureSession } * will fail.</p> * <p>Switching to a FPS range that has different maximum FPS may trigger some camera device @@ -986,7 +986,7 @@ public abstract class CameraMetadata<TKey> { * non-active physical cameras. For example, if the logical camera has a wide-ultrawide * configuration where the wide lens is the default, when the crop region is set to the * logical camera's active array size, (and the zoom ratio set to 1.0 starting from - * Android 11), a physical stream for the ultrawide camera may prefer outputing images + * Android 11), a physical stream for the ultrawide camera may prefer outputting images * with larger field-of-view than that of the wide camera for better stereo matching * margin or more robust motion tracking. At the same time, the physical non-RAW streams' * field of view must not be smaller than the requested crop region and zoom ratio, as @@ -1175,21 +1175,23 @@ public abstract class CameraMetadata<TKey> { * when {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} is set to * {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }), * the <code>RAW_SENSOR</code> stream will have a regular bayer pattern.</p> - * <p>This capability requires the camera device to support the following : - * * The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below + * <p>This capability requires the camera device to support the following :</p> + * <ul> + * <li>The {@link android.hardware.camera2.params.StreamConfigurationMap } mentioned below * refers to the one, described by - * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>. - * * One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>. - * * {@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input + * <code>{@link CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION android.scaler.streamConfigurationMapMaximumResolution}</code>.</li> + * <li>One input stream is supported, that is, <code>{@link CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS android.request.maxNumInputStreams} == 1</code>.</li> + * <li>{@link android.graphics.ImageFormat#RAW_SENSOR } is supported as an output/input * format, that is, {@link android.graphics.ImageFormat#RAW_SENSOR } is included in the - * lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }. - * * {@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput } - * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }. - * * Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)} - * * Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate - * drop relative to the sensor's maximum capture rate (at that resolution). - * * No CaptureRequest controls will be applicable when a request has an input target - * with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</p> + * lists of formats returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats } and {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputFormats }.</li> + * <li>{@link android.hardware.camera2.params.StreamConfigurationMap#getValidOutputFormatsForInput } + * returns non-empty int[] for each supported input format returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputFormats }.</li> + * <li>Each size returned by {@link android.hardware.camera2.params.StreamConfigurationMap#getInputSizes getInputSizes(ImageFormat.RAW_SENSOR)} is also included in {@link android.hardware.camera2.params.StreamConfigurationMap#getOutputSizes getOutputSizes(ImageFormat.RAW_SENSOR)}</li> + * <li>Using {@link android.graphics.ImageFormat#RAW_SENSOR } does not cause a frame rate + * drop relative to the sensor's maximum capture rate (at that resolution).</li> + * <li>No CaptureRequest controls will be applicable when a request has an input target + * with {@link android.graphics.ImageFormat#RAW_SENSOR } format.</li> + * </ul> * * @see CameraCharacteristics#REQUEST_MAX_NUM_INPUT_STREAMS * @see CameraCharacteristics#SCALER_STREAM_CONFIGURATION_MAP_MAXIMUM_RESOLUTION @@ -1205,16 +1207,18 @@ public abstract class CameraMetadata<TKey> { * {@link android.hardware.camera2.params.DynamicRangeProfiles#getSupportedProfiles }. * They can be configured as part of the capture session initialization via * {@link android.hardware.camera2.params.OutputConfiguration#setDynamicRangeProfile }. - * Cameras that enable this capability must also support the following: - * * Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 } - * * All mandatory stream combinations for this specific capability as per - * documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession } - * * In case the device is not able to capture some combination of supported + * Cameras that enable this capability must also support the following:</p> + * <ul> + * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li> + * <li>All mandatory stream combinations for this specific capability as per + * documentation {@link android.hardware.camera2.CameraDevice#createCaptureSession }</li> + * <li>In case the device is not able to capture some combination of supported * standard 8-bit and/or 10-bit dynamic range profiles within the same capture request, * then those constraints must be listed in - * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints } - * * Recommended dynamic range profile listed in - * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</p> + * {@link android.hardware.camera2.params.DynamicRangeProfiles#getProfileCaptureRequestConstraints }</li> + * <li>Recommended dynamic range profile listed in + * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_RECOMMENDED_TEN_BIT_DYNAMIC_RANGE_PROFILE }.</li> + * </ul> * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES */ public static final int REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT = 18; @@ -1224,22 +1228,26 @@ public abstract class CameraMetadata<TKey> { * {@link android.hardware.camera2.params.OutputConfiguration#setStreamUseCase } * so that the device can optimize camera pipeline parameters such as tuning, sensor * mode, or ISP settings for a specific user scenario. - * Some sample usages of this capability are: - * * Distinguish high quality YUV captures from a regular YUV stream where - * the image quality may not be as good as the JPEG stream, or - * * Use one stream to serve multiple purposes: viewfinder, video recording and + * Some sample usages of this capability are:</p> + * <ul> + * <li>Distinguish high quality YUV captures from a regular YUV stream where + * the image quality may not be as good as the JPEG stream, or</li> + * <li>Use one stream to serve multiple purposes: viewfinder, video recording and * still capture. This is common with applications that wish to apply edits equally - * to preview, saved images, and saved videos.</p> + * to preview, saved images, and saved videos.</li> + * </ul> * <p>This capability requires the camera device to support the following - * stream use cases: - * * DEFAULT for backward compatibility where the application doesn't set - * a stream use case - * * PREVIEW for live viewfinder and in-app image analysis - * * STILL_CAPTURE for still photo capture - * * VIDEO_RECORD for recording video clips - * * PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video - * recording, and still capture. - * * VIDEO_CALL for long running video calls</p> + * stream use cases:</p> + * <ul> + * <li>DEFAULT for backward compatibility where the application doesn't set + * a stream use case</li> + * <li>PREVIEW for live viewfinder and in-app image analysis</li> + * <li>STILL_CAPTURE for still photo capture</li> + * <li>VIDEO_RECORD for recording video clips</li> + * <li>PREVIEW_VIDEO_STILL for one single stream used for viewfinder, video + * recording, and still capture.</li> + * <li>VIDEO_CALL for long running video calls</li> + * </ul> * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES } * lists all of the supported stream use cases.</p> * <p>Refer to {@link android.hardware.camera2.CameraDevice#createCaptureSession } for the @@ -1391,10 +1399,10 @@ public abstract class CameraMetadata<TKey> { * <p>Live stream shown to the user.</p> * <p>Optimized for performance and usability as a viewfinder, but not necessarily for * image quality. The output is not meant to be persisted as saved images or video.</p> - * <p>No stall if android.control.<em> are set to FAST; may have stall if android.control.</em> - * are set to HIGH_QUALITY. This use case has the same behavior as the default - * SurfaceView and SurfaceTexture targets. Additionally, this use case can be used for - * in-app image analysis.</p> + * <p>No stall if android.control.* are set to FAST. There may be stall if + * they are set to HIGH_QUALITY. This use case has the same behavior as the + * default SurfaceView and SurfaceTexture targets. Additionally, this use case can be + * used for in-app image analysis.</p> * @see CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES */ public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW = 0x1; @@ -1441,7 +1449,7 @@ public abstract class CameraMetadata<TKey> { public static final int SCALER_AVAILABLE_STREAM_USE_CASES_PREVIEW_VIDEO_STILL = 0x4; /** - * <p>Long-running video call optimized for both power efficienty and video quality.</p> + * <p>Long-running video call optimized for both power efficiency and video quality.</p> * <p>The camera sensor may run in a lower-resolution mode to reduce power consumption * at the cost of some image and digital zoom quality. Unlike VIDEO_RECORD, VIDEO_CALL * outputs are expected to work in dark conditions, so are usually accompanied with @@ -2946,10 +2954,10 @@ public abstract class CameraMetadata<TKey> { * android.control.availableHighSpeedVideoConfigurations.</li> * <li>No processed non-stalling or raw streams are configured.</li> * </ul> - * <p>When above conditions are NOT satistied, the controls of this mode and + * <p>When above conditions are NOT satisfied, the controls of this mode and * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} will be ignored by the camera device, * the camera device will fall back to {@link CaptureRequest#CONTROL_MODE android.control.mode} <code>==</code> AUTO, - * and the returned capture result metadata will give the fps range choosen + * and the returned capture result metadata will give the fps range chosen * by the camera device.</p> * <p>Switching into or out of this mode may trigger some camera ISP/sensor * reconfigurations, which may introduce extra latency. It is recommended that @@ -3034,7 +3042,7 @@ public abstract class CameraMetadata<TKey> { * if the {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange} gives range of [100, 1600], * the camera device auto-exposure routine tuning process may limit the actual * exposure sensitivity range to [100, 1200] to ensure that the noise level isn't - * exessive in order to preserve the image quality. Under this situation, the image under + * excessive in order to preserve the image quality. Under this situation, the image under * low light may be under-exposed when the sensor max exposure time (bounded by the * {@link CaptureRequest#CONTROL_AE_TARGET_FPS_RANGE android.control.aeTargetFpsRange} when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} is one of the * ON_* modes) and effective max sensitivity are reached. This scene mode allows the @@ -3631,7 +3639,7 @@ public abstract class CameraMetadata<TKey> { public static final int TONEMAP_MODE_HIGH_QUALITY = 2; /** - * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to peform + * <p>Use the gamma value specified in {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma} to perform * tonemapping.</p> * <p>All color enhancement and tonemapping must be disabled, except * for applying the tonemapping curve specified by {@link CaptureRequest#TONEMAP_GAMMA android.tonemap.gamma}.</p> @@ -3644,7 +3652,7 @@ public abstract class CameraMetadata<TKey> { /** * <p>Use the preset tonemapping curve specified in - * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to peform tonemapping.</p> + * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve} to perform tonemapping.</p> * <p>All color enhancement and tonemapping must be disabled, except * for applying the tonemapping curve specified by * {@link CaptureRequest#TONEMAP_PRESET_CURVE android.tonemap.presetCurve}.</p> diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java index 15e59e03ee70..c5cf0f695040 100644 --- a/core/java/android/hardware/camera2/CaptureRequest.java +++ b/core/java/android/hardware/camera2/CaptureRequest.java @@ -1767,7 +1767,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * routine is enabled, overriding the application's selected * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} - * is OFF, the behavior of AWB is device dependent. It is recommened to + * is OFF, the behavior of AWB is device dependent. It is recommended to * also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before * setting AE mode to OFF.</p> * <p>When set to the OFF mode, the camera device's auto-white balance @@ -1917,13 +1917,15 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * strategy.</p> * <p>This control (except for MANUAL) is only effective if * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p> - * <p>All intents are supported by all devices, except that: - * * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains - * PRIVATE_REPROCESSING or YUV_REPROCESSING. - * * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains - * MANUAL_SENSOR. - * * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains - * MOTION_TRACKING.</p> + * <p>All intents are supported by all devices, except that:</p> + * <ul> + * <li>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains + * PRIVATE_REPROCESSING or YUV_REPROCESSING.</li> + * <li>MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains + * MANUAL_SENSOR.</li> + * <li>MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains + * MOTION_TRACKING.</li> + * </ul> * <p><b>Possible values:</b></p> * <ul> * <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li> @@ -2680,7 +2682,7 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> * and keep jpeg and thumbnail image data unrotated.</li> * <li>Rotate the jpeg and thumbnail image data and not set * {@link android.media.ExifInterface#TAG_ORIENTATION EXIF orientation flag}. In this - * case, LIMITED or FULL hardware level devices will report rotated thumnail size in + * case, LIMITED or FULL hardware level devices will report rotated thumbnail size in * capture result, so the width and height will be interchanged if 90 or 270 degree * orientation is requested. LEGACY device will always report unrotated thumbnail * size.</li> @@ -3806,9 +3808,11 @@ public final class CaptureRequest extends CameraMetadata<CaptureRequest.Key<?>> /** * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * GAMMA_VALUE</p> - * <p>The tonemap curve will be defined the following formula: - * * OUT = pow(IN, 1.0 / gamma) - * where IN and OUT is the input pixel value scaled to range [0.0, 1.0], + * <p>The tonemap curve will be defined the following formula:</p> + * <ul> + * <li>OUT = pow(IN, 1.0 / gamma)</li> + * </ul> + * <p>where IN and OUT is the input pixel value scaled to range [0.0, 1.0], * pow is the power function and gamma is the gamma value specified by this * key.</p> * <p>The same curve will be applied to all color channels. The camera device diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 1faec5b76524..3e1deb27584e 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -1173,7 +1173,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <td align="center">Any state (excluding LOCKED)</td> * <td align="center">{@link CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER android.control.aePrecaptureTrigger} is CANCEL, converged</td> * <td align="center">CONVERGED</td> - * <td align="center">Converged after a precapture sequenceis canceled, transient states are skipped by camera device.</td> + * <td align="center">Converged after a precapture sequences canceled, transient states are skipped by camera device.</td> * </tr> * <tr> * <td align="center">CONVERGED</td> @@ -1847,7 +1847,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * routine is enabled, overriding the application's selected * {@link CaptureRequest#COLOR_CORRECTION_TRANSFORM android.colorCorrection.transform}, {@link CaptureRequest#COLOR_CORRECTION_GAINS android.colorCorrection.gains} and * {@link CaptureRequest#COLOR_CORRECTION_MODE android.colorCorrection.mode}. Note that when {@link CaptureRequest#CONTROL_AE_MODE android.control.aeMode} - * is OFF, the behavior of AWB is device dependent. It is recommened to + * is OFF, the behavior of AWB is device dependent. It is recommended to * also set AWB mode to OFF or lock AWB by using {@link CaptureRequest#CONTROL_AWB_LOCK android.control.awbLock} before * setting AE mode to OFF.</p> * <p>When set to the OFF mode, the camera device's auto-white balance @@ -1997,13 +1997,15 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * strategy.</p> * <p>This control (except for MANUAL) is only effective if * <code>{@link CaptureRequest#CONTROL_MODE android.control.mode} != OFF</code> and any 3A routine is active.</p> - * <p>All intents are supported by all devices, except that: - * * ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains - * PRIVATE_REPROCESSING or YUV_REPROCESSING. - * * MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains - * MANUAL_SENSOR. - * * MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains - * MOTION_TRACKING.</p> + * <p>All intents are supported by all devices, except that:</p> + * <ul> + * <li>ZERO_SHUTTER_LAG will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains + * PRIVATE_REPROCESSING or YUV_REPROCESSING.</li> + * <li>MANUAL will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains + * MANUAL_SENSOR.</li> + * <li>MOTION_TRACKING will be supported if {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains + * MOTION_TRACKING.</li> + * </ul> * <p><b>Possible values:</b></p> * <ul> * <li>{@link #CONTROL_CAPTURE_INTENT_CUSTOM CUSTOM}</li> @@ -2929,7 +2931,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * and keep jpeg and thumbnail image data unrotated.</li> * <li>Rotate the jpeg and thumbnail image data and not set * {@link android.media.ExifInterface#TAG_ORIENTATION EXIF orientation flag}. In this - * case, LIMITED or FULL hardware level devices will report rotated thumnail size in + * case, LIMITED or FULL hardware level devices will report rotated thumbnail size in * capture result, so the width and height will be interchanged if 90 or 270 degree * orientation is requested. LEGACY device will always report unrotated thumbnail * size.</li> @@ -3149,7 +3151,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <p>When the state is STATIONARY, the lens parameters are not changing. This could be * either because the parameters are all fixed, or because the lens has had enough * time to reach the most recently-requested values. - * If all these lens parameters are not changable for a camera device, as listed below:</p> + * If all these lens parameters are not changeable for a camera device, as listed below:</p> * <ul> * <li>Fixed focus (<code>{@link CameraCharacteristics#LENS_INFO_MINIMUM_FOCUS_DISTANCE android.lens.info.minimumFocusDistance} == 0</code>), which means * {@link CaptureRequest#LENS_FOCUS_DISTANCE android.lens.focusDistance} parameter will always be 0.</li> @@ -4009,7 +4011,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * noise model used here is:</p> * <p>N(x) = sqrt(Sx + O)</p> * <p>Where x represents the recorded signal of a CFA channel normalized to - * the range [0, 1], and S and O are the noise model coeffiecients for + * the range [0, 1], and S and O are the noise model coefficients for * that channel.</p> * <p>A more detailed description of the noise model can be found in the * Adobe DNG specification for the NoiseProfile tag.</p> @@ -4054,7 +4056,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { * <li>1.20 <= R >= 1.03 will require some software * correction to avoid demosaic errors (3-20% divergence).</li> * <li>R > 1.20 will require strong software correction to produce - * a usuable image (>20% divergence).</li> + * a usable image (>20% divergence).</li> * </ul> * <p>Starting from Android Q, this key will not be present for a MONOCHROME camera, even if * the camera device has RAW capability.</p> @@ -4274,7 +4276,7 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>Whether <code>RAW</code> images requested have their bayer pattern as described by * {@link CameraCharacteristics#SENSOR_INFO_BINNING_FACTOR android.sensor.info.binningFactor}.</p> - * <p>This key will only be present in devices advertisting the + * <p>This key will only be present in devices advertising the * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } * capability which also advertise <code>REMOSAIC_REPROCESSING</code> capability. On all other devices * RAW targets will have a regular bayer pattern.</p> @@ -5128,9 +5130,11 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { /** * <p>Tonemapping curve to use when {@link CaptureRequest#TONEMAP_MODE android.tonemap.mode} is * GAMMA_VALUE</p> - * <p>The tonemap curve will be defined the following formula: - * * OUT = pow(IN, 1.0 / gamma) - * where IN and OUT is the input pixel value scaled to range [0.0, 1.0], + * <p>The tonemap curve will be defined the following formula:</p> + * <ul> + * <li>OUT = pow(IN, 1.0 / gamma)</li> + * </ul> + * <p>where IN and OUT is the input pixel value scaled to range [0.0, 1.0], * pow is the power function and gamma is the gamma value specified by this * key.</p> * <p>The same curve will be applied to all color channels. The camera device diff --git a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java index 1263da6e4884..4d0ba63d7759 100644 --- a/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraExtensionSessionImpl.java @@ -72,7 +72,7 @@ import java.util.Set; import java.util.concurrent.Executor; public final class CameraExtensionSessionImpl extends CameraExtensionSession { - private static final int PREVIEW_QUEUE_SIZE = 3; + private static final int PREVIEW_QUEUE_SIZE = 10; private static final String TAG = "CameraExtensionSessionImpl"; private final Executor mExecutor; @@ -1057,15 +1057,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mClientRequest)); if (mCaptureResultHandler != null) { - CameraMetadataNative captureResults = new CameraMetadataNative(); - for (CaptureResult.Key key : mSupportedResultKeys) { - Object value = result.get(key); - if (value != null) { - captureResults.set(key, value); - } - } mCaptureResultHandler.onCaptureCompleted(timestamp, - captureResults); + initializeFilteredResults(result)); } } finally { Binder.restoreCallingIdentity(ident); @@ -1127,6 +1120,11 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private class ImageCallback implements OnImageAvailableListener { @Override + public void onImageDropped(long timestamp) { + notifyCaptureFailed(); + } + + @Override public void onImageAvailable(ImageReader reader, Image img) { if (mCaptureFailed) { img.close(); @@ -1160,6 +1158,9 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private class ImageLoopbackCallback implements OnImageAvailableListener { @Override + public void onImageDropped(long timestamp) { } + + @Override public void onImageAvailable(ImageReader reader, Image img) { img.close(); } @@ -1221,7 +1222,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } private interface OnImageAvailableListener { - public void onImageAvailable (ImageReader reader, Image img); + void onImageDropped(long timestamp); + void onImageAvailable (ImageReader reader, Image img); } private class CameraOutputImageCallback implements ImageReader.OnImageAvailableListener, @@ -1263,6 +1265,29 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } else { mImageListenerMap.put(img.getTimestamp(), new Pair<>(img, null)); } + + notifyDroppedImages(timestamp); + } + + private void notifyDroppedImages(long timestamp) { + Set<Long> timestamps = mImageListenerMap.keySet(); + ArrayList<Long> removedTs = new ArrayList<>(); + for (long ts : timestamps) { + if (ts < timestamp) { + Log.e(TAG, "Dropped image with ts: " + ts); + Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(ts); + if (entry.second != null) { + entry.second.onImageDropped(ts); + } + if (entry.first != null) { + entry.first.close(); + } + removedTs.add(ts); + } + } + for (long ts : removedTs) { + mImageListenerMap.remove(ts); + } } public void registerListener(Long timestamp, OnImageAvailableListener listener) { @@ -1291,6 +1316,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { entry.first.close(); } } + for (long timestamp : mImageListenerMap.keySet()) { + Pair<Image, OnImageAvailableListener> entry = mImageListenerMap.get(timestamp); + if (entry.second != null) { + entry.second.onImageDropped(timestamp); + } + } mImageListenerMap.clear(); } } @@ -1447,7 +1478,6 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } else { notifyConfigurationFailure(); } - } @Override @@ -1536,7 +1566,16 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } else if (mPreviewProcessorType == IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR) { int idx = mPendingResultMap.indexOfKey(timestamp); - if (idx >= 0) { + + if ((idx >= 0) && (mPendingResultMap.get(timestamp).first == null)) { + // Image was dropped before we can receive the capture results + if ((mCaptureResultHandler != null)) { + mCaptureResultHandler.onCaptureCompleted(timestamp, + initializeFilteredResults(result)); + } + discardPendingRepeatingResults(idx, mPendingResultMap, false); + } else if (idx >= 0) { + // Image came before the capture results ParcelImage parcelImage = initializeParcelImage( mPendingResultMap.get(timestamp).first); try { @@ -1563,6 +1602,7 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } discardPendingRepeatingResults(idx, mPendingResultMap, false); } else { + // Image not yet available notifyClient = false; mPendingResultMap.put(timestamp, new Pair<>(null, @@ -1581,16 +1621,8 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { mClientRequest)); if ((mCaptureResultHandler != null) && (mPreviewProcessorType != IPreviewExtenderImpl.PROCESSOR_TYPE_IMAGE_PROCESSOR)) { - CameraMetadataNative captureResults = - new CameraMetadataNative(); - for (CaptureResult.Key key : mSupportedResultKeys) { - Object value = result.get(key); - if (value != null) { - captureResults.set(key, value); - } - } mCaptureResultHandler.onCaptureCompleted(timestamp, - captureResults); + initializeFilteredResults(result)); } } else { mExecutor.execute( @@ -1657,19 +1689,24 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { for (int i = idx; i >= 0; i--) { if (previewMap.valueAt(i).first != null) { previewMap.valueAt(i).first.close(); - } else { - if (mClientNotificationsEnabled && ((i != idx) || notifyCurrentIndex)) { - Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i)); - final long ident = Binder.clearCallingIdentity(); - try { - mExecutor.execute( - () -> mCallbacks - .onCaptureFailed(CameraExtensionSessionImpl.this, - mClientRequest)); - } finally { - Binder.restoreCallingIdentity(ident); - } + } else if (mClientNotificationsEnabled && (previewMap.valueAt(i).second != null) && + ((i != idx) || notifyCurrentIndex)) { + TotalCaptureResult result = previewMap.valueAt(i).second; + Long timestamp = result.get(CaptureResult.SENSOR_TIMESTAMP); + mCaptureResultHandler.onCaptureCompleted(timestamp, + initializeFilteredResults(result)); + + Log.w(TAG, "Preview frame drop with timestamp: " + previewMap.keyAt(i)); + final long ident = Binder.clearCallingIdentity(); + try { + mExecutor.execute( + () -> mCallbacks + .onCaptureFailed(CameraExtensionSessionImpl.this, + mClientRequest)); + } finally { + Binder.restoreCallingIdentity(ident); } + } previewMap.removeAt(i); } @@ -1683,6 +1720,12 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } @Override + public void onImageDropped(long timestamp) { + discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp), + mPendingResultMap, true); + } + + @Override public void onImageAvailable(ImageReader reader, Image img) { if (img == null) { Log.e(TAG, "Invalid image!"); @@ -1703,6 +1746,15 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { private class ImageProcessCallback implements OnImageAvailableListener { @Override + public void onImageDropped(long timestamp) { + discardPendingRepeatingResults(mPendingResultMap.indexOfKey(timestamp), + mPendingResultMap, true); + // Add an empty frame&results entry to flag that we dropped a frame + // and valid capture results can immediately return to client. + mPendingResultMap.put(timestamp, new Pair<>(null, null)); + } + + @Override public void onImageAvailable(ImageReader reader, Image img) { if (mPendingResultMap.size() + 1 >= PREVIEW_QUEUE_SIZE) { // We reached the maximum acquired images limit. This is possible in case we @@ -1768,6 +1820,17 @@ public final class CameraExtensionSessionImpl extends CameraExtensionSession { } } + private CameraMetadataNative initializeFilteredResults(TotalCaptureResult result) { + CameraMetadataNative captureResults = new CameraMetadataNative(); + for (CaptureResult.Key key : mSupportedResultKeys) { + Object value = result.get(key); + if (value != null) { + captureResults.set(key, value); + } + } + return captureResults; + } + private static Size findSmallestAspectMatchedSize(@NonNull List<Size> sizes, @NonNull Size arSize) { final float TOLL = .01f; diff --git a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java index 14ed689af26f..34c83366e42c 100644 --- a/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java +++ b/core/java/android/hardware/camera2/params/DynamicRangeProfiles.java @@ -277,7 +277,7 @@ public final class DynamicRangeProfiles { * profile.</p> * * @return non-modifiable set of dynamic range profiles - * @throws IllegalArgumentException - If the profile argument is not + * @throws IllegalArgumentException If the profile argument is not * within the list returned by * getSupportedProfiles() * @@ -303,7 +303,7 @@ public final class DynamicRangeProfiles { * * @return true if the given profile is not suitable for latency sensitive use cases, false * otherwise - * @throws IllegalArgumentException - If the profile argument is not + * @throws IllegalArgumentException If the profile argument is not * within the list returned by * getSupportedProfiles() * diff --git a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java index 4bf9a740f971..f701ec3ec367 100644 --- a/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java +++ b/core/java/android/hardware/fingerprint/FingerprintSensorPropertiesInternal.java @@ -39,6 +39,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna * See {@link FingerprintSensorProperties.SensorType}. */ public final @FingerprintSensorProperties.SensorType int sensorType; + public final boolean halControlsIllumination; private final List<SensorLocationInternal> mSensorLocations; @@ -46,6 +47,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna @SensorProperties.Strength int strength, int maxEnrollmentsPerUser, @NonNull List<ComponentInfoInternal> componentInfo, @FingerprintSensorProperties.SensorType int sensorType, + boolean halControlsIllumination, boolean resetLockoutRequiresHardwareAuthToken, @NonNull List<SensorLocationInternal> sensorLocations) { // IBiometricsFingerprint@2.1 handles lockout in the framework, so the challenge is not @@ -55,6 +57,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna super(sensorId, strength, maxEnrollmentsPerUser, componentInfo, resetLockoutRequiresHardwareAuthToken, false /* resetLockoutRequiresChallenge */); this.sensorType = sensorType; + this.halControlsIllumination = halControlsIllumination; this.mSensorLocations = List.copyOf(sensorLocations); } @@ -68,14 +71,15 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna boolean resetLockoutRequiresHardwareAuthToken) { // TODO(b/179175438): Value should be provided from the HAL this(sensorId, strength, maxEnrollmentsPerUser, componentInfo, sensorType, - resetLockoutRequiresHardwareAuthToken, List.of(new SensorLocationInternal( - "" /* displayId */, 540 /* sensorLocationX */, 1636 /* sensorLocationY */, - 130 /* sensorRadius */))); + false /* halControlsIllumination */, resetLockoutRequiresHardwareAuthToken, + List.of(new SensorLocationInternal("" /* displayId */, 540 /* sensorLocationX */, + 1636 /* sensorLocationY */, 130 /* sensorRadius */))); } protected FingerprintSensorPropertiesInternal(Parcel in) { super(in); sensorType = in.readInt(); + halControlsIllumination = in.readBoolean(); mSensorLocations = in.createTypedArrayList(SensorLocationInternal.CREATOR); } @@ -101,6 +105,7 @@ public class FingerprintSensorPropertiesInternal extends SensorPropertiesInterna public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(sensorType); + dest.writeBoolean(halControlsIllumination); dest.writeTypedList(mSensorLocations); } diff --git a/core/java/android/hardware/input/InputManagerInternal.java b/core/java/android/hardware/input/InputManagerInternal.java index b37c27c2a9e7..fc6bc555e823 100644 --- a/core/java/android/hardware/input/InputManagerInternal.java +++ b/core/java/android/hardware/input/InputManagerInternal.java @@ -75,8 +75,15 @@ public abstract class InputManagerInternal { /** * Sets the display id that the MouseCursorController will be forced to target. Pass * {@link android.view.Display#INVALID_DISPLAY} to clear the override. + * + * Note: This method generally blocks until the pointer display override has propagated. + * When setting a new override, the caller should ensure that an input device that can control + * the mouse pointer is connected. If a new override is set when no such input device is + * connected, the caller may be blocked for an arbitrary period of time. + * + * @return true if the pointer displayId was set successfully, or false if it fails. */ - public abstract void setVirtualMousePointerDisplayId(int pointerDisplayId); + public abstract boolean setVirtualMousePointerDisplayId(int pointerDisplayId); /** * Gets the display id that the MouseCursorController is being forced to target. Returns diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index f9ed0e3db499..3f49b73a78fc 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -37,6 +37,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodSession; import android.view.inputmethod.InputMethodSubtype; +import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.CancellationGroup; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; @@ -194,7 +195,9 @@ class IInputMethodWrapper extends IInputMethod.Stub case DO_START_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; final IBinder startInputToken = (IBinder) args.arg1; - final IInputContext inputContext = (IInputContext) args.arg2; + final IInputContext inputContext = (IInputContext) ((SomeArgs) args.arg2).arg1; + final ImeOnBackInvokedDispatcher imeDispatcher = + (ImeOnBackInvokedDispatcher) ((SomeArgs) args.arg2).arg2; final EditorInfo info = (EditorInfo) args.arg3; final CancellationGroup cancellationGroup = (CancellationGroup) args.arg4; final boolean restarting = args.argi5 == 1; @@ -205,7 +208,7 @@ class IInputMethodWrapper extends IInputMethod.Stub : null; info.makeCompatible(mTargetSdkVersion); inputMethod.dispatchStartInputWithToken(ic, info, restarting, startInputToken, - navButtonFlags); + navButtonFlags, imeDispatcher); args.recycle(); return; } @@ -348,13 +351,17 @@ class IInputMethodWrapper extends IInputMethod.Stub @Override public void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, boolean restarting, - @InputMethodNavButtonFlags int navButtonFlags) { + @InputMethodNavButtonFlags int navButtonFlags, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (mCancellationGroup == null) { Log.e(TAG, "startInput must be called after bindInput."); mCancellationGroup = new CancellationGroup(); } + SomeArgs args = SomeArgs.obtain(); + args.arg1 = inputContext; + args.arg2 = imeDispatcher; mCaller.executeOrSendMessage(mCaller.obtainMessageOOOOII(DO_START_INPUT, startInputToken, - inputContext, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags)); + args, attribute, mCancellationGroup, restarting ? 1 : 0, navButtonFlags)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index efd4f0681838..25296bc0a8b9 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -101,6 +101,7 @@ import android.view.BatchedInputEventReceiver.SimpleBatchedInputEventReceiver; import android.view.Choreographer; import android.view.Gravity; import android.view.InputChannel; +import android.view.InputDevice; import android.view.InputEventReceiver; import android.view.KeyCharacterMap; import android.view.KeyEvent; @@ -134,6 +135,9 @@ import android.widget.FrameLayout; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import android.window.ImeOnBackInvokedDispatcher; +import android.window.OnBackInvokedCallback; +import android.window.OnBackInvokedDispatcher; import android.window.WindowMetricsHelper; import com.android.internal.annotations.GuardedBy; @@ -344,6 +348,9 @@ public class InputMethodService extends AbstractInputMethodService { * A circular buffer of size MAX_EVENTS_BUFFER in case IME is taking too long to add ink view. **/ private RingBuffer<MotionEvent> mPendingEvents; + private ImeOnBackInvokedDispatcher mImeDispatcher; + private Boolean mBackCallbackRegistered = false; + private final OnBackInvokedCallback mCompatBackCallback = this::compatHandleBack; /** * Returns whether {@link InputMethodService} is responsible for rendering the back button and @@ -797,7 +804,13 @@ public class InputMethodService extends AbstractInputMethodService { @Override public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) { + @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + mImeDispatcher = imeDispatcher; + if (mWindow != null) { + mWindow.getOnBackInvokedDispatcher().setImeOnBackInvokedDispatcher( + imeDispatcher); + } mPrivOps.reportStartInputAsync(startInputToken); mNavigationBarController.onNavButtonFlagsChanged(navButtonFlags); if (restarting) { @@ -1496,6 +1509,10 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.initSoftInputWindow"); mWindow = new SoftInputWindow(this, mTheme, mDispatcherState); + if (mImeDispatcher != null) { + mWindow.getOnBackInvokedDispatcher() + .setImeOnBackInvokedDispatcher(mImeDispatcher); + } mNavigationBarController.onSoftInputWindowCreated(mWindow); { final Window window = mWindow.getWindow(); @@ -1608,6 +1625,8 @@ public class InputMethodService extends AbstractInputMethodService { // when IME developers are doing something unsupported. InputMethodPrivilegedOperationsRegistry.remove(mToken); } + unregisterCompatOnBackInvokedCallback(); + mImeDispatcher = null; } /** @@ -2568,8 +2587,46 @@ public class InputMethodService extends AbstractInputMethodService { cancelImeSurfaceRemoval(); mInShowWindow = false; Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + registerCompatOnBackInvokedCallback(); + } + + + /** + * Registers an {@link OnBackInvokedCallback} to handle back invocation when ahead-of-time + * back dispatching is enabled. We keep the {@link KeyEvent#KEYCODE_BACK} based legacy code + * around to handle back on older devices. + */ + private void registerCompatOnBackInvokedCallback() { + if (mBackCallbackRegistered) { + return; + } + if (mWindow != null) { + mWindow.getOnBackInvokedDispatcher().registerOnBackInvokedCallback( + OnBackInvokedDispatcher.PRIORITY_DEFAULT, mCompatBackCallback); + mBackCallbackRegistered = true; + } + } + + private void unregisterCompatOnBackInvokedCallback() { + if (!mBackCallbackRegistered) { + return; + } + if (mWindow != null) { + mWindow.getOnBackInvokedDispatcher() + .unregisterOnBackInvokedCallback(mCompatBackCallback); + mBackCallbackRegistered = false; + } } + private KeyEvent createBackKeyEvent(int action, boolean isTracking) { + final long when = SystemClock.uptimeMillis(); + return new KeyEvent(when, when, action, + KeyEvent.KEYCODE_BACK, 0 /* repeat */, 0 /* metaState */, + KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */, + KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY + | (isTracking ? KeyEvent.FLAG_TRACKING : 0), + InputDevice.SOURCE_KEYBOARD); + } private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; @@ -2658,6 +2715,7 @@ public class InputMethodService extends AbstractInputMethodService { } mLastWasInFullscreenMode = mIsFullscreen; updateFullscreenMode(); + unregisterCompatOnBackInvokedCallback(); } /** @@ -3797,4 +3855,14 @@ public class InputMethodService extends AbstractInputMethodService { proto.end(token); } }; + + private void compatHandleBack() { + final KeyEvent downEvent = createBackKeyEvent( + KeyEvent.ACTION_DOWN, false /* isTracking */); + onKeyDown(KeyEvent.KEYCODE_BACK, downEvent); + final boolean hasStartedTracking = + (downEvent.getFlags() & KeyEvent.FLAG_START_TRACKING) != 0; + final KeyEvent upEvent = createBackKeyEvent(KeyEvent.ACTION_UP, hasStartedTracking); + onKeyUp(KeyEvent.KEYCODE_BACK, upEvent); + } } diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java index ecdc803c0074..022d213e84da 100644 --- a/core/java/android/os/Environment.java +++ b/core/java/android/os/Environment.java @@ -1361,6 +1361,18 @@ public class Environment { return false; } + // Apps with PROPERTY_NO_APP_DATA_STORAGE should not be allowed in scoped storage + final String packageName = AppGlobals.getInitialPackage(); + try { + final PackageManager.Property noAppStorageProp = packageManager.getProperty( + PackageManager.PROPERTY_NO_APP_DATA_STORAGE, packageName); + if (noAppStorageProp != null && noAppStorageProp.getBoolean()) { + return false; + } + } catch (PackageManager.NameNotFoundException ignore) { + // Property not defined for the package + } + boolean defaultScopedStorage = Compatibility.isChangeEnabled(DEFAULT_SCOPED_STORAGE); boolean forceEnableScopedStorage = Compatibility.isChangeEnabled( FORCE_ENABLE_SCOPED_STORAGE); diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index a64e63eacd56..196f2f94120e 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java @@ -2179,7 +2179,10 @@ public class UserManager { } } else { UserInfo userInfo = getUserInfo(mUserId); - return userInfo == null ? "" : userInfo.name; + if (userInfo != null && userInfo.name != null) { + return userInfo.name; + } + return ""; } } diff --git a/core/java/android/provider/CallLog.java b/core/java/android/provider/CallLog.java index 0e5a65ce71b7..77c00676878c 100644 --- a/core/java/android/provider/CallLog.java +++ b/core/java/android/provider/CallLog.java @@ -1292,7 +1292,8 @@ public class CallLog { USER_MISSED_LOW_RING_VOLUME, USER_MISSED_NO_VIBRATE, USER_MISSED_CALL_SCREENING_SERVICE_SILENCED, - USER_MISSED_CALL_FILTERS_TIMEOUT + USER_MISSED_CALL_FILTERS_TIMEOUT, + USER_MISSED_NEVER_RANG }) @Retention(RetentionPolicy.SOURCE) public @interface MissedReason {} @@ -1383,6 +1384,13 @@ public class CallLog { public static final long USER_MISSED_CALL_FILTERS_TIMEOUT = 1 << 22; /** + * When {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, set this bit when + * the call ended before ringing. + * @hide + */ + public static final long USER_MISSED_NEVER_RANG = 1 << 23; + + /** * Where the {@link CallLog.Calls#TYPE} is {@link CallLog.Calls#MISSED_TYPE}, * indicates factors which may have lead the user to miss the call. * <P>Type: INTEGER</P> diff --git a/core/java/android/provider/DeviceConfig.java b/core/java/android/provider/DeviceConfig.java index 37f44e98c165..9a2f7baa7265 100644 --- a/core/java/android/provider/DeviceConfig.java +++ b/core/java/android/provider/DeviceConfig.java @@ -738,6 +738,14 @@ public final class DeviceConfig { */ public static final String NAMESPACE_VENDOR_SYSTEM_NATIVE = "vendor_system_native"; + /** + * Namespace for DevicePolicyManager related features. + * + * @hide + */ + public static final String NAMESPACE_DEVICE_POLICY_MANAGER = + "device_policy_manager"; + private static final Object sLock = new Object(); @GuardedBy("sLock") private static ArrayMap<OnPropertiesChangedListener, Pair<String, Executor>> sListeners = diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index dac54cf6146e..f35a45891545 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -9695,6 +9695,40 @@ public final class Settings { "active_unlock_on_biometric_fail"; /** + * If active unlock triggers on biometric failures, include the following error codes + * as a biometric failure. See {@link android.hardware.biometrics.BiometricFaceConstants}. + * Error codes should be separated by a pipe. For example: "1|4|5". If active unlock + * should never trigger on any face errors, this should be set to an empty string. + * A null value will use the system default value (TIMEOUT). + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_FACE_ERRORS = + "active_unlock_on_face_errors"; + + /** + * If active unlock triggers on biometric failures, include the following acquired info + * as a "biometric failure". See {@link android.hardware.biometrics.BiometricFaceConstants}. + * Acquired codes should be separated by a pipe. For example: "1|4|5". If active unlock + * should never on trigger on any acquired info messages, this should be + * set to an empty string. A null value will use the system default value (none). + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO = + "active_unlock_on_face_acquire_info"; + + /** + * If active unlock triggers on biometric failures, then also request active unlock on + * unlock intent when each setting (BiometricType) is the only biometric type enrolled. + * Biometric types should be separated by a pipe. For example: "0|3" or "0". If this + * setting should be disabled, then this should be set to an empty string. A null value + * will use the system default value (0 / None). + * 0 = None, 1 = Any face, 2 = Any fingerprint, 3 = Under display fingerprint + * @hide + */ + public static final String ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED = + "active_unlock_on_unlock_intent_when_biometric_enrolled"; + + /** * Whether the assist gesture should be enabled. * * @hide diff --git a/core/java/android/service/quickaccesswallet/TEST_MAPPING b/core/java/android/service/quickaccesswallet/TEST_MAPPING index 4d97ab6e612d..5d2a3a806a7b 100644 --- a/core/java/android/service/quickaccesswallet/TEST_MAPPING +++ b/core/java/android/service/quickaccesswallet/TEST_MAPPING @@ -1,5 +1,5 @@ { - "presubmit": [ + "presubmit-large": [ { "name": "CtsQuickAccessWalletTestCases" } diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java index 3be4c3edc10e..24ded932b636 100644 --- a/core/java/android/util/FeatureFlagUtils.java +++ b/core/java/android/util/FeatureFlagUtils.java @@ -50,12 +50,21 @@ public class FeatureFlagUtils { public static final String SETTINGS_ENABLE_SECURITY_HUB = "settings_enable_security_hub"; /** @hide */ public static final String SETTINGS_SUPPORT_LARGE_SCREEN = "settings_support_large_screen"; + /** * Support per app's language selection * @hide */ public static final String SETTINGS_APP_LANGUAGE_SELECTION = "settings_app_language_selection"; + /** + * Support locale opt-out and opt-in switch for per app's language. + * @hide + */ + public static final String SETTINGS_APP_LOCALE_OPT_IN_ENABLED = + "settings_app_locale_opt_in_enabled"; + + /** @hide */ public static final String SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS = "settings_enable_monitor_phantom_procs"; @@ -97,6 +106,7 @@ public class FeatureFlagUtils { DEFAULT_FLAGS.put(SETTINGS_SUPPORT_LARGE_SCREEN, "true"); DEFAULT_FLAGS.put("settings_search_always_expand", "true"); DEFAULT_FLAGS.put(SETTINGS_APP_LANGUAGE_SELECTION, "true"); + DEFAULT_FLAGS.put(SETTINGS_APP_LOCALE_OPT_IN_ENABLED, "true"); DEFAULT_FLAGS.put(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS, "true"); DEFAULT_FLAGS.put(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME, "true"); DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true"); @@ -106,6 +116,7 @@ public class FeatureFlagUtils { static { PERSISTENT_FLAGS = new HashSet<>(); PERSISTENT_FLAGS.add(SETTINGS_APP_LANGUAGE_SELECTION); + PERSISTENT_FLAGS.add(SETTINGS_APP_LOCALE_OPT_IN_ENABLED); PERSISTENT_FLAGS.add(SETTINGS_SUPPORT_LARGE_SCREEN); PERSISTENT_FLAGS.add(SETTINGS_ENABLE_MONITOR_PHANTOM_PROCS); PERSISTENT_FLAGS.add(SETTINGS_APP_ALLOW_DARK_THEME_ACTIVATION_AT_BEDTIME); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index fd557e79c9c1..2e48c2b77233 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -719,9 +719,14 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall private void releaseSurfaces(boolean releaseSurfacePackage) { mSurfaceAlpha = 1f; - - synchronized (mSurfaceControlLock) { + + mSurfaceLock.lock(); + try { mSurface.destroy(); + } finally { + mSurfaceLock.unlock(); + } + synchronized (mSurfaceControlLock) { if (mBlastBufferQueue != null) { mBlastBufferQueue.destroy(); mBlastBufferQueue = null; @@ -770,105 +775,99 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall Transaction surfaceUpdateTransaction) { boolean realSizeChanged = false; - mSurfaceLock.lock(); - try { - mDrawingStopped = !mVisible; + mDrawingStopped = !mVisible; - if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " - + "Cur surface: " + mSurface); - - // If we are creating the surface control or the parent surface has not - // changed, then set relative z. Otherwise allow the parent - // SurfaceChangedCallback to update the relative z. This is needed so that - // we do not change the relative z before the server is ready to swap the - // parent surface. - if (creating) { - updateRelativeZ(surfaceUpdateTransaction); - if (mSurfacePackage != null) { - reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage); - } - } - mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId(); + if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + + "Cur surface: " + mSurface); - if (mViewVisibility) { - surfaceUpdateTransaction.show(mSurfaceControl); - } else { - surfaceUpdateTransaction.hide(mSurfaceControl); + // If we are creating the surface control or the parent surface has not + // changed, then set relative z. Otherwise allow the parent + // SurfaceChangedCallback to update the relative z. This is needed so that + // we do not change the relative z before the server is ready to swap the + // parent surface. + if (creating) { + updateRelativeZ(surfaceUpdateTransaction); + if (mSurfacePackage != null) { + reparentSurfacePackage(surfaceUpdateTransaction, mSurfacePackage); } + } + mParentSurfaceSequenceId = viewRoot.getSurfaceSequenceId(); + if (mViewVisibility) { + surfaceUpdateTransaction.show(mSurfaceControl); + } else { + surfaceUpdateTransaction.hide(mSurfaceControl); + } - updateBackgroundVisibility(surfaceUpdateTransaction); - updateBackgroundColor(surfaceUpdateTransaction); - if (mUseAlpha) { - float alpha = getFixedAlpha(); - surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha); - mSurfaceAlpha = alpha; - } - surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius); - if ((sizeChanged || hintChanged) && !creating) { - setBufferSize(surfaceUpdateTransaction); - } - if (sizeChanged || creating || !isHardwareAccelerated()) { - - // Set a window crop when creating the surface or changing its size to - // crop the buffer to the surface size since the buffer producer may - // use SCALING_MODE_SCALE and submit a larger size than the surface - // size. - if (mClipSurfaceToBounds && mClipBounds != null) { - surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds); - } else { - surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth, - mSurfaceHeight); - } + updateBackgroundVisibility(surfaceUpdateTransaction); + updateBackgroundColor(surfaceUpdateTransaction); + if (mUseAlpha) { + float alpha = getFixedAlpha(); + surfaceUpdateTransaction.setAlpha(mSurfaceControl, alpha); + mSurfaceAlpha = alpha; + } - surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth, - mSurfaceHeight); - - if (isHardwareAccelerated()) { - // This will consume the passed in transaction and the transaction will be - // applied on a render worker thread. - replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight); - } else { - onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl, - mScreenRect.left /*positionLeft*/, - mScreenRect.top /*positionTop*/, - mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/, - mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/); - } - if (DEBUG_POSITION) { - Log.d(TAG, String.format( - "%d performSurfaceTransaction %s " - + "position = [%d, %d, %d, %d] surfaceSize = %dx%d", - System.identityHashCode(this), - isHardwareAccelerated() ? "RenderWorker" : "UI Thread", - mScreenRect.left, mScreenRect.top, mScreenRect.right, - mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight)); - } + surfaceUpdateTransaction.setCornerRadius(mSurfaceControl, mCornerRadius); + if ((sizeChanged || hintChanged) && !creating) { + setBufferSize(surfaceUpdateTransaction); + } + if (sizeChanged || creating || !isHardwareAccelerated()) { + // Set a window crop when creating the surface or changing its size to + // crop the buffer to the surface size since the buffer producer may + // use SCALING_MODE_SCALE and submit a larger size than the surface + // size. + if (mClipSurfaceToBounds && mClipBounds != null) { + surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mClipBounds); + } else { + surfaceUpdateTransaction.setWindowCrop(mSurfaceControl, mSurfaceWidth, + mSurfaceHeight); } - applyTransactionOnVriDraw(surfaceUpdateTransaction); - updateEmbeddedAccessibilityMatrix(false); - - mSurfaceFrame.left = 0; - mSurfaceFrame.top = 0; - if (translator == null) { - mSurfaceFrame.right = mSurfaceWidth; - mSurfaceFrame.bottom = mSurfaceHeight; + + surfaceUpdateTransaction.setDesintationFrame(mBlastSurfaceControl, mSurfaceWidth, + mSurfaceHeight); + + if (isHardwareAccelerated()) { + // This will consume the passed in transaction and the transaction will be + // applied on a render worker thread. + replacePositionUpdateListener(mSurfaceWidth, mSurfaceHeight); } else { - float appInvertedScale = translator.applicationInvertedScale; - mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f); - mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f); + onSetSurfacePositionAndScale(surfaceUpdateTransaction, mSurfaceControl, + mScreenRect.left /*positionLeft*/, + mScreenRect.top /*positionTop*/, + mScreenRect.width() / (float) mSurfaceWidth /*postScaleX*/, + mScreenRect.height() / (float) mSurfaceHeight /*postScaleY*/); } - final int surfaceWidth = mSurfaceFrame.right; - final int surfaceHeight = mSurfaceFrame.bottom; - realSizeChanged = mLastSurfaceWidth != surfaceWidth - || mLastSurfaceHeight != surfaceHeight; - mLastSurfaceWidth = surfaceWidth; - mLastSurfaceHeight = surfaceHeight; - } finally { - mSurfaceLock.unlock(); + if (DEBUG_POSITION) { + Log.d(TAG, String.format( + "%d performSurfaceTransaction %s " + + "position = [%d, %d, %d, %d] surfaceSize = %dx%d", + System.identityHashCode(this), + isHardwareAccelerated() ? "RenderWorker" : "UI Thread", + mScreenRect.left, mScreenRect.top, mScreenRect.right, + mScreenRect.bottom, mSurfaceWidth, mSurfaceHeight)); + } + } + applyTransactionOnVriDraw(surfaceUpdateTransaction); + updateEmbeddedAccessibilityMatrix(false); + mSurfaceFrame.left = 0; + mSurfaceFrame.top = 0; + if (translator == null) { + mSurfaceFrame.right = mSurfaceWidth; + mSurfaceFrame.bottom = mSurfaceHeight; + } else { + float appInvertedScale = translator.applicationInvertedScale; + mSurfaceFrame.right = (int) (mSurfaceWidth * appInvertedScale + 0.5f); + mSurfaceFrame.bottom = (int) (mSurfaceHeight * appInvertedScale + 0.5f); } + final int surfaceWidth = mSurfaceFrame.right; + final int surfaceHeight = mSurfaceFrame.bottom; + realSizeChanged = mLastSurfaceWidth != surfaceWidth + || mLastSurfaceHeight != surfaceHeight; + mLastSurfaceWidth = surfaceWidth; + mLastSurfaceHeight = surfaceHeight; + return realSizeChanged; } @@ -1103,21 +1102,30 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall * Surface for compatibility reasons. */ private void copySurface(boolean surfaceControlCreated, boolean bufferSizeChanged) { - if (surfaceControlCreated) { - mSurface.copyFrom(mBlastBufferQueue); - } - - if (bufferSizeChanged && getContext().getApplicationInfo().targetSdkVersion - < Build.VERSION_CODES.O) { - // Some legacy applications use the underlying native {@link Surface} object - // as a key to whether anything has changed. In these cases, updates to the - // existing {@link Surface} will be ignored when the size changes. - // Therefore, we must explicitly recreate the {@link Surface} in these - // cases. - if (mBlastBufferQueue != null) { - mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle()); - } - } + // Some legacy applications use the underlying native {@link Surface} object + // as a key to whether anything has changed. In these cases, updates to the + // existing {@link Surface} will be ignored when the size changes. + // Therefore, we must explicitly recreate the {@link Surface} in these + // cases. + boolean needsWorkaround = bufferSizeChanged && + getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O; + if (!surfaceControlCreated && !needsWorkaround) { + return; + } + mSurfaceLock.lock(); + try { + if (surfaceControlCreated) { + mSurface.copyFrom(mBlastBufferQueue); + } + + if (needsWorkaround) { + if (mBlastBufferQueue != null) { + mSurface.transferFrom(mBlastBufferQueue.createSurfaceWithHandle()); + } + } + } finally { + mSurfaceLock.unlock(); + } } private void setBufferSize(Transaction transaction) { @@ -1200,8 +1208,10 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall } mTransformHint = viewRoot.getBufferTransformHint(); mBlastSurfaceControl.setTransformHint(mTransformHint); + mBlastBufferQueue = new BLASTBufferQueue(name, false /* updateDestinationFrame */); mBlastBufferQueue.update(mBlastSurfaceControl, mSurfaceWidth, mSurfaceHeight, mFormat); + mBlastBufferQueue.setTransactionHangCallback(ViewRootImpl.sTransactionHangCallback); } private void onDrawFinished() { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 8901d86e1f2a..d04b07c13b41 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -8206,24 +8206,25 @@ public class View implements Drawable.Callback, KeyEvent.Callback, if (canNotifyAutofillEnterExitEvent()) { AutofillManager afm = getAutofillManager(); if (afm != null) { - if (enter && isFocused()) { + if (enter) { // We have not been laid out yet, hence cannot evaluate // whether this view is visible to the user, we will do // the evaluation once layout is complete. if (!isLaidOut()) { mPrivateFlags3 |= PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT; } else if (isVisibleToUser()) { - // TODO This is a potential problem that View gets focus before it's visible - // to User. Ideally View should handle the event when isVisibleToUser() - // becomes true where it should issue notifyViewEntered(). - afm.notifyViewEntered(this); - } else { - afm.enableFillRequestActivityStarted(this); + if (isFocused()) { + // TODO This is a potential problem that View gets focus before it's + // visible to User. Ideally View should handle the event when + // isVisibleToUser() becomes true where it should issue + // notifyViewEntered(). + afm.notifyViewEntered(this); + } else { + afm.notifyViewEnteredForFillDialog(this); + } } - } else if (!enter && !isFocused()) { + } else if (!isFocused()) { afm.notifyViewExited(this); - } else if (enter) { - afm.enableFillRequestActivityStarted(this); } } } @@ -9921,7 +9922,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback, * <ol> * <li>It should only be called when content capture is enabled for the view. * <li>It must call viewAppeared() before viewDisappeared() - * <li>viewAppearead() can only be called when the view is visible and laidout + * <li>viewAppeared() can only be called when the view is visible and laid out * <li>It should not call the same event twice. * </ol> */ @@ -9998,6 +9999,11 @@ public class View implements Drawable.Callback, KeyEvent.Callback, Log.v(CONTENT_CAPTURE_LOG_TAG, "no AttachInfo on disappeared for " + this); } } + + // We reset any translation state as views may be re-used (e.g., as in ListView and + // RecyclerView). We only need to do this for views important for content capture since + // views unimportant for content capture won't be translated anyway. + clearTranslationState(); } } @@ -12718,6 +12724,21 @@ public class View implements Drawable.Callback, KeyEvent.Callback, } /** + * @hide + */ + public void clearTranslationState() { + if (mViewTranslationCallback != null) { + mViewTranslationCallback.onClearTranslation(this); + } + clearViewTranslationCallback(); + clearViewTranslationResponse(); + if (hasTranslationTransientState()) { + setHasTransientState(false); + setHasTranslationTransientState(false); + } + } + + /** * Returns true if this view is currently attached to a window. */ public boolean isAttachedToWindow() { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 42b5691b239e..48c102bd4883 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -590,7 +590,6 @@ public final class ViewRootImpl implements ViewParent, @Nullable int mContentCaptureEnabled = CONTENT_CAPTURE_ENABLED_NOT_CHECKED; boolean mPerformContentCapture; - boolean mPerformAutoFill; boolean mReportNextDraw; @@ -858,6 +857,28 @@ public final class ViewRootImpl implements ViewParent, */ private Bundle mRelayoutBundle = new Bundle(); + private static volatile boolean sAnrReported = false; + static BLASTBufferQueue.TransactionHangCallback sTransactionHangCallback = + new BLASTBufferQueue.TransactionHangCallback() { + @Override + public void onTransactionHang(boolean isGPUHang) { + if (isGPUHang && !sAnrReported) { + sAnrReported = true; + try { + ActivityManager.getService().appNotResponding( + "Buffer processing hung up due to stuck fence. Indicates GPU hang"); + } catch (RemoteException e) { + // We asked the system to crash us, but the system + // already crashed. Unfortunately things may be + // out of control. + } + } else { + // TODO: Do something with this later. For now we just ANR + // in dequeue buffer later like we always have. + } + } + }; + private String mTag = TAG; public ViewRootImpl(Context context, Display display) { @@ -890,7 +911,6 @@ public final class ViewRootImpl implements ViewParent, mPreviousTransparentRegion = new Region(); mFirst = true; // true for the first time the view is added mPerformContentCapture = true; // also true for the first time the view is added - mPerformAutoFill = true; mAdded = false; mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); @@ -2100,6 +2120,7 @@ public final class ViewRootImpl implements ViewParent, } mBlastBufferQueue = new BLASTBufferQueue(mTag, mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y, mWindowAttributes.format); + mBlastBufferQueue.setTransactionHangCallback(sTransactionHangCallback); Surface blastSurface = mBlastBufferQueue.createSurface(); // Only call transferFrom if the surface has changed to prevent inc the generation ID and // causing EGL resources to be recreated. @@ -4308,18 +4329,6 @@ public final class ViewRootImpl implements ViewParent, if (mPerformContentCapture) { performContentCaptureInitialReport(); } - - if (mPerformAutoFill) { - notifyEnterForAutoFillIfNeeded(); - } - } - - private void notifyEnterForAutoFillIfNeeded() { - mPerformAutoFill = false; - final AutofillManager afm = getAutofillManager(); - if (afm != null) { - afm.notifyViewEnteredForActivityStarted(mView); - } } /** @@ -6086,6 +6095,28 @@ public final class ViewRootImpl implements ViewParent, @Override protected int onProcess(QueuedInputEvent q) { + if (q.mEvent instanceof KeyEvent) { + final KeyEvent event = (KeyEvent) q.mEvent; + + // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the + // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}. + if (isBack(event) + && mContext != null + && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { + OnBackInvokedCallback topCallback = + getOnBackInvokedDispatcher().getTopCallback(); + if (event.getAction() == KeyEvent.ACTION_UP) { + if (topCallback != null) { + topCallback.onBackInvoked(); + return FINISH_HANDLED; + } + } else { + // Drop other actions such as {@link KeyEvent.ACTION_DOWN}. + return FINISH_NOT_HANDLED; + } + } + } + if (mInputQueue != null && q.mEvent instanceof KeyEvent) { mInputQueue.sendInputEvent(q.mEvent, q, true, this); return DEFER; @@ -6437,24 +6468,6 @@ public final class ViewRootImpl implements ViewParent, return FINISH_HANDLED; } - // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the - // view tree and invoke the appropriate {@link OnBackInvokedCallback}. - if (isBack(event) - && mContext != null - && WindowOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled(mContext)) { - OnBackInvokedCallback topCallback = - getOnBackInvokedDispatcher().getTopCallback(); - if (event.getAction() == KeyEvent.ACTION_UP) { - if (topCallback != null) { - topCallback.onBackInvoked(); - return FINISH_HANDLED; - } - } else { - // Drop other actions such as {@link KeyEvent.ACTION_DOWN}. - return FINISH_NOT_HANDLED; - } - } - // Deliver the key to the view hierarchy. if (mView.dispatchKeyEvent(event)) { return FINISH_HANDLED; diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java index 0a75992811f4..dcedb3083f76 100644 --- a/core/java/android/view/autofill/AutofillManager.java +++ b/core/java/android/view/autofill/AutofillManager.java @@ -102,6 +102,7 @@ import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import sun.misc.Cleaner; @@ -645,16 +646,6 @@ public final class AutofillManager { private boolean mEnabledForAugmentedAutofillOnly; /** - * Indicates whether there are any fields that need to do a fill request - * after the activity starts. - * - * Note: This field will be set to true multiple times if there are many - * autofillable views. So needs to check mIsFillRequested at the same time to - * avoid re-trigger autofill. - */ - private boolean mRequireAutofill; - - /** * Indicates whether there is already a field to do a fill request after * the activity started. * @@ -663,7 +654,7 @@ public final class AutofillManager { * triggered autofill, it is unnecessary to trigger again through * AutofillManager#notifyViewEnteredForActivityStarted. */ - private boolean mIsFillRequested; + private AtomicBoolean mIsFillRequested; @Nullable private List<AutofillId> mFillDialogTriggerIds; @@ -811,8 +802,7 @@ public final class AutofillManager { mContext = Objects.requireNonNull(context, "context cannot be null"); mService = service; mOptions = context.getAutofillOptions(); - mIsFillRequested = false; - mRequireAutofill = false; + mIsFillRequested = new AtomicBoolean(false); mIsFillDialogEnabled = DeviceConfig.getBoolean( DeviceConfig.NAMESPACE_AUTOFILL, @@ -1113,22 +1103,31 @@ public final class AutofillManager { } /** - * The view have the allowed autofill hints, marked to perform a fill request after layout if - * the field does not trigger a fill request. + * The {@link #DEVICE_CONFIG_AUTOFILL_DIALOG_ENABLED} is {@code true} or the view have + * the allowed autofill hints, performs a fill request to know there is any field supported + * fill dialog. * * @hide */ - public void enableFillRequestActivityStarted(View v) { - if (mRequireAutofill) { + public void notifyViewEnteredForFillDialog(View v) { + // Skip if the fill request has been performed for a view. + if (mIsFillRequested.get()) { return; } if (mIsFillDialogEnabled || ArrayUtils.containsAny(v.getAutofillHints(), mFillDialogEnabledHints)) { if (sDebug) { - Log.d(TAG, "Trigger fill request at starting"); + Log.d(TAG, "Trigger fill request at view entered"); } - mRequireAutofill = true; + + // Note: No need for atomic getAndSet as this method is called on the UI thread. + mIsFillRequested.set(true); + + int flags = FLAG_SUPPORTS_FILL_DIALOG; + flags |= FLAG_VIEW_NOT_FOCUSED; + // use root view, so autofill UI does not trigger immediately. + notifyViewEntered(v.getRootView(), flags); } } @@ -1136,25 +1135,6 @@ public final class AutofillManager { return mIsFillDialogEnabled || !ArrayUtils.isEmpty(mFillDialogEnabledHints); } - /** - * Notify autofill to do a fill request while the activity started. - * - * @hide - */ - public void notifyViewEnteredForActivityStarted(@NonNull View view) { - if (!hasAutofillFeature() || !hasFillDialogUiFeature()) { - return; - } - - if (!mRequireAutofill || mIsFillRequested) { - return; - } - - int flags = FLAG_SUPPORTS_FILL_DIALOG; - flags |= FLAG_VIEW_NOT_FOCUSED; - notifyViewEntered(view, flags); - } - private int getImeStateFlag(View v) { final WindowInsets rootWindowInsets = v.getRootWindowInsets(); if (rootWindowInsets != null && rootWindowInsets.isVisible(WindowInsets.Type.ime())) { @@ -1203,7 +1183,7 @@ public final class AutofillManager { } AutofillCallback callback; synchronized (mLock) { - mIsFillRequested = true; + mIsFillRequested.set(true); callback = notifyViewEnteredLocked(view, flags); } @@ -2119,8 +2099,7 @@ public final class AutofillManager { mFillableIds = null; mSaveTriggerId = null; mIdShownFillUi = null; - mIsFillRequested = false; - mRequireAutofill = false; + mIsFillRequested.set(false); mShowAutofillDialogCalled = false; mFillDialogTriggerIds = null; if (resetEnteredIds) { diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 6209b46997e8..dbdc0daff2c1 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -29,6 +29,7 @@ import android.util.Log; import android.view.InputChannel; import android.view.MotionEvent; import android.view.View; +import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodNavButtonFlags; @@ -232,6 +233,10 @@ public interface InputMethod { * long as your implementation of {@link InputMethod} relies on such * IPCs * @param navButtonFlags {@link InputMethodNavButtonFlags} in the initial state of this session. + * @param imeDispatcher The {@link ImeOnBackInvokedDispatcher }} to be set on the + * IME's {@link android.window.WindowOnBackInvokedDispatcher}, so that IME + * {@link android.window.OnBackInvokedCallback}s can be forwarded to + * the client requesting to start input. * @see #startInput(InputConnection, EditorInfo) * @see #restartInput(InputConnection, EditorInfo) * @see EditorInfo @@ -240,7 +245,8 @@ public interface InputMethod { @MainThread default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags) { + @NonNull IBinder startInputToken, @InputMethodNavButtonFlags int navButtonFlags, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (restarting) { restartInput(inputConnection, editorInfo); } else { diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index d9bde5825fde..e2e9a8557793 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -91,6 +91,8 @@ import android.view.View; import android.view.ViewRootImpl; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.autofill.AutofillManager; +import android.window.ImeOnBackInvokedDispatcher; +import android.window.WindowOnBackInvokedDispatcher; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.DirectBootAwareness; @@ -105,6 +107,7 @@ import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; import com.android.internal.inputmethod.UnbindReason; import com.android.internal.os.SomeArgs; +import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; import com.android.internal.view.IInputMethodSession; @@ -279,6 +282,21 @@ public final class InputMethodManager { private static final String SUBTYPE_MODE_VOICE = "voice"; /** + * Provide this to {@link IInputMethodManager#startInputOrWindowGainedFocus( + * int, IInputMethodClient, IBinder, int, int, int, EditorInfo, IInputContext, int)} to receive + * {@link android.window.OnBackInvokedCallback} registrations from IME. + */ + private final ImeOnBackInvokedDispatcher mImeDispatcher = + new ImeOnBackInvokedDispatcher(Handler.getMain()) { + @Override + public WindowOnBackInvokedDispatcher getReceivingDispatcher() { + synchronized (mH) { + return mCurRootView != null ? mCurRootView.getOnBackInvokedDispatcher() : null; + } + } + }; + + /** * Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly * or indirectly relied on {@link #sInstance} via reflection or something like that. * @@ -740,7 +758,8 @@ public final class InputMethodManager { windowFlags, null, null, null, - mCurRootView.mContext.getApplicationInfo().targetSdkVersion); + mCurRootView.mContext.getApplicationInfo().targetSdkVersion, + mImeDispatcher); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -1687,6 +1706,8 @@ public final class InputMethodManager { mServedConnecting = false; clearConnectionLocked(); } + // Clear the back callbacks held by the ime dispatcher to avoid memory leaks. + mImeDispatcher.clear(); } public void displayCompletions(View view, CompletionInfo[] completions) { @@ -2359,7 +2380,8 @@ public final class InputMethodManager { softInputMode, windowFlags, tba, servedInputConnection, servedInputConnection == null ? null : servedInputConnection.asIRemoteAccessibilityInputConnection(), - view.getContext().getApplicationInfo().targetSdkVersion); + view.getContext().getApplicationInfo().targetSdkVersion, + mImeDispatcher); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/translation/UiTranslationController.java b/core/java/android/view/translation/UiTranslationController.java index 8cf032bc03cb..6bf2474beb17 100644 --- a/core/java/android/view/translation/UiTranslationController.java +++ b/core/java/android/view/translation/UiTranslationController.java @@ -158,12 +158,7 @@ public class UiTranslationController implements Dumpable { case STATE_UI_TRANSLATION_FINISHED: destroyTranslators(); runForEachView((view, callback) -> { - callback.onClearTranslation(view); - view.clearViewTranslationResponse(); - if (view.hasTranslationTransientState()) { - view.setHasTransientState(false); - view.setHasTranslationTransientState(false); - } + view.clearTranslationState(); }); notifyTranslationFinished(/* activityDestroyed= */ false); synchronized (mLock) { diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.aidl b/core/java/android/window/ImeOnBackInvokedDispatcher.aidl new file mode 100644 index 000000000000..04e64203dd39 --- /dev/null +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.aidl @@ -0,0 +1,20 @@ +/* //device/java/android/android/os/ParcelFileDescriptor.aidl +** +** Copyright 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 android.window; + +parcelable ImeOnBackInvokedDispatcher; diff --git a/core/java/android/window/ImeOnBackInvokedDispatcher.java b/core/java/android/window/ImeOnBackInvokedDispatcher.java new file mode 100644 index 000000000000..d5763aa25884 --- /dev/null +++ b/core/java/android/window/ImeOnBackInvokedDispatcher.java @@ -0,0 +1,187 @@ +/* + * 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 android.window; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.RemoteException; +import android.os.ResultReceiver; +import android.util.Log; + +import java.util.HashMap; + +/** + * A {@link OnBackInvokedDispatcher} for IME that forwards {@link OnBackInvokedCallback} + * registrations from the IME process to the app process to be registered on the app window. + * <p> + * The app process creates and propagates an instance of {@link ImeOnBackInvokedDispatcher} + * to the IME to be set on the IME window's {@link WindowOnBackInvokedDispatcher}. + * <p> + * @see WindowOnBackInvokedDispatcher#setImeOnBackInvokedDispatcher + * + * @hide + */ +public class ImeOnBackInvokedDispatcher implements OnBackInvokedDispatcher, Parcelable { + + private static final String TAG = "ImeBackDispatcher"; + static final String RESULT_KEY_ID = "id"; + static final String RESULT_KEY_CALLBACK = "callback"; + static final String RESULT_KEY_PRIORITY = "priority"; + static final int RESULT_CODE_REGISTER = 0; + static final int RESULT_CODE_UNREGISTER = 1; + @NonNull + private final ResultReceiver mResultReceiver; + + public ImeOnBackInvokedDispatcher(Handler handler) { + mResultReceiver = new ResultReceiver(handler) { + @Override + public void onReceiveResult(int resultCode, Bundle resultData) { + WindowOnBackInvokedDispatcher dispatcher = getReceivingDispatcher(); + if (dispatcher != null) { + receive(resultCode, resultData, dispatcher); + } + } + }; + } + + /** + * Override this method to return the {@link WindowOnBackInvokedDispatcher} of the window + * that should receive the forwarded callback. + */ + @Nullable + protected WindowOnBackInvokedDispatcher getReceivingDispatcher() { + return null; + } + + ImeOnBackInvokedDispatcher(Parcel in) { + mResultReceiver = in.readTypedObject(ResultReceiver.CREATOR); + } + + @Override + public void registerOnBackInvokedCallback( + @OnBackInvokedDispatcher.Priority int priority, + @NonNull OnBackInvokedCallback callback) { + final Bundle bundle = new Bundle(); + final IOnBackInvokedCallback iCallback = + new WindowOnBackInvokedDispatcher.OnBackInvokedCallbackWrapper(callback); + bundle.putBinder(RESULT_KEY_CALLBACK, iCallback.asBinder()); + bundle.putInt(RESULT_KEY_PRIORITY, priority); + bundle.putInt(RESULT_KEY_ID, callback.hashCode()); + mResultReceiver.send(RESULT_CODE_REGISTER, bundle); + } + + @Override + public void unregisterOnBackInvokedCallback( + @NonNull OnBackInvokedCallback callback) { + Bundle bundle = new Bundle(); + bundle.putInt(RESULT_KEY_ID, callback.hashCode()); + mResultReceiver.send(RESULT_CODE_UNREGISTER, bundle); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedObject(mResultReceiver, flags); + } + + @NonNull + public static final Parcelable.Creator<ImeOnBackInvokedDispatcher> CREATOR = + new Parcelable.Creator<ImeOnBackInvokedDispatcher>() { + public ImeOnBackInvokedDispatcher createFromParcel(Parcel in) { + return new ImeOnBackInvokedDispatcher(in); + } + public ImeOnBackInvokedDispatcher[] newArray(int size) { + return new ImeOnBackInvokedDispatcher[size]; + } + }; + + private final HashMap<Integer, OnBackInvokedCallback> mImeCallbackMap = new HashMap<>(); + + private void receive( + int resultCode, Bundle resultData, + @NonNull OnBackInvokedDispatcher receivingDispatcher) { + final int callbackId = resultData.getInt(RESULT_KEY_ID); + if (resultCode == RESULT_CODE_REGISTER) { + int priority = resultData.getInt(RESULT_KEY_PRIORITY); + final IOnBackInvokedCallback callback = IOnBackInvokedCallback.Stub.asInterface( + resultData.getBinder(RESULT_KEY_CALLBACK)); + registerReceivedCallback( + callback, priority, callbackId, receivingDispatcher); + } else if (resultCode == RESULT_CODE_UNREGISTER) { + unregisterReceivedCallback(callbackId, receivingDispatcher); + } + } + + private void registerReceivedCallback( + @NonNull IOnBackInvokedCallback iCallback, + @OnBackInvokedDispatcher.Priority int priority, + int callbackId, + @NonNull OnBackInvokedDispatcher receivingDispatcher) { + final ImeOnBackInvokedCallback imeCallback = + new ImeOnBackInvokedCallback(iCallback); + mImeCallbackMap.put(callbackId, imeCallback); + receivingDispatcher.registerOnBackInvokedCallback(priority, imeCallback); + } + + private void unregisterReceivedCallback( + int callbackId, OnBackInvokedDispatcher receivingDispatcher) { + final OnBackInvokedCallback callback = mImeCallbackMap.get(callbackId); + if (callback == null) { + Log.e(TAG, "Ime callback not found. Ignoring unregisterReceivedCallback. " + + "callbackId: " + callbackId); + return; + } + receivingDispatcher.unregisterOnBackInvokedCallback(callback); + } + + /** Clears all registered callbacks on the instance. */ + public void clear() { + mImeCallbackMap.clear(); + } + + static class ImeOnBackInvokedCallback implements OnBackInvokedCallback { + @NonNull + private final IOnBackInvokedCallback mIOnBackInvokedCallback; + + ImeOnBackInvokedCallback(@NonNull IOnBackInvokedCallback iCallback) { + mIOnBackInvokedCallback = iCallback; + } + + @Override + public void onBackInvoked() { + try { + if (mIOnBackInvokedCallback != null) { + mIOnBackInvokedCallback.onBackInvoked(); + } + } catch (RemoteException e) { + Log.e(TAG, "Exception when invoking forwarded callback. e: ", e); + } + } + + IOnBackInvokedCallback getIOnBackInvokedCallback() { + return mIOnBackInvokedCallback; + } + } +} diff --git a/core/java/android/window/OnBackInvokedDispatcher.java b/core/java/android/window/OnBackInvokedDispatcher.java index 6bc2b5043e79..3539049af219 100644 --- a/core/java/android/window/OnBackInvokedDispatcher.java +++ b/core/java/android/window/OnBackInvokedDispatcher.java @@ -96,4 +96,19 @@ public interface OnBackInvokedDispatcher { * @hide */ default void registerSystemOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { } + + + /** + * Sets an {@link ImeOnBackInvokedDispatcher} to forward {@link OnBackInvokedCallback}s + * from IME to the app process to be registered on the app window. + * + * Only call this on the IME window. Create the {@link ImeOnBackInvokedDispatcher} from + * the application process and override + * {@link ImeOnBackInvokedDispatcher#getReceivingDispatcher()} to point to the app + * window's {@link WindowOnBackInvokedDispatcher}. + * + * @hide + */ + default void setImeOnBackInvokedDispatcher( + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { } } diff --git a/core/java/android/window/ProxyOnBackInvokedDispatcher.java b/core/java/android/window/ProxyOnBackInvokedDispatcher.java index 44093971a23e..bedf5038f669 100644 --- a/core/java/android/window/ProxyOnBackInvokedDispatcher.java +++ b/core/java/android/window/ProxyOnBackInvokedDispatcher.java @@ -49,6 +49,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private final List<Pair<OnBackInvokedCallback, Integer>> mCallbacks = new ArrayList<>(); private final Object mLock = new Object(); private OnBackInvokedDispatcher mActualDispatcher = null; + private ImeOnBackInvokedDispatcher mImeDispatcher; @Override public void registerOnBackInvokedCallback( @@ -108,6 +109,9 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { Log.v(TAG, String.format("Proxy transferring %d callbacks to %s", mCallbacks.size(), mActualDispatcher)); } + if (mImeDispatcher != null) { + mActualDispatcher.setImeOnBackInvokedDispatcher(mImeDispatcher); + } for (Pair<OnBackInvokedCallback, Integer> callbackPair : mCallbacks) { int priority = callbackPair.second; if (priority >= 0) { @@ -117,6 +121,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } } mCallbacks.clear(); + mImeDispatcher = null; } private void clearCallbacksOnDispatcher() { @@ -142,6 +147,7 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } synchronized (mLock) { mCallbacks.clear(); + mImeDispatcher = null; } } @@ -169,4 +175,14 @@ public class ProxyOnBackInvokedDispatcher implements OnBackInvokedDispatcher { transferCallbacksToDispatcher(); } } + + @Override + public void setImeOnBackInvokedDispatcher( + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + if (mActualDispatcher != null) { + mActualDispatcher.setImeOnBackInvokedDispatcher(imeDispatcher); + } else { + mImeDispatcher = imeDispatcher; + } + } } diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 781859cecb2c..edfdbcc1f4f8 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -55,6 +55,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { .getInt("persist.wm.debug.predictive_back", 1) != 0; private static final boolean ALWAYS_ENFORCE_PREDICTIVE_BACK = SystemProperties .getInt("persist.wm.debug.predictive_back_always_enforce", 0) != 0; + @Nullable + private ImeOnBackInvokedDispatcher mImeDispatcher; /** Convenience hashmap to quickly decide if a callback has been added. */ private final HashMap<OnBackInvokedCallback, Integer> mAllCallbacks = new HashMap<>(); @@ -94,6 +96,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { private void registerOnBackInvokedCallbackUnchecked( @NonNull OnBackInvokedCallback callback, @Priority int priority) { + if (mImeDispatcher != null) { + mImeDispatcher.registerOnBackInvokedCallback(priority, callback); + return; + } if (!mOnBackInvokedCallbacks.containsKey(priority)) { mOnBackInvokedCallbacks.put(priority, new ArrayList<>()); } @@ -120,6 +126,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { @Override public void unregisterOnBackInvokedCallback(@NonNull OnBackInvokedCallback callback) { + if (mImeDispatcher != null) { + mImeDispatcher.unregisterOnBackInvokedCallback(callback); + return; + } if (!mAllCallbacks.containsKey(callback)) { if (DEBUG) { Log.i(TAG, "Callback not found. returning..."); @@ -153,6 +163,9 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } mAllCallbacks.clear(); mOnBackInvokedCallbacks.clear(); + if (mImeDispatcher != null) { + mImeDispatcher = null; + } } private void setTopOnBackInvokedCallback(@Nullable OnBackInvokedCallback callback) { @@ -160,14 +173,18 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return; } try { - if (callback == null) { - mWindowSession.setOnBackInvokedCallbackInfo(mWindow, null); - } else { + OnBackInvokedCallbackInfo callbackInfo = null; + if (callback != null) { int priority = mAllCallbacks.get(callback); - mWindowSession.setOnBackInvokedCallbackInfo( - mWindow, new OnBackInvokedCallbackInfo( - new OnBackInvokedCallbackWrapper(callback), priority)); + final IOnBackInvokedCallback iCallback = + callback instanceof ImeOnBackInvokedDispatcher + .ImeOnBackInvokedCallback + ? ((ImeOnBackInvokedDispatcher.ImeOnBackInvokedCallback) + callback).getIOnBackInvokedCallback() + : new OnBackInvokedCallbackWrapper(callback); + callbackInfo = new OnBackInvokedCallbackInfo(iCallback, priority); } + mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo); if (DEBUG && callback == null) { Log.d(TAG, TextUtils.formatSimple("setTopOnBackInvokedCallback(null) Callers:%s", Debug.getCallers(5, " "))); @@ -190,7 +207,7 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return null; } - private static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { + static class OnBackInvokedCallbackWrapper extends IOnBackInvokedCallback.Stub { private final WeakReference<OnBackInvokedCallback> mCallback; OnBackInvokedCallbackWrapper(@NonNull OnBackInvokedCallback callback) { @@ -270,4 +287,10 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { return featureFlagEnabled && (appRequestsPredictiveBack || ALWAYS_ENFORCE_PREDICTIVE_BACK); } + + @Override + public void setImeOnBackInvokedDispatcher( + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { + mImeDispatcher = imeDispatcher; + } } diff --git a/core/java/android/window/WindowTokenClient.java b/core/java/android/window/WindowTokenClient.java index 7db4243d3a83..0976f45c02b0 100644 --- a/core/java/android/window/WindowTokenClient.java +++ b/core/java/android/window/WindowTokenClient.java @@ -21,8 +21,10 @@ import static android.window.ConfigurationHelper.shouldUpdateResources; import android.annotation.AnyThread; import android.annotation.BinderThread; +import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityThread; import android.app.IWindowToken; import android.app.ResourcesManager; import android.content.Context; @@ -33,7 +35,6 @@ import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.view.IWindowManager; @@ -42,6 +43,7 @@ import android.view.WindowManagerGlobal; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.function.pooled.PooledLambda; import java.lang.ref.WeakReference; @@ -76,7 +78,7 @@ public class WindowTokenClient extends IWindowToken.Stub { private boolean mAttachToWindowContainer; - private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final Handler mHandler = ActivityThread.currentActivityThread().getHandler(); /** * Attaches {@code context} to this {@link WindowTokenClient}. Each {@link WindowTokenClient} @@ -188,8 +190,8 @@ public class WindowTokenClient extends IWindowToken.Stub { @BinderThread @Override public void onConfigurationChanged(Configuration newConfig, int newDisplayId) { - mHandler.post(() -> onConfigurationChanged(newConfig, newDisplayId, - true /* shouldReportConfigChange */)); + mHandler.post(PooledLambda.obtainRunnable(this::onConfigurationChanged, newConfig, + newDisplayId, true /* shouldReportConfigChange */).recycleOnUse()); } // TODO(b/192048581): rewrite this method based on WindowContext and WindowProviderService @@ -279,12 +281,16 @@ public class WindowTokenClient extends IWindowToken.Stub { @BinderThread @Override public void onWindowTokenRemoved() { - mHandler.post(() -> { - final Context context = mContextRef.get(); - if (context != null) { - context.destroy(); - mContextRef.clear(); - } - }); + mHandler.post(PooledLambda.obtainRunnable( + WindowTokenClient::onWindowTokenRemovedInner, this).recycleOnUse()); + } + + @MainThread + private void onWindowTokenRemovedInner() { + final Context context = mContextRef.get(); + if (context != null) { + context.destroy(); + mContextRef.clear(); + } } } diff --git a/core/java/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index f0a685ec4d2e..781b6d5459ca 100644 --- a/core/java/com/android/internal/app/ChooserActivity.java +++ b/core/java/com/android/internal/app/ChooserActivity.java @@ -245,7 +245,7 @@ public class ChooserActivity extends ResolverActivity implements SystemUiDeviceConfigFlags.IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP, DEFAULT_IS_NEARBY_SHARE_FIRST_TARGET_IN_RANKED_APP); - private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 250; + private static final int DEFAULT_LIST_VIEW_UPDATE_DELAY_MS = 125; @VisibleForTesting int mListViewUpdateDelayMs = DeviceConfig.getInt(DeviceConfig.NAMESPACE_SYSTEMUI, @@ -971,7 +971,8 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logShareTargetSelected( SELECTION_TYPE_COPY, "", - -1); + -1, + false); setResult(RESULT_OK); finish(); @@ -1155,7 +1156,8 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logShareTargetSelected( SELECTION_TYPE_NEARBY, "", - -1); + -1, + false); // Action bar is user-independent, always start as primary safelyStartActivityAsUser(ti, getPersonalProfileUserHandle()); finish(); @@ -1177,7 +1179,8 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logShareTargetSelected( SELECTION_TYPE_EDIT, "", - -1); + -1, + false); // Action bar is user-independent, always start as primary safelyStartActivityAsUser(ti, getPersonalProfileUserHandle()); finish(); @@ -1754,7 +1757,8 @@ public class ChooserActivity extends ResolverActivity implements target.getComponentName().getPackageName() + target.getTitle().toString(), mMaxHashSaltDays); - directTargetAlsoRanked = getRankedPosition((SelectableTargetInfo) targetInfo); + SelectableTargetInfo selectableTargetInfo = (SelectableTargetInfo) targetInfo; + directTargetAlsoRanked = getRankedPosition(selectableTargetInfo); if (mCallerChooserTargets != null) { numCallerProvided = mCallerChooserTargets.length; @@ -1762,7 +1766,8 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logShareTargetSelected( SELECTION_TYPE_SERVICE, targetInfo.getResolveInfo().activityInfo.processName, - value + value, + selectableTargetInfo.isPinned() ); break; case ChooserListAdapter.TARGET_CALLER: @@ -1773,7 +1778,8 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logShareTargetSelected( SELECTION_TYPE_APP, targetInfo.getResolveInfo().activityInfo.processName, - value + value, + targetInfo.isPinned() ); break; case ChooserListAdapter.TARGET_STANDARD_AZ: @@ -1784,7 +1790,8 @@ public class ChooserActivity extends ResolverActivity implements getChooserActivityLogger().logShareTargetSelected( SELECTION_TYPE_STANDARD, targetInfo.getResolveInfo().activityInfo.processName, - value + value, + false ); break; } diff --git a/core/java/com/android/internal/app/ChooserActivityLogger.java b/core/java/com/android/internal/app/ChooserActivityLogger.java index 321730786471..bb7d50af4200 100644 --- a/core/java/com/android/internal/app/ChooserActivityLogger.java +++ b/core/java/com/android/internal/app/ChooserActivityLogger.java @@ -34,7 +34,8 @@ public interface ChooserActivityLogger { int appProvidedApp, boolean isWorkprofile, int previewType, String intent); /** Logs a UiEventReported event for the system sharesheet when the user selects a target. */ - void logShareTargetSelected(int targetType, String packageName, int positionPicked); + void logShareTargetSelected(int targetType, String packageName, int positionPicked, + boolean isPinned); /** Logs a UiEventReported event for the system sharesheet being triggered by the user. */ default void logSharesheetTriggered() { diff --git a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java index 48bdba3f5dae..e3cc4f12fcc6 100644 --- a/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java +++ b/core/java/com/android/internal/app/ChooserActivityLoggerImpl.java @@ -51,12 +51,14 @@ public class ChooserActivityLoggerImpl implements ChooserActivityLogger { } @Override - public void logShareTargetSelected(int targetType, String packageName, int positionPicked) { + public void logShareTargetSelected(int targetType, String packageName, int positionPicked, + boolean isPinned) { FrameworkStatsLog.write(FrameworkStatsLog.RANKING_SELECTED, /* event_id = 1 */ SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), /* package_name = 2 */ packageName, /* instance_id = 3 */ getInstanceId().getId(), - /* position_picked = 4 */ positionPicked); + /* position_picked = 4 */ positionPicked, + /* is_pinned = 5 */ isPinned); } @Override diff --git a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java index c5b21ac4da90..e7f80a7f6071 100644 --- a/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java +++ b/core/java/com/android/internal/app/ResolverRankerServiceResolverComparator.java @@ -589,7 +589,7 @@ class ResolverRankerServiceResolverComparator extends AbstractResolverComparator MetricsLogger metricsLogger = new MetricsLogger(); LogMaker log = new LogMaker(MetricsEvent.ACTION_TARGET_SELECTED); log.setComponentName(mRankerServiceName); - log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed); + log.addTaggedData(MetricsEvent.FIELD_IS_CATEGORY_USED, mAnnotationsUsed ? 1 : 0); log.addTaggedData(MetricsEvent.FIELD_RANKED_POSITION, selectedPos); metricsLogger.write(log); } diff --git a/core/java/com/android/internal/app/UnlaunchableAppActivity.java b/core/java/com/android/internal/app/UnlaunchableAppActivity.java index 957a6365739d..e56d92b48528 100644 --- a/core/java/com/android/internal/app/UnlaunchableAppActivity.java +++ b/core/java/com/android/internal/app/UnlaunchableAppActivity.java @@ -91,7 +91,12 @@ public class UnlaunchableAppActivity extends Activity } else { builder.setPositiveButton(R.string.ok, null); } - builder.show(); + final AlertDialog dialog = builder.create(); + dialog.create(); + // Prevents screen overlay attack. + getWindow().setHideOverlayWindows(true); + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true); + dialog.show(); } private String getDialogTitle() { diff --git a/core/java/com/android/internal/logging/UiEventLoggerImpl.java b/core/java/com/android/internal/logging/UiEventLoggerImpl.java index 983e0fe6144e..ffbf646a345c 100644 --- a/core/java/com/android/internal/logging/UiEventLoggerImpl.java +++ b/core/java/com/android/internal/logging/UiEventLoggerImpl.java @@ -69,7 +69,8 @@ public class UiEventLoggerImpl implements UiEventLogger { /* event_id = 1 */ eventID, /* package_name = 2 */ packageName, /* instance_id = 3 */ 0, - /* position_picked = 4 */ position); + /* position_picked = 4 */ position, + /* is_pinned = 5 */ false); } } @@ -82,7 +83,8 @@ public class UiEventLoggerImpl implements UiEventLogger { /* event_id = 1 */ eventID, /* package_name = 2 */ packageName, /* instance_id = 3 */ instance.getId(), - /* position_picked = 4 */ position); + /* position_picked = 4 */ position, + /* is_pinned = 5 */ false); } else if ((eventID > 0)) { logWithPosition(event, uid, packageName, position); } diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl index 46b463074383..ef8f2db5ff57 100644 --- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl +++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl @@ -88,7 +88,7 @@ interface IStatusBarService in int notificationLocation, boolean modifiedBeforeSending); void onNotificationSettingsViewed(String key); void onNotificationBubbleChanged(String key, boolean isBubble, int flags); - void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, boolean isBubbleSuppressed); + void onBubbleMetadataFlagChanged(String key, int flags); void hideCurrentInputMethodForBubbles(); void grantInlineReplyUriPermission(String key, in Uri uri, in UserHandle user, String packageName); oneway void clearInlineReplyUriPermissions(String key); diff --git a/core/java/com/android/internal/util/ImageUtils.java b/core/java/com/android/internal/util/ImageUtils.java index 397b2c052514..62dea9dc82e0 100644 --- a/core/java/com/android/internal/util/ImageUtils.java +++ b/core/java/com/android/internal/util/ImageUtils.java @@ -137,6 +137,18 @@ public class ImageUtils { */ public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, int maxHeight) { + return buildScaledBitmap(drawable, maxWidth, maxHeight, false); + } + + /** + * Convert a drawable to a bitmap, scaled to fit within maxWidth and maxHeight. + * + * @param allowUpscaling if true, the drawable will not only be scaled down, but also scaled up + * to fit within the maximum size given. This is useful for converting + * vectorized icons which usually have a very small intrinsic size. + */ + public static Bitmap buildScaledBitmap(Drawable drawable, int maxWidth, + int maxHeight, boolean allowUpscaling) { if (drawable == null) { return null; } @@ -155,7 +167,9 @@ public class ImageUtils { // a large notification icon if necessary float ratio = Math.min((float) maxWidth / (float) originalWidth, (float) maxHeight / (float) originalHeight); - ratio = Math.min(1.0f, ratio); + if (!allowUpscaling) { + ratio = Math.min(1.0f, ratio); + } int scaledWidth = (int) (ratio * originalWidth); int scaledHeight = (int) (ratio * originalHeight); Bitmap result = Bitmap.createBitmap(scaledWidth, scaledHeight, Config.ARGB_8888); diff --git a/core/java/com/android/internal/view/IInputMethod.aidl b/core/java/com/android/internal/view/IInputMethod.aidl index 40d89db6165c..4e2526a281b3 100644 --- a/core/java/com/android/internal/view/IInputMethod.aidl +++ b/core/java/com/android/internal/view/IInputMethod.aidl @@ -23,6 +23,7 @@ import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethodSubtype; +import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.view.IInlineSuggestionsRequestCallback; import com.android.internal.view.IInputContext; @@ -47,7 +48,8 @@ oneway interface IInputMethod { void unbindInput(); void startInput(in IBinder startInputToken, in IInputContext inputContext, - in EditorInfo attribute, boolean restarting, int navigationBarFlags); + in EditorInfo attribute, boolean restarting, int navigationBarFlags, + in ImeOnBackInvokedDispatcher imeDispatcher); void onNavButtonFlagsChanged(int navButtonFlags); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index d7bb2cb10b8c..315776045afe 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -20,6 +20,7 @@ import android.os.ResultReceiver; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.EditorInfo; +import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; @@ -57,7 +58,7 @@ interface IInputMethodManager { /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode, int windowFlags, in EditorInfo attribute, in IInputContext inputContext, in IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, - int unverifiedTargetSdkVersion); + int unverifiedTargetSdkVersion, in ImeOnBackInvokedDispatcher imeDispatcher); void showInputMethodPickerFromClient(in IInputMethodClient client, int auxiliarySubtypeMode); diff --git a/core/jni/android_graphics_BLASTBufferQueue.cpp b/core/jni/android_graphics_BLASTBufferQueue.cpp index c0f7b41d116d..4af28ea24361 100644 --- a/core/jni/android_graphics_BLASTBufferQueue.cpp +++ b/core/jni/android_graphics_BLASTBufferQueue.cpp @@ -47,6 +47,43 @@ static JNIEnv* getenv(JavaVM* vm) { return env; } + struct { + jmethodID onTransactionHang; +} gTransactionHangCallback; + +class TransactionHangCallbackWrapper : public LightRefBase<TransactionHangCallbackWrapper> { +public: + explicit TransactionHangCallbackWrapper(JNIEnv* env, jobject jobject) { + env->GetJavaVM(&mVm); + mTransactionHangObject = env->NewGlobalRef(jobject); + LOG_ALWAYS_FATAL_IF(!mTransactionHangObject, "Failed to make global ref"); + } + + ~TransactionHangCallbackWrapper() { + if (mTransactionHangObject) { + getenv()->DeleteGlobalRef(mTransactionHangObject); + mTransactionHangObject = nullptr; + } + } + + void onTransactionHang(bool isGpuHang) { + if (mTransactionHangObject) { + getenv()->CallVoidMethod(mTransactionHangObject, + gTransactionHangCallback.onTransactionHang, isGpuHang); + } + } + +private: + JavaVM* mVm; + jobject mTransactionHangObject; + + JNIEnv* getenv() { + JNIEnv* env; + mVm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6); + return env; + } +}; + static jlong nativeCreate(JNIEnv* env, jclass clazz, jstring jName, jboolean updateDestinationFrame) { ScopedUtfChars name(env, jName); @@ -141,6 +178,20 @@ static bool nativeIsSameSurfaceControl(JNIEnv* env, jclass clazz, jlong ptr, jlo sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); return queue->isSameSurfaceControl(reinterpret_cast<SurfaceControl*>(surfaceControl)); } + +static void nativeSetTransactionHangCallback(JNIEnv* env, jclass clazz, jlong ptr, + jobject transactionHangCallback) { + sp<BLASTBufferQueue> queue = reinterpret_cast<BLASTBufferQueue*>(ptr); + if (transactionHangCallback == nullptr) { + queue->setTransactionHangCallback(nullptr); + } else { + sp<TransactionHangCallbackWrapper> wrapper = + new TransactionHangCallbackWrapper{env, transactionHangCallback}; + queue->setTransactionHangCallback([wrapper](bool isGpuHang) { + wrapper->onTransactionHang(isGpuHang); + }); + } +} static jobject nativeGatherPendingTransactions(JNIEnv* env, jclass clazz, jlong ptr, jlong frameNum) { @@ -163,7 +214,10 @@ static const JNINativeMethod gMethods[] = { {"nativeGetLastAcquiredFrameNum", "(J)J", (void*)nativeGetLastAcquiredFrameNum}, {"nativeApplyPendingTransactions", "(JJ)V", (void*)nativeApplyPendingTransactions}, {"nativeIsSameSurfaceControl", "(JJ)Z", (void*)nativeIsSameSurfaceControl}, - {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions} + {"nativeGatherPendingTransactions", "(JJ)Landroid/view/SurfaceControl$Transaction;", (void*)nativeGatherPendingTransactions}, + {"nativeSetTransactionHangCallback", + "(JLandroid/graphics/BLASTBufferQueue$TransactionHangCallback;)V", + (void*)nativeSetTransactionHangCallback}, // clang-format on }; @@ -180,6 +234,11 @@ int register_android_graphics_BLASTBufferQueue(JNIEnv* env) { jclass consumer = FindClassOrDie(env, "java/util/function/Consumer"); gTransactionConsumer.accept = GetMethodIDOrDie(env, consumer, "accept", "(Ljava/lang/Object;)V"); + jclass transactionHangClass = + FindClassOrDie(env, "android/graphics/BLASTBufferQueue$TransactionHangCallback"); + gTransactionHangCallback.onTransactionHang = + GetMethodIDOrDie(env, transactionHangClass, "onTransactionHang", "(Z)V"); + return 0; } diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d32acaf1e620..0f328b034f38 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7056,6 +7056,10 @@ android:permission="android.permission.BIND_JOB_SERVICE"> </service> + <service android:name="com.android.server.notification.ReviewNotificationPermissionsJobService" + android:permission="android.permission.BIND_JOB_SERVICE"> + </service> + <service android:name="com.android.server.pm.PackageManagerShellCommandDataLoader" android:exported="false"> <intent-filter> diff --git a/core/res/OWNERS b/core/res/OWNERS index 95d2712a2b41..c54638a368a2 100644 --- a/core/res/OWNERS +++ b/core/res/OWNERS @@ -45,3 +45,4 @@ per-file res/xml/power_profile_test.xml = file:/BATTERY_STATS_OWNERS # Telephony per-file res/values/config_telephony.xml = file:/platform/frameworks/opt/telephony:/OWNERS +per-file res/xml/sms_short_codes.xml = file:/platform/frameworks/opt/telephony:/OWNERS diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index edaf8cf279e3..689ff66a3b4d 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -5451,6 +5451,8 @@ <bool name="config_supportsHardwareCamToggle">false</bool> <!-- Whether a camera intent is launched when the lens cover is toggled --> <bool name="config_launchCameraOnCameraLensCoverToggle">true</bool> + <!-- Whether changing sensor privacy SW setting requires device to be unlocked --> + <bool name="config_sensorPrivacyRequiresAuthentication">true</bool> <!-- List containing the allowed install sources for accessibility service. --> <string-array name="config_accessibility_allowed_install_source" translatable="false"/> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index b88212324d24..443f9a628a7e 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -4661,6 +4661,7 @@ <java-symbol type="bool" name="config_supportsHardwareMicToggle" /> <java-symbol type="bool" name="config_supportsHardwareCamToggle" /> <java-symbol type="bool" name="config_launchCameraOnCameraLensCoverToggle" /> + <java-symbol type="bool" name="config_sensorPrivacyRequiresAuthentication" /> <java-symbol type="dimen" name="starting_surface_icon_size" /> <java-symbol type="dimen" name="starting_surface_default_icon_size" /> diff --git a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java index 2ecc26179964..0dca63847269 100644 --- a/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java +++ b/core/tests/coretests/src/com/android/internal/app/ChooserActivityLoggerFake.java @@ -44,6 +44,7 @@ public class ChooserActivityLoggerFake implements ChooserActivityLogger { // share completed fields public int targetType; public int positionPicked; + public boolean isPinned; CallRecord(int atomId, UiEventLogger.UiEventEnum eventId, String packageName, InstanceId instanceId) { @@ -68,12 +69,13 @@ public class ChooserActivityLoggerFake implements ChooserActivityLogger { } CallRecord(int atomId, String packageName, InstanceId instanceId, int targetType, - int positionPicked) { + int positionPicked, boolean isPinned) { this.atomId = atomId; this.packageName = packageName; this.instanceId = instanceId; this.targetType = targetType; this.positionPicked = positionPicked; + this.isPinned = isPinned; } } @@ -112,9 +114,11 @@ public class ChooserActivityLoggerFake implements ChooserActivityLogger { } @Override - public void logShareTargetSelected(int targetType, String packageName, int positionPicked) { + public void logShareTargetSelected(int targetType, String packageName, int positionPicked, + boolean isPinned) { mCalls.add(new CallRecord(FrameworkStatsLog.RANKING_SELECTED, packageName, getInstanceId(), - SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked)); + SharesheetTargetSelectedEvent.fromTargetType(targetType).getId(), positionPicked, + isPinned)); } @Override diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 891c82d2c166..60da2e8cba27 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -2575,12 +2575,6 @@ "group": "WM_DEBUG_ANIM", "at": "com\/android\/server\/wm\/WindowContainer.java" }, - "397105698": { - "message": "grantEmbeddedWindowFocus remove request for win=%s dropped since no candidate was found", - "level": "VERBOSE", - "group": "WM_DEBUG_FOCUS", - "at": "com\/android\/server\/wm\/WindowManagerService.java" - }, "397382873": { "message": "Moving to PAUSED: %s %s", "level": "VERBOSE", @@ -3109,6 +3103,12 @@ "group": "WM_DEBUG_LOCKTASK", "at": "com\/android\/server\/wm\/LockTaskController.java" }, + "958338552": { + "message": "grantEmbeddedWindowFocus win=%s dropped focus so setting focus to null since no candidate was found", + "level": "VERBOSE", + "group": "WM_DEBUG_FOCUS", + "at": "com\/android\/server\/wm\/WindowManagerService.java" + }, "959486822": { "message": "setSyncGroup #%d on %s", "level": "VERBOSE", diff --git a/data/keyboards/Vendor_0957_Product_0001.kl b/data/keyboards/Vendor_0957_Product_0001.kl index 13b4096ca82a..54f8808e2285 100644 --- a/data/keyboards/Vendor_0957_Product_0001.kl +++ b/data/keyboards/Vendor_0957_Product_0001.kl @@ -44,33 +44,37 @@ key 10 9 key 11 0 # custom keys -key usage 0x000c019C PROFILE_SWITCH -key usage 0x000c01A2 ALL_APPS key usage 0x000c01BB TV_INPUT + +key usage 0x000c0185 TV_TELETEXT +key usage 0x000c0061 CAPTIONS + +key usage 0x000c01BD INFO +key usage 0x000c0037 PERIOD + +key usage 0x000c0069 PROG_RED +key usage 0x000c006A PROG_GREEN +key usage 0x000c006C PROG_YELLOW +key usage 0x000c006B PROG_BLUE +key usage 0x000c00B4 MEDIA_SKIP_BACKWARD +key usage 0x000c00CD MEDIA_PLAY_PAUSE +key usage 0x000c00B2 MEDIA_RECORD +key usage 0x000c00B3 MEDIA_SKIP_FORWARD + key usage 0x000c022A BOOKMARK +key usage 0x000c01A2 ALL_APPS +key usage 0x000c019C PROFILE_SWITCH + key usage 0x000c0096 SETTINGS key usage 0x000c009F NOTIFICATION + key usage 0x000c008D GUIDE key usage 0x000c0089 TV + key usage 0x000c009C CHANNEL_UP key usage 0x000c009D CHANNEL_DOWN -key usage 0x000c00CD MEDIA_PLAY_PAUSE -key usage 0x000c00B2 MEDIA_RECORD -key usage 0x000c00B4 MEDIA_SKIP_BACKWARD -key usage 0x000c00B3 MEDIA_SKIP_FORWARD -key usage 0x000c0226 MEDIA_STOP key usage 0x000c0077 BUTTON_3 WAKE #YouTube key usage 0x000c0078 BUTTON_4 WAKE #Netflix -key usage 0x000c0079 BUTTON_6 WAKE #Disney+ -key usage 0x000c007A BUTTON_7 WAKE #HBOmax - -key usage 0x00070037 PERIOD -key usage 0x000c01BD INFO -key usage 0x000c0061 CAPTIONS -key usage 0x000c0185 TV_TELETEXT - -key usage 0x000c0069 PROG_RED -key usage 0x000c006A PROG_GREEN -key usage 0x000c006B PROG_BLUE -key usage 0x000c006C PROG_YELLOW
\ No newline at end of file +key usage 0x000c0079 BUTTON_6 WAKE +key usage 0x000c007A BUTTON_7 WAKE
\ No newline at end of file diff --git a/graphics/java/android/graphics/BLASTBufferQueue.java b/graphics/java/android/graphics/BLASTBufferQueue.java index 4b723d1569c9..1c41d06a3da2 100644 --- a/graphics/java/android/graphics/BLASTBufferQueue.java +++ b/graphics/java/android/graphics/BLASTBufferQueue.java @@ -43,6 +43,12 @@ public final class BLASTBufferQueue { private static native boolean nativeIsSameSurfaceControl(long ptr, long surfaceControlPtr); private static native SurfaceControl.Transaction nativeGatherPendingTransactions(long ptr, long frameNumber); + private static native void nativeSetTransactionHangCallback(long ptr, + TransactionHangCallback callback); + + public interface TransactionHangCallback { + void onTransactionHang(boolean isGpuHang); + } /** Create a new connection with the surface flinger. */ public BLASTBufferQueue(String name, SurfaceControl sc, int width, int height, @@ -184,4 +190,8 @@ public final class BLASTBufferQueue { public SurfaceControl.Transaction gatherPendingTransactions(long frameNumber) { return nativeGatherPendingTransactions(mNativeObject, frameNumber); } + + public void setTransactionHangCallback(TransactionHangCallback hangCallback) { + nativeSetTransactionHangCallback(mNativeObject, hangCallback); + } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java index 82f8a131ae2a..faada1aa03ef 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java @@ -777,22 +777,14 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen return null; } - private void updateCallbackIfNecessary() { - updateCallbackIfNecessary(true /* deferCallbackUntilAllActivitiesCreated */); - } - /** * Notifies listeners about changes to split states if necessary. - * - * @param deferCallbackUntilAllActivitiesCreated boolean to indicate whether the split info - * callback should be deferred until all the - * organized activities have been created. */ - private void updateCallbackIfNecessary(boolean deferCallbackUntilAllActivitiesCreated) { + private void updateCallbackIfNecessary() { if (mEmbeddingCallback == null) { return; } - if (deferCallbackUntilAllActivitiesCreated && !allActivitiesCreated()) { + if (!allActivitiesCreated()) { return; } List<SplitInfo> currentSplitStates = getActiveSplitStates(); @@ -848,9 +840,7 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen for (int i = mTaskContainers.size() - 1; i >= 0; i--) { final List<TaskFragmentContainer> containers = mTaskContainers.valueAt(i).mContainers; for (TaskFragmentContainer container : containers) { - if (container.getInfo() == null - || container.getInfo().getActivities().size() - != container.collectActivities().size()) { + if (!container.taskInfoActivityCountMatchesCreated()) { return false; } } @@ -1035,11 +1025,8 @@ public class SplitController implements JetpackTaskFragmentOrganizer.TaskFragmen && container.getTaskFragmentToken().equals(initialTaskFragmentToken)) { // The onTaskFragmentInfoChanged callback containing this activity has not // reached the client yet, so add the activity to the pending appeared - // activities and send a split info callback to the client before - // {@link Activity#onCreate} is called. + // activities. container.addPendingAppearedActivity(activity); - updateCallbackIfNecessary( - false /* deferCallbackUntilAllActivitiesCreated */); return; } } diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java index 35981d3af948..26bbcbb937f0 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java @@ -145,6 +145,18 @@ class TaskFragmentContainer { return allActivities; } + /** + * Checks if the count of activities from the same process in task fragment info corresponds to + * the ones created and available on the client side. + */ + boolean taskInfoActivityCountMatchesCreated() { + if (mInfo == null) { + return false; + } + return mPendingAppearedActivities.isEmpty() + && mInfo.getActivities().size() == collectActivities().size(); + } + ActivityStack toActivityStack() { return new ActivityStack(collectActivities(), isEmpty()); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java index c3fbe5543630..8fa9f564fb22 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java @@ -246,9 +246,13 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont * {@link BackAnimationController} */ public void onMotionEvent(MotionEvent event, int action, @BackEvent.SwipeEdge int swipeEdge) { - if (action == MotionEvent.ACTION_DOWN) { - initAnimation(event); - } else if (action == MotionEvent.ACTION_MOVE) { + if (action == MotionEvent.ACTION_MOVE) { + if (!mBackGestureStarted) { + // Let the animation initialized here to make sure the onPointerDownOutsideFocus + // could be happened when ACTION_DOWN, it may change the current focus that we + // would access it when startBackNavigation. + initAnimation(event); + } onMove(event, swipeEdge); } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { ProtoLog.d(WM_SHELL_BACK_PREVIEW, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 227494c04049..31fc6a5be589 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -71,7 +71,7 @@ public class Bubble implements BubbleViewProvider { private long mLastAccessed; @Nullable - private Bubbles.SuppressionChangedListener mSuppressionListener; + private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener; /** Whether the bubble should show a dot for the notification indicating updated content. */ private boolean mShowBubbleUpdateDot = true; @@ -192,13 +192,13 @@ public class Bubble implements BubbleViewProvider { @VisibleForTesting(visibility = PRIVATE) public Bubble(@NonNull final BubbleEntry entry, - @Nullable final Bubbles.SuppressionChangedListener listener, + @Nullable final Bubbles.BubbleMetadataFlagListener listener, final Bubbles.PendingIntentCanceledListener intentCancelListener, Executor mainExecutor) { mKey = entry.getKey(); mGroupKey = entry.getGroupKey(); mLocusId = entry.getLocusId(); - mSuppressionListener = listener; + mBubbleMetadataFlagListener = listener; mIntentCancelListener = intent -> { if (mIntent != null) { mIntent.unregisterCancelListener(mIntentCancelListener); @@ -606,8 +606,8 @@ public class Bubble implements BubbleViewProvider { mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; } - if (showInShade() != prevShowInShade && mSuppressionListener != null) { - mSuppressionListener.onBubbleNotificationSuppressionChange(this); + if (showInShade() != prevShowInShade && mBubbleMetadataFlagListener != null) { + mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this); } } @@ -626,8 +626,8 @@ public class Bubble implements BubbleViewProvider { } else { mFlags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; } - if (prevSuppressed != suppressBubble && mSuppressionListener != null) { - mSuppressionListener.onBubbleNotificationSuppressionChange(this); + if (prevSuppressed != suppressBubble && mBubbleMetadataFlagListener != null) { + mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this); } } @@ -771,12 +771,17 @@ public class Bubble implements BubbleViewProvider { return isEnabled(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } - void setShouldAutoExpand(boolean shouldAutoExpand) { + @VisibleForTesting + public void setShouldAutoExpand(boolean shouldAutoExpand) { + boolean prevAutoExpand = shouldAutoExpand(); if (shouldAutoExpand) { enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } else { disable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); } + if (prevAutoExpand != shouldAutoExpand && mBubbleMetadataFlagListener != null) { + mBubbleMetadataFlagListener.onBubbleMetadataFlagChanged(this); + } } public void setIsBubble(final boolean isBubble) { @@ -799,6 +804,10 @@ public class Bubble implements BubbleViewProvider { return (mFlags & option) != 0; } + public int getFlags() { + return mFlags; + } + @Override public String toString() { return "Bubble{" + mKey + '}'; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 806c395bf395..f407bdcb8852 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -323,7 +323,7 @@ public class BubbleController { public void initialize() { mBubbleData.setListener(mBubbleDataListener); - mBubbleData.setSuppressionChangedListener(this::onBubbleNotificationSuppressionChanged); + mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged); mBubbleData.setPendingIntentCancelledListener(bubble -> { if (bubble.getBubbleIntent() == null) { @@ -554,11 +554,10 @@ public class BubbleController { } @VisibleForTesting - public void onBubbleNotificationSuppressionChanged(Bubble bubble) { + public void onBubbleMetadataFlagChanged(Bubble bubble) { // Make sure NoMan knows suppression state so that anyone querying it can tell. try { - mBarService.onBubbleNotificationSuppressionChanged(bubble.getKey(), - !bubble.showInShade(), bubble.isSuppressed()); + mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags()); } catch (RemoteException e) { // Bad things have happened } @@ -1038,7 +1037,15 @@ public class BubbleController { } } else { Bubble bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); - inflateAndAdd(bubble, suppressFlyout, showInShade); + if (notif.shouldSuppressNotificationList()) { + // If we're suppressing notifs for DND, we don't want the bubbles to randomly + // expand when DND turns off so flip the flag. + if (bubble.shouldAutoExpand()) { + bubble.setShouldAutoExpand(false); + } + } else { + inflateAndAdd(bubble, suppressFlyout, showInShade); + } } } @@ -1070,7 +1077,8 @@ public class BubbleController { } } - private void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) { + @VisibleForTesting + public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp) { // shouldBubbleUp checks canBubble & for bubble metadata boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry); if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { @@ -1096,7 +1104,8 @@ public class BubbleController { } } - private void onRankingUpdated(RankingMap rankingMap, + @VisibleForTesting + public void onRankingUpdated(RankingMap rankingMap, HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) { if (mTmpRanking == null) { mTmpRanking = new NotificationListenerService.Ranking(); @@ -1107,19 +1116,22 @@ public class BubbleController { Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key); BubbleEntry entry = entryData.first; boolean shouldBubbleUp = entryData.second; - if (entry != null && !isCurrentProfile( entry.getStatusBarNotification().getUser().getIdentifier())) { return; } - + if (entry != null && (entry.shouldSuppressNotificationList() + || entry.getRanking().isSuspended())) { + shouldBubbleUp = false; + } rankingMap.getRanking(key, mTmpRanking); - boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key); - if (isActiveBubble && !mTmpRanking.canBubble()) { + boolean isActiveOrInOverflow = mBubbleData.hasAnyBubbleWithKey(key); + boolean isActive = mBubbleData.hasBubbleInStackWithKey(key); + if (isActiveOrInOverflow && !mTmpRanking.canBubble()) { // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason. // This means that the app or channel's ability to bubble has been revoked. mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED); - } else if (isActiveBubble && (!shouldBubbleUp || entry.getRanking().isSuspended())) { + } else if (isActiveOrInOverflow && !shouldBubbleUp) { // If this entry is allowed to bubble, but cannot currently bubble up or is // suspended, dismiss it. This happens when DND is enabled and configured to hide // bubbles, or focus mode is enabled and the app is designated as distracting. @@ -1127,9 +1139,9 @@ public class BubbleController { // notification, so that the bubble will be re-created if shouldBubbleUp returns // true. mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP); - } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { + } else if (entry != null && mTmpRanking.isBubble() && !isActive) { entry.setFlagBubble(true); - onEntryUpdated(entry, shouldBubbleUp && !entry.getRanking().isSuspended()); + onEntryUpdated(entry, shouldBubbleUp); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index c98c0e69de15..e4a0fd03860c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -159,7 +159,7 @@ public class BubbleData { private Listener mListener; @Nullable - private Bubbles.SuppressionChangedListener mSuppressionListener; + private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener; private Bubbles.PendingIntentCanceledListener mCancelledListener; /** @@ -190,9 +190,8 @@ public class BubbleData { mMaxOverflowBubbles = mContext.getResources().getInteger(R.integer.bubbles_max_overflow); } - public void setSuppressionChangedListener( - Bubbles.SuppressionChangedListener listener) { - mSuppressionListener = listener; + public void setSuppressionChangedListener(Bubbles.BubbleMetadataFlagListener listener) { + mBubbleMetadataFlagListener = listener; } public void setPendingIntentCancelledListener( @@ -311,7 +310,7 @@ public class BubbleData { bubbleToReturn = mPendingBubbles.get(key); } else if (entry != null) { // New bubble - bubbleToReturn = new Bubble(entry, mSuppressionListener, mCancelledListener, + bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener, mMainExecutor); } else { // Persisted bubble being promoted @@ -1058,6 +1057,22 @@ public class BubbleData { return null; } + /** + * Get a pending bubble with given notification <code>key</code> + * + * @param key notification key + * @return bubble that matches or null + */ + @VisibleForTesting(visibility = PRIVATE) + public Bubble getPendingBubbleWithKey(String key) { + for (Bubble b : mPendingBubbles.values()) { + if (b.getKey().equals(key)) { + return b; + } + } + return null; + } + @VisibleForTesting(visibility = PRIVATE) void setTimeSource(TimeSource timeSource) { mTimeSource = timeSource; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 2b2a2f7e35df..c7db8d8d1646 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -263,10 +263,10 @@ public interface Bubbles { void onBubbleExpandChanged(boolean isExpanding, String key); } - /** Listener to be notified when the flags for notification or bubble suppression changes.*/ - interface SuppressionChangedListener { - /** Called when the notification suppression state of a bubble changes. */ - void onBubbleNotificationSuppressionChange(Bubble bubble); + /** Listener to be notified when the flags on BubbleMetadata have changed. */ + interface BubbleMetadataFlagListener { + /** Called when the flags on BubbleMetadata have changed for the provided bubble. */ + void onBubbleMetadataFlagChanged(Bubble bubble); } /** Listener to be notified when a pending intent has been canceled for a bubble. */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java index 4b125b118ceb..6305959bb6ac 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java @@ -24,6 +24,7 @@ import android.animation.AnimatorListenerAdapter; import android.animation.ObjectAnimator; import android.content.Context; import android.graphics.Rect; +import android.os.Bundle; import android.util.AttributeSet; import android.util.Property; import android.view.GestureDetector; @@ -37,6 +38,8 @@ import android.view.View; import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.WindowManager; +import android.view.accessibility.AccessibilityNodeInfo; +import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.widget.FrameLayout; import androidx.annotation.NonNull; @@ -80,7 +83,6 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { private final Rect mTempRect = new Rect(); private FrameLayout mDividerBar; - static final Property<DividerView, Integer> DIVIDER_HEIGHT_PROPERTY = new Property<DividerView, Integer>(Integer.class, "height") { @Override @@ -109,6 +111,74 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { } }; + private final AccessibilityDelegate mHandleDelegate = new AccessibilityDelegate() { + @Override + public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) { + super.onInitializeAccessibilityNodeInfo(host, info); + final DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm; + if (isLandscape()) { + info.addAction(new AccessibilityAction(R.id.action_move_tl_full, + mContext.getString(R.string.accessibility_action_divider_left_full))); + if (snapAlgorithm.isFirstSplitTargetAvailable()) { + info.addAction(new AccessibilityAction(R.id.action_move_tl_70, + mContext.getString(R.string.accessibility_action_divider_left_70))); + } + if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) { + // Only show the middle target if there are more than 1 split target + info.addAction(new AccessibilityAction(R.id.action_move_tl_50, + mContext.getString(R.string.accessibility_action_divider_left_50))); + } + if (snapAlgorithm.isLastSplitTargetAvailable()) { + info.addAction(new AccessibilityAction(R.id.action_move_tl_30, + mContext.getString(R.string.accessibility_action_divider_left_30))); + } + info.addAction(new AccessibilityAction(R.id.action_move_rb_full, + mContext.getString(R.string.accessibility_action_divider_right_full))); + } else { + info.addAction(new AccessibilityAction(R.id.action_move_tl_full, + mContext.getString(R.string.accessibility_action_divider_top_full))); + if (snapAlgorithm.isFirstSplitTargetAvailable()) { + info.addAction(new AccessibilityAction(R.id.action_move_tl_70, + mContext.getString(R.string.accessibility_action_divider_top_70))); + } + if (snapAlgorithm.showMiddleSplitTargetForAccessibility()) { + // Only show the middle target if there are more than 1 split target + info.addAction(new AccessibilityAction(R.id.action_move_tl_50, + mContext.getString(R.string.accessibility_action_divider_top_50))); + } + if (snapAlgorithm.isLastSplitTargetAvailable()) { + info.addAction(new AccessibilityAction(R.id.action_move_tl_30, + mContext.getString(R.string.accessibility_action_divider_top_30))); + } + info.addAction(new AccessibilityAction(R.id.action_move_rb_full, + mContext.getString(R.string.accessibility_action_divider_bottom_full))); + } + } + + @Override + public boolean performAccessibilityAction(@NonNull View host, int action, + @Nullable Bundle args) { + DividerSnapAlgorithm.SnapTarget nextTarget = null; + DividerSnapAlgorithm snapAlgorithm = mSplitLayout.mDividerSnapAlgorithm; + if (action == R.id.action_move_tl_full) { + nextTarget = snapAlgorithm.getDismissEndTarget(); + } else if (action == R.id.action_move_tl_70) { + nextTarget = snapAlgorithm.getLastSplitTarget(); + } else if (action == R.id.action_move_tl_50) { + nextTarget = snapAlgorithm.getMiddleTarget(); + } else if (action == R.id.action_move_tl_30) { + nextTarget = snapAlgorithm.getFirstSplitTarget(); + } else if (action == R.id.action_move_rb_full) { + nextTarget = snapAlgorithm.getDismissStartTarget(); + } + if (nextTarget != null) { + mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), nextTarget); + return true; + } + return super.performAccessibilityAction(host, action, args); + } + }; + public DividerView(@NonNull Context context) { super(context); } @@ -179,6 +249,7 @@ public class DividerView extends FrameLayout implements View.OnTouchListener { mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener()); mInteractive = true; setOnTouchListener(this); + mHandle.setAccessibilityDelegate(mHandleDelegate); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java index 72c8141c8f2a..dfd4362e2373 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvPipModule.java @@ -29,6 +29,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.common.annotations.ShellMainThread; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipAppOpsListener; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; import com.android.wm.shell.pip.PipSnapAlgorithm; @@ -63,6 +64,7 @@ public abstract class TvPipModule { Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, TvPipMenuController tvPipMenuController, PipMediaController pipMediaController, @@ -79,6 +81,7 @@ public abstract class TvPipModule { context, tvPipBoundsState, tvPipBoundsAlgorithm, + pipAppOpsListener, pipTaskOrganizer, pipTransitionController, tvPipMenuController, @@ -140,8 +143,11 @@ public abstract class TvPipModule { @Provides static TvPipNotificationController provideTvPipNotificationController(Context context, PipMediaController pipMediaController, + PipParamsChangedForwarder pipParamsChangedForwarder, + TvPipBoundsState tvPipBoundsState, @ShellMainThread Handler mainHandler) { - return new TvPipNotificationController(context, pipMediaController, mainHandler); + return new TvPipNotificationController(context, pipMediaController, + pipParamsChangedForwarder, tvPipBoundsState, mainHandler); } @WMSingleton @@ -185,4 +191,12 @@ public abstract class TvPipModule { static PipParamsChangedForwarder providePipParamsChangedForwarder() { return new PipParamsChangedForwarder(); } + + @WMSingleton + @Provides + static PipAppOpsListener providePipAppOpsListener(Context context, + PipTaskOrganizer pipTaskOrganizer, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipAppOpsListener(context, pipTaskOrganizer::removePip, mainExecutor); + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 333567320312..db6131a17114 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -78,7 +78,6 @@ import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipSurfaceTransactionHelper; import com.android.wm.shell.pip.PipUiEventLogger; -import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasks; import com.android.wm.shell.recents.RecentTasksController; @@ -435,14 +434,6 @@ public abstract class WMShellBaseModule { return new FloatingContentCoordinator(); } - @WMSingleton - @Provides - static PipAppOpsListener providePipAppOpsListener(Context context, - PipTouchHandler pipTouchHandler, - @ShellMainThread ShellExecutor mainExecutor) { - return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor); - } - // Needs handler for registering broadcast receivers @WMSingleton @Provides diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 7513e5129ade..1bc9e31b9e2e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -51,6 +51,7 @@ import com.android.wm.shell.legacysplitscreen.LegacySplitScreenController; import com.android.wm.shell.onehanded.OneHandedController; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipAppOpsListener; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; @@ -63,9 +64,7 @@ import com.android.wm.shell.pip.PipTransitionController; import com.android.wm.shell.pip.PipTransitionState; import com.android.wm.shell.pip.PipUiEventLogger; import com.android.wm.shell.pip.phone.PhonePipMenuController; -import com.android.wm.shell.pip.phone.PipAppOpsListener; import com.android.wm.shell.pip.phone.PipController; -import com.android.wm.shell.pip.phone.PipKeepClearAlgorithm; import com.android.wm.shell.pip.phone.PipMotionHelper; import com.android.wm.shell.pip.phone.PipTouchHandler; import com.android.wm.shell.recents.RecentTasksController; @@ -211,8 +210,7 @@ public class WMShellModule { @Provides static Optional<Pip> providePip(Context context, DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, - PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, + PipBoundsState pipBoundsState, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, @@ -221,8 +219,8 @@ public class WMShellModule { Optional<OneHandedController> oneHandedController, @ShellMainThread ShellExecutor mainExecutor) { return Optional.ofNullable(PipController.create(context, displayController, - pipAppOpsListener, pipBoundsAlgorithm, pipKeepClearAlgorithm, pipBoundsState, - pipMotionHelper, pipMediaController, phonePipMenuController, pipTaskOrganizer, + pipAppOpsListener, pipBoundsAlgorithm, pipBoundsState, + pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor)); } @@ -241,12 +239,6 @@ public class WMShellModule { @WMSingleton @Provides - static PipKeepClearAlgorithm providePipKeepClearAlgorithm() { - return new PipKeepClearAlgorithm(); - } - - @WMSingleton - @Provides static PipBoundsAlgorithm providesPipBoundsAlgorithm(Context context, PipBoundsState pipBoundsState, PipSnapAlgorithm pipSnapAlgorithm) { return new PipBoundsAlgorithm(context, pipBoundsState, pipSnapAlgorithm); @@ -333,6 +325,14 @@ public class WMShellModule { @WMSingleton @Provides + static PipAppOpsListener providePipAppOpsListener(Context context, + PipTouchHandler pipTouchHandler, + @ShellMainThread ShellExecutor mainExecutor) { + return new PipAppOpsListener(context, pipTouchHandler.getMotionHelper(), mainExecutor); + } + + @WMSingleton + @Provides static PipMotionHelper providePipMotionHelper(Context context, PipBoundsState pipBoundsState, PipTaskOrganizer pipTaskOrganizer, PhonePipMenuController menuController, PipSnapAlgorithm pipSnapAlgorithm, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java index d97d2d6ebb4f..48a3fc2460a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipAppOpsListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAppOpsListener.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.wm.shell.pip.phone; +package com.android.wm.shell.pip; import static android.app.AppOpsManager.MODE_ALLOWED; import static android.app.AppOpsManager.OP_PICTURE_IN_PICTURE; @@ -28,7 +28,6 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.util.Pair; import com.android.wm.shell.common.ShellExecutor; -import com.android.wm.shell.pip.PipUtils; public class PipAppOpsListener { private static final String TAG = PipAppOpsListener.class.getSimpleName(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java index 8a50f2233573..65a12d629c5a 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMediaController.java @@ -32,6 +32,7 @@ import android.content.IntentFilter; import android.graphics.drawable.Icon; import android.media.MediaMetadata; import android.media.session.MediaController; +import android.media.session.MediaSession; import android.media.session.MediaSessionManager; import android.media.session.PlaybackState; import android.os.Handler; @@ -64,7 +65,7 @@ public class PipMediaController { */ public interface ActionListener { /** - * Called when the media actions changes. + * Called when the media actions changed. */ void onMediaActionsChanged(List<RemoteAction> actions); } @@ -74,11 +75,21 @@ public class PipMediaController { */ public interface MetadataListener { /** - * Called when the media metadata changes. + * Called when the media metadata changed. */ void onMediaMetadataChanged(MediaMetadata metadata); } + /** + * A listener interface to receive notification on changes to the media session token. + */ + public interface TokenListener { + /** + * Called when the media session token changed. + */ + void onMediaSessionTokenChanged(MediaSession.Token token); + } + private final Context mContext; private final Handler mMainHandler; private final HandlerExecutor mHandlerExecutor; @@ -133,6 +144,7 @@ public class PipMediaController { private final ArrayList<ActionListener> mActionListeners = new ArrayList<>(); private final ArrayList<MetadataListener> mMetadataListeners = new ArrayList<>(); + private final ArrayList<TokenListener> mTokenListeners = new ArrayList<>(); public PipMediaController(Context context, Handler mainHandler) { mContext = context; @@ -204,6 +216,31 @@ public class PipMediaController { mMetadataListeners.remove(listener); } + /** + * Adds a new token listener. + */ + public void addTokenListener(TokenListener listener) { + if (!mTokenListeners.contains(listener)) { + mTokenListeners.add(listener); + listener.onMediaSessionTokenChanged(getToken()); + } + } + + /** + * Removes a token listener. + */ + public void removeTokenListener(TokenListener listener) { + listener.onMediaSessionTokenChanged(null); + mTokenListeners.remove(listener); + } + + private MediaSession.Token getToken() { + if (mMediaController == null) { + return null; + } + return mMediaController.getSessionToken(); + } + private MediaMetadata getMediaMetadata() { return mMediaController != null ? mMediaController.getMetadata() : null; } @@ -294,6 +331,7 @@ public class PipMediaController { } notifyActionsChanged(); notifyMetadataChanged(getMediaMetadata()); + notifyTokenChanged(getToken()); // TODO(winsonc): Consider if we want to close the PIP after a timeout (like on TV) } @@ -317,4 +355,10 @@ public class PipMediaController { mMetadataListeners.forEach(l -> l.onMediaMetadataChanged(metadata)); } } + + private void notifyTokenChanged(MediaSession.Token token) { + if (!mTokenListeners.isEmpty()) { + mTokenListeners.forEach(l -> l.onMediaSessionTokenChanged(token)); + } + } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index 2e8b5b7979d0..42ceb42f39e8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -76,6 +76,7 @@ import com.android.wm.shell.pip.IPipAnimationListener; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipAppOpsListener; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; @@ -109,9 +110,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb private PipAppOpsListener mAppOpsListener; private PipMediaController mMediaController; private PipBoundsAlgorithm mPipBoundsAlgorithm; - private PipKeepClearAlgorithm mPipKeepClearAlgorithm; private PipBoundsState mPipBoundsState; - private PipMotionHelper mPipMotionHelper; private PipTouchHandler mTouchHandler; private PipTransitionController mPipTransitionController; private TaskStackListenerImpl mTaskStackListener; @@ -247,10 +246,6 @@ public class PipController implements PipTransitionController.PipTransitionCallb Set<Rect> unrestricted) { if (mPipBoundsState.getDisplayId() == displayId) { mPipBoundsState.setKeepClearAreas(restricted, unrestricted); - mPipMotionHelper.moveToBounds(mPipKeepClearAlgorithm.adjust( - mPipBoundsState.getBounds(), - mPipBoundsState.getRestrictedKeepClearAreas(), - mPipBoundsState.getUnrestrictedKeepClearAreas())); } } }; @@ -289,8 +284,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Nullable public static Pip create(Context context, DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipKeepClearAlgorithm pipKeepClearAlgorithm, PipBoundsState pipBoundsState, - PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, + PipBoundsState pipBoundsState, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, PipTouchHandler pipTouchHandler, PipTransitionController pipTransitionController, WindowManagerShellWrapper windowManagerShellWrapper, @@ -305,7 +299,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb } return new PipController(context, displayController, pipAppOpsListener, pipBoundsAlgorithm, - pipKeepClearAlgorithm, pipBoundsState, pipMotionHelper, pipMediaController, + pipBoundsState, pipMediaController, phonePipMenuController, pipTaskOrganizer, pipTouchHandler, pipTransitionController, windowManagerShellWrapper, taskStackListener, pipParamsChangedForwarder, oneHandedController, mainExecutor) @@ -316,9 +310,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb DisplayController displayController, PipAppOpsListener pipAppOpsListener, PipBoundsAlgorithm pipBoundsAlgorithm, - PipKeepClearAlgorithm pipKeepClearAlgorithm, @NonNull PipBoundsState pipBoundsState, - PipMotionHelper pipMotionHelper, PipMediaController pipMediaController, PhonePipMenuController phonePipMenuController, PipTaskOrganizer pipTaskOrganizer, @@ -341,9 +333,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb mWindowManagerShellWrapper = windowManagerShellWrapper; mDisplayController = displayController; mPipBoundsAlgorithm = pipBoundsAlgorithm; - mPipKeepClearAlgorithm = pipKeepClearAlgorithm; mPipBoundsState = pipBoundsState; - mPipMotionHelper = pipMotionHelper; mPipTaskOrganizer = pipTaskOrganizer; mMainExecutor = mainExecutor; mMediaController = pipMediaController; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java deleted file mode 100644 index a83258f9063b..000000000000 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithm.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.wm.shell.pip.phone; - -import android.graphics.Rect; - -import java.util.Set; - -/** - * Calculates the adjusted position that does not occlude keep clear areas. - */ -public class PipKeepClearAlgorithm { - - /** Returns a new {@code Rect} that does not occlude the provided keep clear areas. */ - public Rect adjust(Rect defaultBounds, Set<Rect> restrictedKeepClearAreas, - Set<Rect> unrestrictedKeepClearAreas) { - if (restrictedKeepClearAreas.isEmpty()) { - return defaultBounds; - } - // TODO(b/183746978): implement the adjustment algorithm - // naively check if areas intersect, an if so move PiP upwards - Rect outBounds = new Rect(defaultBounds); - for (Rect r : restrictedKeepClearAreas) { - if (r.intersect(outBounds)) { - outBounds.offset(0, r.top - outBounds.bottom); - } - } - return outBounds; - } -} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java index e9b6babfc5fa..5a21e0734277 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java @@ -44,6 +44,7 @@ import com.android.wm.shell.animation.FloatProperties; import com.android.wm.shell.animation.PhysicsAnimator; import com.android.wm.shell.common.FloatingContentCoordinator; import com.android.wm.shell.common.magnetictarget.MagnetizedObject; +import com.android.wm.shell.pip.PipAppOpsListener; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipSnapAlgorithm; import com.android.wm.shell.pip.PipTaskOrganizer; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java index 8326588bbbad..766779413094 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipController.java @@ -45,6 +45,7 @@ import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.pip.PinnedStackListenerForwarder; import com.android.wm.shell.pip.Pip; import com.android.wm.shell.pip.PipAnimationController; +import com.android.wm.shell.pip.PipAppOpsListener; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; import com.android.wm.shell.pip.PipParamsChangedForwarder; @@ -97,6 +98,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal private final TvPipBoundsState mTvPipBoundsState; private final TvPipBoundsAlgorithm mTvPipBoundsAlgorithm; + private final PipAppOpsListener mAppOpsListener; private final PipTaskOrganizer mPipTaskOrganizer; private final PipMediaController mPipMediaController; private final TvPipNotificationController mPipNotificationController; @@ -121,6 +123,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, TvPipMenuController tvPipMenuController, @@ -136,6 +139,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal context, tvPipBoundsState, tvPipBoundsAlgorithm, + pipAppOpsListener, pipTaskOrganizer, pipTransitionController, tvPipMenuController, @@ -153,6 +157,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal Context context, TvPipBoundsState tvPipBoundsState, TvPipBoundsAlgorithm tvPipBoundsAlgorithm, + PipAppOpsListener pipAppOpsListener, PipTaskOrganizer pipTaskOrganizer, PipTransitionController pipTransitionController, TvPipMenuController tvPipMenuController, @@ -181,6 +186,7 @@ public class TvPipController implements PipTransitionController.PipTransitionCal mTvPipMenuController = tvPipMenuController; mTvPipMenuController.setDelegate(this); + mAppOpsListener = pipAppOpsListener; mPipTaskOrganizer = pipTaskOrganizer; pipTransitionController.registerPipTransitionCallback(this); @@ -287,6 +293,8 @@ public class TvPipController implements PipTransitionController.PipTransitionCal } mTvPipBoundsState.setTvPipManuallyCollapsed(!expanding); mTvPipBoundsState.setTvPipExpanded(expanding); + mPipNotificationController.updateExpansionState(); + updatePinnedStackBounds(); } @@ -521,6 +529,12 @@ public class TvPipController implements PipTransitionController.PipTransitionCal @Override public void onActivityPinned(String packageName, int userId, int taskId, int stackId) { checkIfPinnedTaskAppeared(); + mAppOpsListener.onActivityPinned(packageName); + } + + @Override + public void onActivityUnpinned() { + mAppOpsListener.onActivityUnpinned(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java index 868e45655ba3..320c05c4a415 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuView.java @@ -494,6 +494,14 @@ public class TvPipMenuView extends FrameLayout implements View.OnClickListener { setFrameHighlighted(false); } + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + if (!hasWindowFocus) { + hideAllUserControls(); + } + } + private void animateAlphaTo(float alpha, View view) { if (view.getAlpha() == alpha) { return; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java index 4033f030b702..61a609d9755e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipNotificationController.java @@ -16,36 +16,47 @@ package com.android.wm.shell.pip.tv; +import static android.app.Notification.Action.SEMANTIC_ACTION_DELETE; +import static android.app.Notification.Action.SEMANTIC_ACTION_NONE; + import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; +import android.app.RemoteAction; import android.content.BroadcastReceiver; +import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; -import android.media.MediaMetadata; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.Icon; +import android.media.session.MediaSession; +import android.os.Bundle; import android.os.Handler; import android.text.TextUtils; import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; import com.android.internal.protolog.common.ProtoLog; +import com.android.internal.util.ImageUtils; import com.android.wm.shell.R; import com.android.wm.shell.pip.PipMediaController; +import com.android.wm.shell.pip.PipParamsChangedForwarder; +import com.android.wm.shell.pip.PipUtils; import com.android.wm.shell.protolog.ShellProtoLogGroup; -import java.util.Objects; +import java.util.ArrayList; +import java.util.List; /** - * A notification that informs users that PIP is running and also provides PIP controls. - * <p>Once it's created, it will manage the PIP notification UI by itself except for handling - * configuration changes. + * A notification that informs users that PiP is running and also provides PiP controls. + * <p>Once it's created, it will manage the PiP notification UI by itself except for handling + * configuration changes and user initiated expanded PiP toggling. */ public class TvPipNotificationController { private static final String TAG = "TvPipNotification"; - private static final boolean DEBUG = TvPipController.DEBUG; // Referenced in com.android.systemui.util.NotificationChannels. public static final String NOTIFICATION_CHANNEL = "TVPIP"; @@ -60,6 +71,8 @@ public class TvPipNotificationController { "com.android.wm.shell.pip.tv.notification.action.MOVE_PIP"; private static final String ACTION_TOGGLE_EXPANDED_PIP = "com.android.wm.shell.pip.tv.notification.action.TOGGLE_EXPANDED_PIP"; + private static final String ACTION_FULLSCREEN = + "com.android.wm.shell.pip.tv.notification.action.FULLSCREEN"; private final Context mContext; private final PackageManager mPackageManager; @@ -68,44 +81,88 @@ public class TvPipNotificationController { private final ActionBroadcastReceiver mActionBroadcastReceiver; private final Handler mMainHandler; private Delegate mDelegate; + private final TvPipBoundsState mTvPipBoundsState; private String mDefaultTitle; + private final List<RemoteAction> mCustomActions = new ArrayList<>(); + private final List<RemoteAction> mMediaActions = new ArrayList<>(); + private RemoteAction mCustomCloseAction; + + private MediaSession.Token mMediaSessionToken; + /** Package name for the application that owns PiP window. */ private String mPackageName; - private boolean mNotified; - private String mMediaTitle; - private Bitmap mArt; + + private boolean mIsNotificationShown; + private String mPipTitle; + private String mPipSubtitle; + + private Bitmap mActivityIcon; public TvPipNotificationController(Context context, PipMediaController pipMediaController, + PipParamsChangedForwarder pipParamsChangedForwarder, TvPipBoundsState tvPipBoundsState, Handler mainHandler) { mContext = context; mPackageManager = context.getPackageManager(); mNotificationManager = context.getSystemService(NotificationManager.class); mMainHandler = mainHandler; + mTvPipBoundsState = tvPipBoundsState; mNotificationBuilder = new Notification.Builder(context, NOTIFICATION_CHANNEL) .setLocalOnly(true) - .setOngoing(false) + .setOngoing(true) .setCategory(Notification.CATEGORY_SYSTEM) .setShowWhen(true) .setSmallIcon(R.drawable.pip_icon) + .setAllowSystemGeneratedContextualActions(false) + .setContentIntent(createPendingIntent(context, ACTION_FULLSCREEN)) + .setDeleteIntent(getCloseAction().actionIntent) .extend(new Notification.TvExtender() .setContentIntent(createPendingIntent(context, ACTION_SHOW_PIP_MENU)) .setDeleteIntent(createPendingIntent(context, ACTION_CLOSE_PIP))); mActionBroadcastReceiver = new ActionBroadcastReceiver(); - pipMediaController.addMetadataListener(this::onMediaMetadataChanged); + pipMediaController.addActionListener(this::onMediaActionsChanged); + pipMediaController.addTokenListener(this::onMediaSessionTokenChanged); + + pipParamsChangedForwarder.addListener( + new PipParamsChangedForwarder.PipParamsChangedCallback() { + @Override + public void onExpandedAspectRatioChanged(float ratio) { + updateExpansionState(); + } + + @Override + public void onActionsChanged(List<RemoteAction> actions, + RemoteAction closeAction) { + mCustomActions.clear(); + mCustomActions.addAll(actions); + mCustomCloseAction = closeAction; + updateNotificationContent(); + } + + @Override + public void onTitleChanged(String title) { + mPipTitle = title; + updateNotificationContent(); + } + + @Override + public void onSubtitleChanged(String subtitle) { + mPipSubtitle = subtitle; + updateNotificationContent(); + } + }); onConfigurationChanged(context); } void setDelegate(Delegate delegate) { - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: setDelegate(), delegate=%s", TAG, delegate); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: setDelegate(), delegate=%s", + TAG, delegate); + if (mDelegate != null) { throw new IllegalStateException( "The delegate has already been set and should not change."); @@ -118,90 +175,181 @@ public class TvPipNotificationController { } void show(String packageName) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: show %s", TAG, packageName); if (mDelegate == null) { throw new IllegalStateException("Delegate is not set."); } + mIsNotificationShown = true; mPackageName = packageName; - update(); + mActivityIcon = getActivityIcon(); mActionBroadcastReceiver.register(); + + updateNotificationContent(); } void dismiss() { - mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP); - mNotified = false; + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: dismiss()", TAG); + + mIsNotificationShown = false; mPackageName = null; mActionBroadcastReceiver.unregister(); + + mNotificationManager.cancel(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP); } - private void onMediaMetadataChanged(MediaMetadata metadata) { - if (updateMediaControllerMetadata(metadata) && mNotified) { - // update notification - update(); + private Notification.Action getToggleAction(boolean expanded) { + if (expanded) { + return createSystemAction(R.drawable.pip_ic_collapse, + R.string.pip_collapse, ACTION_TOGGLE_EXPANDED_PIP); + } else { + return createSystemAction(R.drawable.pip_ic_expand, R.string.pip_expand, + ACTION_TOGGLE_EXPANDED_PIP); } } - /** - * Called by {@link PipController} when the configuration is changed. - */ - void onConfigurationChanged(Context context) { - mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title); - if (mNotified) { - // Update the notification. - update(); + private Notification.Action createSystemAction(int iconRes, int titleRes, String action) { + Notification.Action.Builder builder = new Notification.Action.Builder( + Icon.createWithResource(mContext, iconRes), + mContext.getString(titleRes), + createPendingIntent(mContext, action)); + builder.setContextual(true); + return builder.build(); + } + + private void onMediaActionsChanged(List<RemoteAction> actions) { + mMediaActions.clear(); + mMediaActions.addAll(actions); + if (mCustomActions.isEmpty()) { + updateNotificationContent(); } } - private void update() { - mNotified = true; - mNotificationBuilder - .setWhen(System.currentTimeMillis()) - .setContentTitle(getNotificationTitle()); - if (mArt != null) { - mNotificationBuilder.setStyle(new Notification.BigPictureStyle() - .bigPicture(mArt)); - } else { - mNotificationBuilder.setStyle(null); + private void onMediaSessionTokenChanged(MediaSession.Token token) { + mMediaSessionToken = token; + updateNotificationContent(); + } + + private Notification.Action remoteToNotificationAction(RemoteAction action) { + return remoteToNotificationAction(action, SEMANTIC_ACTION_NONE); + } + + private Notification.Action remoteToNotificationAction(RemoteAction action, + int semanticAction) { + Notification.Action.Builder builder = new Notification.Action.Builder(action.getIcon(), + action.getTitle(), + action.getActionIntent()); + if (action.getContentDescription() != null) { + Bundle extras = new Bundle(); + extras.putCharSequence(Notification.EXTRA_PICTURE_CONTENT_DESCRIPTION, + action.getContentDescription()); + builder.addExtras(extras); } - mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP, - mNotificationBuilder.build()); + builder.setSemanticAction(semanticAction); + builder.setContextual(true); + return builder.build(); } - private boolean updateMediaControllerMetadata(MediaMetadata metadata) { - String title = null; - Bitmap art = null; - if (metadata != null) { - title = metadata.getString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE); - if (TextUtils.isEmpty(title)) { - title = metadata.getString(MediaMetadata.METADATA_KEY_TITLE); - } - art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART); - if (art == null) { - art = metadata.getBitmap(MediaMetadata.METADATA_KEY_ART); + private Notification.Action[] getNotificationActions() { + final List<Notification.Action> actions = new ArrayList<>(); + + // 1. Fullscreen + actions.add(getFullscreenAction()); + // 2. Close + actions.add(getCloseAction()); + // 3. App actions + final List<RemoteAction> appActions = + mCustomActions.isEmpty() ? mMediaActions : mCustomActions; + for (RemoteAction appAction : appActions) { + if (PipUtils.remoteActionsMatch(mCustomCloseAction, appAction) + || !appAction.isEnabled()) { + continue; } + actions.add(remoteToNotificationAction(appAction)); + } + // 4. Move + actions.add(getMoveAction()); + // 5. Toggle expansion (if expanded PiP enabled) + if (mTvPipBoundsState.getDesiredTvExpandedAspectRatio() > 0 + && mTvPipBoundsState.isTvExpandedPipSupported()) { + actions.add(getToggleAction(mTvPipBoundsState.isTvPipExpanded())); } + return actions.toArray(new Notification.Action[0]); + } - if (TextUtils.equals(title, mMediaTitle) && Objects.equals(art, mArt)) { - return false; + private Notification.Action getCloseAction() { + if (mCustomCloseAction == null) { + return createSystemAction(R.drawable.pip_ic_close_white, R.string.pip_close, + ACTION_CLOSE_PIP); + } else { + return remoteToNotificationAction(mCustomCloseAction, SEMANTIC_ACTION_DELETE); } + } + + private Notification.Action getFullscreenAction() { + return createSystemAction(R.drawable.pip_ic_fullscreen_white, + R.string.pip_fullscreen, ACTION_FULLSCREEN); + } - mMediaTitle = title; - mArt = art; + private Notification.Action getMoveAction() { + return createSystemAction(R.drawable.pip_ic_move_white, R.string.pip_move, + ACTION_MOVE_PIP); + } - return true; + /** + * Called by {@link TvPipController} when the configuration is changed. + */ + void onConfigurationChanged(Context context) { + mDefaultTitle = context.getResources().getString(R.string.pip_notification_unknown_title); + updateNotificationContent(); } + void updateExpansionState() { + updateNotificationContent(); + } - private String getNotificationTitle() { - if (!TextUtils.isEmpty(mMediaTitle)) { - return mMediaTitle; + private void updateNotificationContent() { + if (mPackageManager == null || !mIsNotificationShown) { + return; + } + + Notification.Action[] actions = getNotificationActions(); + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: update(), title: %s, subtitle: %s, mediaSessionToken: %s, #actions: %s", TAG, + getNotificationTitle(), mPipSubtitle, mMediaSessionToken, actions.length); + for (Notification.Action action : actions) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, "%s: action: %s", TAG, + action.toString()); } + mNotificationBuilder + .setWhen(System.currentTimeMillis()) + .setContentTitle(getNotificationTitle()) + .setContentText(mPipSubtitle) + .setSubText(getApplicationLabel(mPackageName)) + .setActions(actions); + setPipIcon(); + + Bundle extras = new Bundle(); + extras.putParcelable(Notification.EXTRA_MEDIA_SESSION, mMediaSessionToken); + mNotificationBuilder.setExtras(extras); + + // TvExtender not recognized if not set last. + mNotificationBuilder.extend(new Notification.TvExtender() + .setContentIntent(createPendingIntent(mContext, ACTION_SHOW_PIP_MENU)) + .setDeleteIntent(createPendingIntent(mContext, ACTION_CLOSE_PIP))); + mNotificationManager.notify(NOTIFICATION_TAG, SystemMessage.NOTE_TV_PIP, + mNotificationBuilder.build()); + } + + private String getNotificationTitle() { + if (!TextUtils.isEmpty(mPipTitle)) { + return mPipTitle; + } final String applicationTitle = getApplicationLabel(mPackageName); if (!TextUtils.isEmpty(applicationTitle)) { return applicationTitle; } - return mDefaultTitle; } @@ -214,10 +362,37 @@ public class TvPipNotificationController { } } + private void setPipIcon() { + if (mActivityIcon != null) { + mNotificationBuilder.setLargeIcon(mActivityIcon); + return; + } + // Fallback: Picture-in-Picture icon + mNotificationBuilder.setLargeIcon(Icon.createWithResource(mContext, R.drawable.pip_icon)); + } + + private Bitmap getActivityIcon() { + if (mContext == null) return null; + ComponentName componentName = PipUtils.getTopPipActivity(mContext).first; + if (componentName == null) return null; + + Drawable drawable; + try { + drawable = mPackageManager.getActivityIcon(componentName); + } catch (PackageManager.NameNotFoundException e) { + return null; + } + int width = mContext.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + int height = mContext.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_height); + return ImageUtils.buildScaledBitmap(drawable, width, height, /* allowUpscaling */ true); + } + private static PendingIntent createPendingIntent(Context context, String action) { return PendingIntent.getBroadcast(context, 0, new Intent(action).setPackage(context.getPackageName()), - PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_IMMUTABLE); + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } private class ActionBroadcastReceiver extends BroadcastReceiver { @@ -228,6 +403,7 @@ public class TvPipNotificationController { mIntentFilter.addAction(ACTION_SHOW_PIP_MENU); mIntentFilter.addAction(ACTION_MOVE_PIP); mIntentFilter.addAction(ACTION_TOGGLE_EXPANDED_PIP); + mIntentFilter.addAction(ACTION_FULLSCREEN); } boolean mRegistered = false; @@ -249,10 +425,8 @@ public class TvPipNotificationController { @Override public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); - if (DEBUG) { - ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, - "%s: on(Broadcast)Receive(), action=%s", TAG, action); - } + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: on(Broadcast)Receive(), action=%s", TAG, action); if (ACTION_SHOW_PIP_MENU.equals(action)) { mDelegate.showPictureInPictureMenu(); @@ -262,14 +436,21 @@ public class TvPipNotificationController { mDelegate.enterPipMovementMenu(); } else if (ACTION_TOGGLE_EXPANDED_PIP.equals(action)) { mDelegate.togglePipExpansion(); + } else if (ACTION_FULLSCREEN.equals(action)) { + mDelegate.movePipToFullscreen(); } } } interface Delegate { void showPictureInPictureMenu(); + void closePip(); + void enterPipMovementMenu(); + void togglePipExpansion(); + + void movePipToFullscreen(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java index 64017e176fc3..d04c34916256 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/protolog/ShellProtoLogGroup.java @@ -40,6 +40,8 @@ public enum ShellProtoLogGroup implements IProtoLogGroup { Consts.TAG_WM_SHELL), WM_SHELL_PICTURE_IN_PICTURE(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM_SHELL), + WM_SHELL_SPLIT_SCREEN(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, + Consts.TAG_WM_SHELL), TEST_GROUP(true, true, false, "WindowManagerShellProtoLogTest"); private final boolean mEnabled; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index 91f9d2522397..d543aa742377 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -54,6 +54,9 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_P import static com.android.wm.shell.transition.Transitions.isClosingType; import static com.android.wm.shell.transition.Transitions.isOpeningType; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; import android.annotation.CallSuper; import android.annotation.NonNull; import android.annotation.Nullable; @@ -68,6 +71,7 @@ import android.content.res.Configuration; import android.graphics.Rect; import android.hardware.devicestate.DeviceStateManager; import android.os.Bundle; +import android.os.Debug; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -147,7 +151,9 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private final int mDisplayId; private SplitLayout mSplitLayout; + private ValueAnimator mDividerFadeInAnimator; private boolean mDividerVisible; + private boolean mKeyguardShowing; private final SyncTransactionQueue mSyncQueue; private final ShellTaskOrganizer mTaskOrganizer; private final Context mContext; @@ -404,6 +410,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.init(); // Set false to avoid record new bounds with old task still on top; mShouldUpdateRecents = false; + mIsDividerRemoteAnimating = true; final WindowContainerTransaction wct = new WindowContainerTransaction(); final WindowContainerTransaction evictWct = new WindowContainerTransaction(); prepareEvictChildTasks(SPLIT_POSITION_TOP_OR_LEFT, evictWct); @@ -417,7 +424,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, final IRemoteAnimationFinishedCallback finishedCallback) { - mIsDividerRemoteAnimating = true; RemoteAnimationTarget[] augmentedNonApps = new RemoteAnimationTarget[nonApps.length + 1]; for (int i = 0; i < nonApps.length; ++i) { @@ -494,8 +500,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } // Using legacy transitions, so we can't use blast sync since it conflicts. mTaskOrganizer.applyTransaction(wct); - mSyncQueue.runInSync(t -> - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */)); + mSyncQueue.runInSync(t -> { + setDividerVisibility(true, t); + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); + }); } private void onRemoteAnimationFinishedOrCancelled(WindowContainerTransaction evictWct) { @@ -510,10 +518,6 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, ? mSideStage : mMainStage, EXIT_REASON_UNKNOWN)); } else { mSyncQueue.queue(evictWct); - mSyncQueue.runInSync(t -> { - setDividerVisibility(true, t); - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); - }); } } @@ -623,16 +627,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } void onKeyguardVisibilityChanged(boolean showing) { + mKeyguardShowing = showing; if (!mMainStage.isActive()) { return; } - if (ENABLE_SHELL_TRANSITIONS) { - // Update divider visibility so it won't float on top of keyguard. - setDividerVisibility(!showing, null /* transaction */); - } - - if (!showing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { + if (!mKeyguardShowing && mTopStageAfterFoldDismiss != STAGE_TYPE_UNDEFINED) { if (ENABLE_SHELL_TRANSITIONS) { final WindowContainerTransaction wct = new WindowContainerTransaction(); prepareExitSplitScreen(mTopStageAfterFoldDismiss, wct); @@ -643,7 +643,10 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mTopStageAfterFoldDismiss == STAGE_TYPE_MAIN ? mMainStage : mSideStage, EXIT_REASON_DEVICE_FOLDED); } + return; } + + setDividerVisibility(!mKeyguardShowing, null); } void onFinishedWakingUp() { @@ -727,6 +730,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, setResizingSplits(false /* resizing */); t.setWindowCrop(mMainStage.mRootLeash, null) .setWindowCrop(mSideStage.mRootLeash, null); + setDividerVisibility(false, t); }); // Hide divider and reset its position. @@ -1055,8 +1059,31 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, } private void setDividerVisibility(boolean visible, @Nullable SurfaceControl.Transaction t) { + if (visible == mDividerVisible) { + return; + } + + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "%s: Request to %s divider bar from %s.", TAG, + (visible ? "show" : "hide"), Debug.getCaller()); + + // Defer showing divider bar after keyguard dismissed, so it won't interfere with keyguard + // dismissing animation. + if (visible && mKeyguardShowing) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "%s: Defer showing divider bar due to keyguard showing.", TAG); + return; + } + mDividerVisible = visible; sendSplitVisibilityChanged(); + + if (mIsDividerRemoteAnimating) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "%s: Skip animating divider bar due to it's remote animating.", TAG); + return; + } + if (t != null) { applyDividerVisibility(t); } else { @@ -1066,15 +1093,56 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, private void applyDividerVisibility(SurfaceControl.Transaction t) { final SurfaceControl dividerLeash = mSplitLayout.getDividerLeash(); - if (mIsDividerRemoteAnimating || dividerLeash == null) return; + if (dividerLeash == null) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "%s: Skip animating divider bar due to divider leash not ready.", TAG); + return; + } + if (mIsDividerRemoteAnimating) { + ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, + "%s: Skip animating divider bar due to it's remote animating.", TAG); + return; + } + + if (mDividerFadeInAnimator != null && mDividerFadeInAnimator.isRunning()) { + mDividerFadeInAnimator.cancel(); + } if (mDividerVisible) { - t.show(dividerLeash); - t.setAlpha(dividerLeash, 1); - t.setLayer(dividerLeash, Integer.MAX_VALUE); - t.setPosition(dividerLeash, - mSplitLayout.getRefDividerBounds().left, - mSplitLayout.getRefDividerBounds().top); + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + mDividerFadeInAnimator = ValueAnimator.ofFloat(0f, 1f); + mDividerFadeInAnimator.addUpdateListener(animation -> { + if (dividerLeash == null) { + mDividerFadeInAnimator.cancel(); + return; + } + transaction.setAlpha(dividerLeash, (float) animation.getAnimatedValue()); + transaction.apply(); + }); + mDividerFadeInAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + if (dividerLeash == null) { + mDividerFadeInAnimator.cancel(); + return; + } + transaction.show(dividerLeash); + transaction.setAlpha(dividerLeash, 0); + transaction.setLayer(dividerLeash, Integer.MAX_VALUE); + transaction.setPosition(dividerLeash, + mSplitLayout.getRefDividerBounds().left, + mSplitLayout.getRefDividerBounds().top); + transaction.apply(); + } + + @Override + public void onAnimationEnd(Animator animation) { + mTransactionPool.release(transaction); + mDividerFadeInAnimator = null; + } + }); + + mDividerFadeInAnimator.start(); } else { t.hide(dividerLeash); } @@ -1096,10 +1164,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler, mSplitLayout.init(); prepareEnterSplitScreen(wct); mSyncQueue.queue(wct); - mSyncQueue.runInSync(t -> { - updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */); - setDividerVisibility(true, t); - }); + mSyncQueue.runInSync(t -> + updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */)); } if (mMainStageListener.mHasChildren && mSideStageListener.mHasChildren) { mShouldUpdateRecents = true; diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt index cf4ea467a29b..41cd31aabf05 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/helpers/AppPairsHelper.kt @@ -37,7 +37,7 @@ class AppPairsHelper( val displayBounds = WindowUtils.displayBounds val secondaryAppBounds = Region.from(0, dividerBounds.bounds.bottom - WindowUtils.dockedStackDividerInset, - displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarHeight) + displayBounds.right, displayBounds.bottom - WindowUtils.navigationBarFrameHeight) return secondaryAppBounds } diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt index a510d699387e..e2da1a4565c0 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/legacysplitscreen/ResizeLegacySplitScreen.kt @@ -171,7 +171,7 @@ class ResizeLegacySplitScreen( val bottomAppBounds = Region.from(0, dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, - displayBounds.bottom - WindowUtils.navigationBarHeight) + displayBounds.bottom - WindowUtils.navigationBarFrameHeight) visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent()) .coversExactly(topAppBounds) visibleRegion(Components.ImeActivity.COMPONENT.toFlickerComponent()) @@ -192,7 +192,7 @@ class ResizeLegacySplitScreen( val bottomAppBounds = Region.from(0, dividerBounds.bottom - WindowUtils.dockedStackDividerInset, displayBounds.right, - displayBounds.bottom - WindowUtils.navigationBarHeight) + displayBounds.bottom - WindowUtils.navigationBarFrameHeight) visibleRegion(Components.SimpleActivity.COMPONENT.toFlickerComponent()) .coversExactly(topAppBounds) diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 6cf88297f3b2..42b101439664 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -209,12 +209,11 @@ public class BackAnimationControllerTest { createNavigationInfo(animationTarget, null, null, BackNavigationInfo.TYPE_RETURN_TO_HOME, null); - // Check that back start is dispatched. doMotionEvent(MotionEvent.ACTION_DOWN, 0); - verify(mIOnBackInvokedCallback).onBackStarted(); - // Check that back progress is dispatched. + // Check that back start and progress is dispatched when first move. doMotionEvent(MotionEvent.ACTION_MOVE, 100); + verify(mIOnBackInvokedCallback).onBackStarted(); ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class); verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture()); assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget()); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 169f03e7bc3e..bde94d9d6c29 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -115,7 +115,7 @@ public class BubbleDataTest extends ShellTestCase { private ArgumentCaptor<BubbleData.Update> mUpdateCaptor; @Mock - private Bubbles.SuppressionChangedListener mSuppressionListener; + private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener; @Mock private Bubbles.PendingIntentCanceledListener mPendingIntentCanceledListener; @@ -136,30 +136,47 @@ public class BubbleDataTest extends ShellTestCase { mock(NotificationListenerService.Ranking.class); when(ranking.isTextChanged()).thenReturn(true); mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking); - mBubbleInterruptive = new Bubble(mEntryInterruptive, mSuppressionListener, null, + mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null, mMainExecutor); mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null); - mBubbleDismissed = new Bubble(mEntryDismissed, mSuppressionListener, null, + mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null, mMainExecutor); mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null, new LocusId("locusId1")); - mBubbleLocusId = new Bubble(mEntryLocusId, mSuppressionListener, null, mMainExecutor); + mBubbleLocusId = new Bubble(mEntryLocusId, + mBubbleMetadataFlagListener, + null /* pendingIntentCanceledListener */, + mMainExecutor); - mBubbleA1 = new Bubble(mEntryA1, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleA1 = new Bubble(mEntryA1, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleA2 = new Bubble(mEntryA2, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleA2 = new Bubble(mEntryA2, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleA3 = new Bubble(mEntryA3, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleA3 = new Bubble(mEntryA3, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleB1 = new Bubble(mEntryB1, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleB1 = new Bubble(mEntryB1, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleB2 = new Bubble(mEntryB2, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleB2 = new Bubble(mEntryB2, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleB3 = new Bubble(mEntryB3, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleB3 = new Bubble(mEntryB3, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); - mBubbleC1 = new Bubble(mEntryC1, mSuppressionListener, mPendingIntentCanceledListener, + mBubbleC1 = new Bubble(mEntryC1, + mBubbleMetadataFlagListener, + mPendingIntentCanceledListener, mMainExecutor); mPositioner = new TestableBubblePositioner(mContext, mock(WindowManager.class)); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java index 819a984b4a77..e8f3f69ca64e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java @@ -63,7 +63,7 @@ public class BubbleTest extends ShellTestCase { private Bubble mBubble; @Mock - private Bubbles.SuppressionChangedListener mSuppressionListener; + private Bubbles.BubbleMetadataFlagListener mBubbleMetadataFlagListener; @Before public void setUp() { @@ -81,7 +81,7 @@ public class BubbleTest extends ShellTestCase { when(mNotif.getBubbleMetadata()).thenReturn(metadata); when(mSbn.getKey()).thenReturn("mock"); mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false); - mBubble = new Bubble(mBubbleEntry, mSuppressionListener, null, mMainExecutor); + mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor); } @Test @@ -144,22 +144,22 @@ public class BubbleTest extends ShellTestCase { } @Test - public void testSuppressionListener_change_notified() { + public void testBubbleMetadataFlagListener_change_notified() { assertThat(mBubble.showInShade()).isTrue(); mBubble.setSuppressNotification(true); assertThat(mBubble.showInShade()).isFalse(); - verify(mSuppressionListener).onBubbleNotificationSuppressionChange(mBubble); + verify(mBubbleMetadataFlagListener).onBubbleMetadataFlagChanged(mBubble); } @Test - public void testSuppressionListener_noChange_doesntNotify() { + public void testBubbleMetadataFlagListener_noChange_doesntNotify() { assertThat(mBubble.showInShade()).isTrue(); mBubble.setSuppressNotification(false); - verify(mSuppressionListener, never()).onBubbleNotificationSuppressionChange(any()); + verify(mBubbleMetadataFlagListener, never()).onBubbleMetadataFlagChanged(any()); } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java index 5368b7db3dc1..df18133adcfb 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java @@ -45,6 +45,7 @@ import com.android.wm.shell.common.DisplayLayout; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TaskStackListenerImpl; import com.android.wm.shell.onehanded.OneHandedController; +import com.android.wm.shell.pip.PipAppOpsListener; import com.android.wm.shell.pip.PipBoundsAlgorithm; import com.android.wm.shell.pip.PipBoundsState; import com.android.wm.shell.pip.PipMediaController; @@ -75,7 +76,6 @@ public class PipControllerTest extends ShellTestCase { @Mock private PhonePipMenuController mMockPhonePipMenuController; @Mock private PipAppOpsListener mMockPipAppOpsListener; @Mock private PipBoundsAlgorithm mMockPipBoundsAlgorithm; - @Mock private PipKeepClearAlgorithm mMockPipKeepClearAlgorithm; @Mock private PipSnapAlgorithm mMockPipSnapAlgorithm; @Mock private PipMediaController mMockPipMediaController; @Mock private PipTaskOrganizer mMockPipTaskOrganizer; @@ -100,12 +100,12 @@ public class PipControllerTest extends ShellTestCase { return null; }).when(mMockExecutor).execute(any()); mPipController = new PipController(mContext, mMockDisplayController, - mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, - mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, + mMockPipAppOpsListener, mMockPipBoundsAlgorithm, + mMockPipBoundsState, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, - mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController, - mMockExecutor); + mMockTaskStackListener, mPipParamsChangedForwarder, + mMockOneHandedController, mMockExecutor); when(mMockPipBoundsAlgorithm.getSnapAlgorithm()).thenReturn(mMockPipSnapAlgorithm); when(mMockPipTouchHandler.getMotionHelper()).thenReturn(mMockPipMotionHelper); } @@ -133,12 +133,12 @@ public class PipControllerTest extends ShellTestCase { when(spyContext.getPackageManager()).thenReturn(mockPackageManager); assertNull(PipController.create(spyContext, mMockDisplayController, - mMockPipAppOpsListener, mMockPipBoundsAlgorithm, mMockPipKeepClearAlgorithm, - mMockPipBoundsState, mMockPipMotionHelper, mMockPipMediaController, + mMockPipAppOpsListener, mMockPipBoundsAlgorithm, + mMockPipBoundsState, mMockPipMediaController, mMockPhonePipMenuController, mMockPipTaskOrganizer, mMockPipTouchHandler, mMockPipTransitionController, mMockWindowManagerShellWrapper, - mMockTaskStackListener, mPipParamsChangedForwarder, mMockOneHandedController, - mMockExecutor)); + mMockTaskStackListener, mPipParamsChangedForwarder, + mMockOneHandedController, mMockExecutor)); } @Test diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java deleted file mode 100644 index f657b5e62d82..000000000000 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipKeepClearAlgorithmTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * 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.wm.shell.pip.phone; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; - -import android.graphics.Rect; -import android.testing.AndroidTestingRunner; -import android.testing.TestableLooper; - -import androidx.test.filters.SmallTest; - -import com.android.wm.shell.ShellTestCase; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.util.Set; - -/** - * Unit tests against {@link PipKeepClearAlgorithm}. - */ -@RunWith(AndroidTestingRunner.class) -@SmallTest -@TestableLooper.RunWithLooper(setAsMainLooper = true) -public class PipKeepClearAlgorithmTest extends ShellTestCase { - - private PipKeepClearAlgorithm mPipKeepClearAlgorithm; - - - @Before - public void setUp() throws Exception { - mPipKeepClearAlgorithm = new PipKeepClearAlgorithm(); - } - - @Test - public void adjust_withCollidingRestrictedKeepClearAreas_movesBounds() { - final Rect inBounds = new Rect(0, 0, 100, 100); - final Rect keepClearRect = new Rect(50, 50, 150, 150); - - final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect), - Set.of()); - - assertFalse(outBounds.contains(keepClearRect)); - } - - @Test - public void adjust_withNonCollidingRestrictedKeepClearAreas_boundsDoNotChange() { - final Rect inBounds = new Rect(0, 0, 100, 100); - final Rect keepClearRect = new Rect(100, 100, 150, 150); - - final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(keepClearRect), - Set.of()); - - assertEquals(inBounds, outBounds); - } - - @Test - public void adjust_withCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() { - // TODO(b/183746978): update this test to accommodate for the updated algorithm - final Rect inBounds = new Rect(0, 0, 100, 100); - final Rect keepClearRect = new Rect(50, 50, 150, 150); - - final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(), - Set.of(keepClearRect)); - - assertEquals(inBounds, outBounds); - } - - @Test - public void adjust_withNonCollidingUnrestrictedKeepClearAreas_boundsDoNotChange() { - final Rect inBounds = new Rect(0, 0, 100, 100); - final Rect keepClearRect = new Rect(100, 100, 150, 150); - - final Rect outBounds = mPipKeepClearAlgorithm.adjust(inBounds, Set.of(), - Set.of(keepClearRect)); - - assertEquals(inBounds, outBounds); - } -} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java index a55f737f2f25..ffaab652aa99 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java @@ -139,6 +139,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test + @UiThreadTest public void testLaunchToSide() { ActivityManager.RunningTaskInfo newTask = new TestRunningTaskInfoBuilder() .setParentTaskId(mSideStage.mRootTaskInfo.taskId).build(); @@ -173,6 +174,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test + @UiThreadTest public void testLaunchPair() { TransitionInfo info = createEnterPairInfo(); @@ -195,6 +197,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test + @UiThreadTest public void testMonitorInSplit() { enterSplit(); @@ -251,6 +254,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test + @UiThreadTest public void testEnterRecents() { enterSplit(); @@ -288,6 +292,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test + @UiThreadTest public void testDismissFromBeingOccluded() { enterSplit(); @@ -325,6 +330,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test + @UiThreadTest public void testDismissFromMultiWindowSupport() { enterSplit(); @@ -346,6 +352,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test + @UiThreadTest public void testDismissSnap() { enterSplit(); @@ -370,6 +377,7 @@ public class SplitTransitionTests extends ShellTestCase { } @Test + @UiThreadTest public void testDismissFromAppFinish() { enterSplit(); diff --git a/libs/hwui/pipeline/skia/ShaderCache.cpp b/libs/hwui/pipeline/skia/ShaderCache.cpp index e7432ac5f216..90c4440c8339 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.cpp +++ b/libs/hwui/pipeline/skia/ShaderCache.cpp @@ -136,24 +136,59 @@ sk_sp<SkData> ShaderCache::load(const SkData& key) { free(valueBuffer); return nullptr; } + mNumShadersCachedInRam++; + ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam); return SkData::MakeFromMalloc(valueBuffer, valueSize); } +namespace { +// Helper for BlobCache::set to trace the result. +void set(BlobCache* cache, const void* key, size_t keySize, const void* value, size_t valueSize) { + switch (cache->set(key, keySize, value, valueSize)) { + case BlobCache::InsertResult::kInserted: + // This is what we expect/hope. It means the cache is large enough. + return; + case BlobCache::InsertResult::kDidClean: { + ATRACE_FORMAT("ShaderCache: evicted an entry to fit {key: %lu value %lu}!", keySize, + valueSize); + return; + } + case BlobCache::InsertResult::kNotEnoughSpace: { + ATRACE_FORMAT("ShaderCache: could not fit {key: %lu value %lu}!", keySize, valueSize); + return; + } + case BlobCache::InsertResult::kInvalidValueSize: + case BlobCache::InsertResult::kInvalidKeySize: { + ATRACE_FORMAT("ShaderCache: invalid size {key: %lu value %lu}!", keySize, valueSize); + return; + } + case BlobCache::InsertResult::kKeyTooBig: + case BlobCache::InsertResult::kValueTooBig: + case BlobCache::InsertResult::kCombinedTooBig: { + ATRACE_FORMAT("ShaderCache: entry too big: {key: %lu value %lu}!", keySize, valueSize); + return; + } + } +} +} // namespace + void ShaderCache::saveToDiskLocked() { ATRACE_NAME("ShaderCache::saveToDiskLocked"); if (mInitialized && mBlobCache && mSavePending) { if (mIDHash.size()) { auto key = sIDKey; - mBlobCache->set(&key, sizeof(key), mIDHash.data(), mIDHash.size()); + set(mBlobCache.get(), &key, sizeof(key), mIDHash.data(), mIDHash.size()); } mBlobCache->writeToFile(); } mSavePending = false; } -void ShaderCache::store(const SkData& key, const SkData& data) { +void ShaderCache::store(const SkData& key, const SkData& data, const SkString& /*description*/) { ATRACE_NAME("ShaderCache::store"); std::lock_guard<std::mutex> lock(mMutex); + mNumShadersCachedInRam++; + ATRACE_FORMAT("HWUI RAM cache: %d shaders", mNumShadersCachedInRam); if (!mInitialized) { return; @@ -187,7 +222,7 @@ void ShaderCache::store(const SkData& key, const SkData& data) { mNewPipelineCacheSize = -1; mTryToStorePipelineCache = true; } - bc->set(key.data(), keySize, value, valueSize); + set(bc, key.data(), keySize, value, valueSize); if (!mSavePending && mDeferredSaveDelay > 0) { mSavePending = true; diff --git a/libs/hwui/pipeline/skia/ShaderCache.h b/libs/hwui/pipeline/skia/ShaderCache.h index 4dcc9fb49802..3e0fd5164011 100644 --- a/libs/hwui/pipeline/skia/ShaderCache.h +++ b/libs/hwui/pipeline/skia/ShaderCache.h @@ -73,7 +73,7 @@ public: * "store" attempts to insert a new key/value blob pair into the cache. * This will be called by Skia after it compiled a new SKSL shader */ - void store(const SkData& key, const SkData& data) override; + void store(const SkData& key, const SkData& data, const SkString& description) override; /** * "onVkFrameFlushed" tries to store Vulkan pipeline cache state. @@ -210,6 +210,13 @@ private: */ static constexpr uint8_t sIDKey = 0; + /** + * Most of this class concerns persistent storage for shaders, but it's also + * interesting to keep track of how many shaders are stored in RAM. This + * class provides a convenient entry point for that. + */ + int mNumShadersCachedInRam = 0; + friend class ShaderCacheTestUtils; // used for unit testing }; diff --git a/libs/hwui/tests/unit/ShaderCacheTests.cpp b/libs/hwui/tests/unit/ShaderCacheTests.cpp index 87981f115763..974d85a453db 100644 --- a/libs/hwui/tests/unit/ShaderCacheTests.cpp +++ b/libs/hwui/tests/unit/ShaderCacheTests.cpp @@ -140,9 +140,9 @@ TEST(ShaderCacheTest, testWriteAndRead) { // write to the in-memory cache without storing on disk and verify we read the same values sk_sp<SkData> inVS; setShader(inVS, "sassas"); - ShaderCache::get().store(GrProgramDescTest(100), *inVS.get()); + ShaderCache::get().store(GrProgramDescTest(100), *inVS.get(), SkString()); setShader(inVS, "someVS"); - ShaderCache::get().store(GrProgramDescTest(432), *inVS.get()); + ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString()); ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(100))), sk_sp<SkData>()); ASSERT_TRUE(checkShader(outVS, "sassas")); ASSERT_NE((outVS = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); @@ -166,7 +166,7 @@ TEST(ShaderCacheTest, testWriteAndRead) { // change data, store to disk, read back again and verify data has been changed setShader(inVS, "ewData1"); - ShaderCache::get().store(GrProgramDescTest(432), *inVS.get()); + ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString()); ShaderCacheTestUtils::terminate(ShaderCache::get(), true); ShaderCache::get().initShaderDiskCache(); ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); @@ -177,7 +177,7 @@ TEST(ShaderCacheTest, testWriteAndRead) { std::vector<uint8_t> dataBuffer(dataSize); genRandomData(dataBuffer); setShader(inVS, dataBuffer); - ShaderCache::get().store(GrProgramDescTest(432), *inVS.get()); + ShaderCache::get().store(GrProgramDescTest(432), *inVS.get(), SkString()); ShaderCacheTestUtils::terminate(ShaderCache::get(), true); ShaderCache::get().initShaderDiskCache(); ASSERT_NE((outVS2 = ShaderCache::get().load(GrProgramDescTest(432))), sk_sp<SkData>()); @@ -225,7 +225,7 @@ TEST(ShaderCacheTest, testCacheValidation) { setShader(data, dataBuffer); blob = std::make_pair(key, data); - ShaderCache::get().store(*key.get(), *data.get()); + ShaderCache::get().store(*key.get(), *data.get(), SkString()); } ShaderCacheTestUtils::terminate(ShaderCache::get(), true); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 1dc74e5f7740..10ea6512c724 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -106,6 +106,7 @@ PointerController::PointerController(const sp<PointerControllerPolicyInterface>& PointerController::~PointerController() { mDisplayInfoListener->onPointerControllerDestroyed(); mUnregisterWindowInfosListener(mDisplayInfoListener); + mContext.getPolicy()->onPointerDisplayIdChanged(ADISPLAY_ID_NONE, 0, 0); } std::mutex& PointerController::getLock() const { @@ -255,6 +256,12 @@ void PointerController::setDisplayViewport(const DisplayViewport& viewport) { getAdditionalMouseResources = true; } mCursorController.setDisplayViewport(viewport, getAdditionalMouseResources); + if (viewport.displayId != mLocked.pointerDisplayId) { + float xPos, yPos; + mCursorController.getPosition(&xPos, &yPos); + mContext.getPolicy()->onPointerDisplayIdChanged(viewport.displayId, xPos, yPos); + mLocked.pointerDisplayId = viewport.displayId; + } } void PointerController::updatePointerIcon(int32_t iconId) { diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 2e6e851ee15a..eab030f71e1a 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -104,6 +104,7 @@ private: struct Locked { Presentation presentation; + int32_t pointerDisplayId = ADISPLAY_ID_NONE; std::vector<gui::DisplayInfo> mDisplayInfos; std::unordered_map<int32_t /* displayId */, TouchSpotController> spotControllers; diff --git a/libs/input/PointerControllerContext.h b/libs/input/PointerControllerContext.h index 26a65a47471d..c2bc1e020279 100644 --- a/libs/input/PointerControllerContext.h +++ b/libs/input/PointerControllerContext.h @@ -79,6 +79,7 @@ public: std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) = 0; virtual int32_t getDefaultPointerIconId() = 0; virtual int32_t getCustomPointerIconId() = 0; + virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) = 0; }; /* diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp index dae1fccec804..f9752ed155df 100644 --- a/libs/input/tests/PointerController_test.cpp +++ b/libs/input/tests/PointerController_test.cpp @@ -56,9 +56,11 @@ public: std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId) override; virtual int32_t getDefaultPointerIconId() override; virtual int32_t getCustomPointerIconId() override; + virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos) override; bool allResourcesAreLoaded(); bool noResourcesAreLoaded(); + std::optional<int32_t> getLastReportedPointerDisplayId() { return latestPointerDisplayId; } private: void loadPointerIconForType(SpriteIcon* icon, int32_t cursorType); @@ -66,6 +68,7 @@ private: bool pointerIconLoaded{false}; bool pointerResourcesLoaded{false}; bool additionalMouseResourcesLoaded{false}; + std::optional<int32_t /*displayId*/> latestPointerDisplayId; }; void MockPointerControllerPolicyInterface::loadPointerIcon(SpriteIcon* icon, int32_t) { @@ -126,12 +129,19 @@ void MockPointerControllerPolicyInterface::loadPointerIconForType(SpriteIcon* ic icon->hotSpotX = hotSpot.first; icon->hotSpotY = hotSpot.second; } + +void MockPointerControllerPolicyInterface::onPointerDisplayIdChanged(int32_t displayId, + float /*xPos*/, + float /*yPos*/) { + latestPointerDisplayId = displayId; +} + class PointerControllerTest : public Test { protected: PointerControllerTest(); ~PointerControllerTest(); - void ensureDisplayViewportIsSet(); + void ensureDisplayViewportIsSet(int32_t displayId = ADISPLAY_ID_DEFAULT); sp<MockSprite> mPointerSprite; sp<MockPointerControllerPolicyInterface> mPolicy; @@ -168,9 +178,9 @@ PointerControllerTest::~PointerControllerTest() { mThread.join(); } -void PointerControllerTest::ensureDisplayViewportIsSet() { +void PointerControllerTest::ensureDisplayViewportIsSet(int32_t displayId) { DisplayViewport viewport; - viewport.displayId = ADISPLAY_ID_DEFAULT; + viewport.displayId = displayId; viewport.logicalRight = 1600; viewport.logicalBottom = 1200; viewport.physicalRight = 800; @@ -255,6 +265,30 @@ TEST_F(PointerControllerTest, doesNotGetResourcesBeforeSettingViewport) { ensureDisplayViewportIsSet(); } +TEST_F(PointerControllerTest, notifiesPolicyWhenPointerDisplayChanges) { + EXPECT_FALSE(mPolicy->getLastReportedPointerDisplayId()) + << "A pointer display change does not occur when PointerController is created."; + + ensureDisplayViewportIsSet(ADISPLAY_ID_DEFAULT); + + const auto lastReportedPointerDisplayId = mPolicy->getLastReportedPointerDisplayId(); + ASSERT_TRUE(lastReportedPointerDisplayId) + << "The policy is notified of a pointer display change when the viewport is first set."; + EXPECT_EQ(ADISPLAY_ID_DEFAULT, *lastReportedPointerDisplayId) + << "Incorrect pointer display notified."; + + ensureDisplayViewportIsSet(42); + + EXPECT_EQ(42, *mPolicy->getLastReportedPointerDisplayId()) + << "The policy is notified when the pointer display changes."; + + // Release the PointerController. + mPointerController = nullptr; + + EXPECT_EQ(ADISPLAY_ID_NONE, *mPolicy->getLastReportedPointerDisplayId()) + << "The pointer display changes to invalid when PointerController is destroyed."; +} + class PointerControllerWindowInfoListenerTest : public Test {}; class TestPointerController : public PointerController { diff --git a/media/java/android/media/ImageReader.java b/media/java/android/media/ImageReader.java index 70d6810d01ec..472586b5e519 100644 --- a/media/java/android/media/ImageReader.java +++ b/media/java/android/media/ImageReader.java @@ -643,6 +643,9 @@ public class ImageReader implements AutoCloseable { /** * <p>Return the frame to the ImageReader for reuse.</p> + * + * This method should only be called via {@link SurfaceImage#close} which ensures that image + * closing is atomic. */ private void releaseImage(Image i) { if (! (i instanceof SurfaceImage) ) { @@ -1125,6 +1128,8 @@ public class ImageReader implements AutoCloseable { } private class SurfaceImage extends android.media.Image { + private final Object mCloseLock = new Object(); + public SurfaceImage(int format) { mFormat = format; mHardwareBufferFormat = ImageReader.this.mHardwareBufferFormat; @@ -1139,7 +1144,9 @@ public class ImageReader implements AutoCloseable { @Override public void close() { - ImageReader.this.releaseImage(this); + synchronized (this.mCloseLock) { + ImageReader.this.releaseImage(this); + } } public ImageReader getReader() { diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 37cbf3017f0b..5e918641e49f 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -484,6 +484,14 @@ public class CompanionDeviceActivity extends FragmentActivity implements if (deviceFilterPairs.isEmpty()) return; mSelectedDevice = requireNonNull(deviceFilterPairs.get(0)); + // No need to show user consent dialog if it is a singleDevice + // and isSkipPrompt(true) AssociationRequest. + // See AssociationRequestsProcessor#mayAssociateWithoutPrompt. + if (mRequest.isSkipPrompt()) { + mSingleDeviceSpinner.setVisibility(View.GONE); + onUserSelectedDevice(mSelectedDevice); + return; + } final String deviceName = mSelectedDevice.getDisplayName(); final Spanned title = getHtmlFromResources( diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java index e8a1a5cc1916..f333b86d4d52 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceDiscoveryService.java @@ -150,8 +150,6 @@ public class CompanionDeviceDiscoveryService extends Service { mBtAdapter = mBtManager.getAdapter(); mBleScanner = mBtAdapter.getBluetoothLeScanner(); mWifiManager = getSystemService(WifiManager.class); - - sScanResultsLiveData.setValue(Collections.emptyList()); } @Override @@ -175,6 +173,7 @@ public class CompanionDeviceDiscoveryService extends Service { @Override public void onDestroy() { + sScanResultsLiveData.setValue(Collections.emptyList()); super.onDestroy(); if (DEBUG) Log.d(TAG, "onDestroy()"); } @@ -188,6 +187,7 @@ public class CompanionDeviceDiscoveryService extends Service { mStopAfterFirstMatch = request.isSingleDevice(); mDiscoveryStarted = true; sStateLiveData.setValue(DiscoveryState.DISCOVERY_IN_PROGRESS); + sScanResultsLiveData.setValue(Collections.emptyList()); final List<DeviceFilter<?>> allFilters = request.getDeviceFilters(); final List<BluetoothDeviceFilter> btFilters = diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml index 25f0771b2170..72b569f22d6c 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/collapsing_toolbar_content_layout.xml @@ -25,7 +25,7 @@ android:fitsSystemWindows="true" android:outlineAmbientShadowColor="@android:color/transparent" android:outlineSpotShadowColor="@android:color/transparent" - android:background="?android:attr/colorPrimary" + android:background="@android:color/transparent" android:theme="@style/Theme.CollapsingToolbar.Settings"> <com.google.android.material.appbar.CollapsingToolbarLayout diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java index 72383fe59e7e..dbb4b5017e6b 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/src/com/android/settingslib/collapsingtoolbar/widget/CollapsingCoordinatorLayout.java @@ -20,6 +20,7 @@ import android.app.ActionBar; import android.app.Activity; import android.content.Context; import android.content.res.TypedArray; +import android.os.Build; import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; @@ -29,6 +30,7 @@ import android.widget.Toolbar; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; import androidx.coordinatorlayout.widget.CoordinatorLayout; @@ -41,6 +43,7 @@ import com.google.android.material.appbar.CollapsingToolbarLayout; * This widget is wrapping the collapsing toolbar and can be directly used by the * {@link AppCompatActivity}. */ +@RequiresApi(Build.VERSION_CODES.S) public class CollapsingCoordinatorLayout extends CoordinatorLayout { private static final String TAG = "CollapsingCoordinatorLayout"; private static final float TOOLBAR_LINE_SPACING_MULTIPLIER = 1.1f; diff --git a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java index ac306361386e..6766cdd0beb6 100644 --- a/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java +++ b/packages/SettingsLib/FooterPreference/src/com/android/settingslib/widget/FooterPreference.java @@ -40,6 +40,8 @@ public class FooterPreference extends Preference { static final int ORDER_FOOTER = Integer.MAX_VALUE - 1; @VisibleForTesting View.OnClickListener mLearnMoreListener; + @VisibleForTesting + int mIconVisibility = View.VISIBLE; private CharSequence mContentDescription; private CharSequence mLearnMoreText; private CharSequence mLearnMoreContentDescription; @@ -84,6 +86,9 @@ public class FooterPreference extends Preference { } else { learnMore.setVisibility(View.GONE); } + + View icon = holder.itemView.findViewById(R.id.icon_frame); + icon.setVisibility(mIconVisibility); } @Override @@ -165,6 +170,17 @@ public class FooterPreference extends Preference { } } + /** + * Set visibility of footer icon. + */ + public void setIconVisibility(int iconVisibility) { + if (mIconVisibility == iconVisibility) { + return; + } + mIconVisibility = iconVisibility; + notifyChanged(); + } + private void init() { setLayoutResource(R.layout.preference_footer); if (getIcon() == null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java index 284da73efb6f..2f30baa79b9d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java @@ -234,5 +234,10 @@ public class RestrictedPreferenceHelper { if (!(mPreference instanceof RestrictedTopLevelPreference)) { mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); } + + if (mPreference instanceof PrimarySwitchPreference) { + ((PrimarySwitchPreference) mPreference) + .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps)); + } } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java index bdeb4740d305..e6160bb9896d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java @@ -16,10 +16,14 @@ package com.android.settingslib; +import static android.app.admin.DevicePolicyResources.Strings.Settings.DISABLED_BY_ADMIN_SWITCH_SUMMARY; +import static android.app.admin.DevicePolicyResources.Strings.Settings.ENABLED_BY_ADMIN_SWITCH_SUMMARY; + import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; import android.annotation.NonNull; import android.app.AppOpsManager; +import android.app.admin.DevicePolicyManager; import android.content.Context; import android.content.res.TypedArray; import android.os.Process; @@ -37,6 +41,8 @@ import androidx.preference.PreferenceManager; import androidx.preference.PreferenceViewHolder; import androidx.preference.SwitchPreference; +import com.android.settingslib.utils.BuildCompatUtils; + /** * Version of SwitchPreference that can be disabled by a device admin * using a user restriction. @@ -117,8 +123,13 @@ public class RestrictedSwitchPreference extends SwitchPreference { CharSequence switchSummary; if (mRestrictedSwitchSummary == null) { - switchSummary = getContext().getText(isChecked() - ? R.string.enabled_by_admin : R.string.disabled_by_admin); + switchSummary = isChecked() + ? getUpdatableEnterpriseString( + getContext(), ENABLED_BY_ADMIN_SWITCH_SUMMARY, + R.string.enabled_by_admin) + : getUpdatableEnterpriseString( + getContext(), DISABLED_BY_ADMIN_SWITCH_SUMMARY, + R.string.disabled_by_admin); } else { switchSummary = mRestrictedSwitchSummary; } @@ -153,6 +164,16 @@ public class RestrictedSwitchPreference extends SwitchPreference { } } + private static String getUpdatableEnterpriseString( + Context context, String updatableStringId, int resId) { + if (!BuildCompatUtils.isAtLeastT()) { + return context.getString(resId); + } + return context.getSystemService(DevicePolicyManager.class).getResources().getString( + updatableStringId, + () -> context.getString(resId)); + } + @Override public void performClick() { if (!mHelper.performClick()) { diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java index fdb06072bbd1..6b9daa35f9ca 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/ApplicationsState.java @@ -863,6 +863,30 @@ public class ApplicationsState { } } + /** + * Activate session to enable a class that implements Callbacks to receive the callback. + */ + public void activateSession() { + synchronized (mEntriesMap) { + if (!mResumed) { + mResumed = true; + mSessionsChanged = true; + } + } + } + + /** + * Deactivate session to disable a class that implements Callbacks to get the callback. + */ + public void deactivateSession() { + synchronized (mEntriesMap) { + if (mResumed) { + mResumed = false; + mSessionsChanged = true; + } + } + } + public ArrayList<AppEntry> getAllApps() { synchronized (mEntriesMap) { return new ArrayList<>(mAppEntries); diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java index 4ee21229e364..6919cf237853 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java @@ -1376,7 +1376,7 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> /** * Store the member devices that are in the same coordinated set. */ - public void setMemberDevice(CachedBluetoothDevice memberDevice) { + public void addMemberDevice(CachedBluetoothDevice memberDevice) { mMemberDevices.add(memberDevice); } @@ -1393,24 +1393,24 @@ public class CachedBluetoothDevice implements Comparable<CachedBluetoothDevice> * device and member devices. * * @param prevMainDevice the previous Main device, it will be added into the member device set. - * @param newMainDevie the new Main device, it will be removed from the member device set. + * @param newMainDevice the new Main device, it will be removed from the member device set. */ public void switchMemberDeviceContent(CachedBluetoothDevice prevMainDevice, - CachedBluetoothDevice newMainDevie) { + CachedBluetoothDevice newMainDevice) { // Backup from main device final BluetoothDevice tmpDevice = mDevice; final short tmpRssi = mRssi; final boolean tmpJustDiscovered = mJustDiscovered; // Set main device from sub device - mDevice = newMainDevie.mDevice; - mRssi = newMainDevie.mRssi; - mJustDiscovered = newMainDevie.mJustDiscovered; - setMemberDevice(prevMainDevice); - mMemberDevices.remove(newMainDevie); + mDevice = newMainDevice.mDevice; + mRssi = newMainDevice.mRssi; + mJustDiscovered = newMainDevice.mJustDiscovered; + addMemberDevice(prevMainDevice); + mMemberDevices.remove(newMainDevice); // Set sub device from backup - newMainDevie.mDevice = tmpDevice; - newMainDevie.mRssi = tmpRssi; - newMainDevie.mJustDiscovered = tmpJustDiscovered; + newMainDevice.mDevice = tmpDevice; + newMainDevice.mRssi = tmpRssi; + newMainDevice.mJustDiscovered = tmpJustDiscovered; fetchActiveDevices(); } diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java index cc56a212aea1..89e10c4b5e11 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CsipDeviceManager.java @@ -85,7 +85,7 @@ public class CsipDeviceManager { // Once there is other devices with the same groupId, to add new device as member // devices. if (CsipDevice != null) { - CsipDevice.setMemberDevice(newDevice); + CsipDevice.addMemberDevice(newDevice); newDevice.setName(CsipDevice.getName()); return true; } @@ -148,7 +148,7 @@ public class CsipDeviceManager { log("onGroupIdChanged: removed from UI device =" + cachedDevice + ", with groupId=" + groupId + " firstMatchedIndex=" + firstMatchedIndex); - mainDevice.setMemberDevice(cachedDevice); + mainDevice.addMemberDevice(cachedDevice); mCachedDevices.remove(i); mBtManager.getEventManager().dispatchDeviceRemoved(cachedDevice); break; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java index c248fff09d6f..b0392be9d336 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java @@ -26,6 +26,7 @@ import android.bluetooth.BluetoothClass; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothLeBroadcastAssistant; import android.bluetooth.BluetoothLeBroadcastMetadata; +import android.bluetooth.BluetoothLeBroadcastReceiveState; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothProfile.ServiceListener; import android.content.Context; @@ -202,6 +203,43 @@ public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile mService.startSearchingForSources(filters); } + /** + * Return true if a search has been started by this application. + * + * @return true if a search has been started by this application + * @hide + */ + public boolean isSearchInProgress() { + if (DEBUG) { + Log.d(TAG, "isSearchInProgress()"); + } + if (mService == null) { + Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); + return false; + } + return mService.isSearchInProgress(); + } + + /** + * Get information about all Broadcast Sources that a Broadcast Sink knows about. + * + * @param sink Broadcast Sink from which to get all Broadcast Sources + * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} + * stored in the Broadcast Sink + * @throws NullPointerException when <var>sink</var> is null + */ + public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources( + @NonNull BluetoothDevice sink) { + if (DEBUG) { + Log.d(TAG, "getAllSources()"); + } + if (mService == null) { + Log.d(TAG, "The BluetoothLeBroadcastAssistant is null"); + return new ArrayList<BluetoothLeBroadcastReceiveState>(); + } + return mService.getAllSources(sink); + } + public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor, @NonNull BluetoothLeBroadcastAssistant.Callback callback) { if (mService == null) { diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java index 5b1fefb249a3..aff9a6e3c08d 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java @@ -25,6 +25,7 @@ import android.bluetooth.BluetoothLeBroadcastMetadata; import android.bluetooth.BluetoothLeBroadcastSubgroup; import android.util.Log; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -95,6 +96,7 @@ public class LocalBluetoothLeBroadcastMetadata { } public String convertToQrCodeString() { + String subgroupString = convertSubgroupToString(mSubgroupList); return new StringBuilder() .append(BluetoothBroadcastUtils.SCHEME_BT_BROADCAST_METADATA) .append(BluetoothBroadcastUtils.PREFIX_BT_ADDRESS_TYPE) @@ -122,11 +124,83 @@ public class LocalBluetoothLeBroadcastMetadata { .append(METADATA_START).append(mPresentationDelayMicros).append(METADATA_END) .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) .append(BluetoothBroadcastUtils.PREFIX_BT_SUBGROUPS) - .append(METADATA_START).append(mSubgroupList).append(METADATA_END) + .append(METADATA_START).append(subgroupString).append(METADATA_END) .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) .toString(); } + private String convertSubgroupToString(List<BluetoothLeBroadcastSubgroup> subgroupList) { + StringBuilder subgroupListBuilder = new StringBuilder(); + String subgroupString = ""; + for (BluetoothLeBroadcastSubgroup subgroup: subgroupList) { + String audioCodec = convertAudioCodecConfigToString(subgroup.getCodecSpecificConfig()); + String audioContent = convertAudioContentToString(subgroup.getContentMetadata()); + String channels = convertChannelToString(subgroup.getChannels()); + subgroupString = new StringBuilder() + .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_ID) + .append(METADATA_START).append(subgroup.getCodecId()).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BTSG_CODEC_CONFIG) + .append(METADATA_START).append(audioCodec).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BTSG_AUDIO_CONTENT) + .append(METADATA_START).append(audioContent).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BTSG_BROADCAST_CHANNEL) + .append(METADATA_START).append(channels).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .toString(); + subgroupListBuilder.append(subgroupString); + } + return subgroupListBuilder.toString(); + } + + private String convertAudioCodecConfigToString(BluetoothLeAudioCodecConfigMetadata config) { + String audioLocation = String.valueOf(config.getAudioLocation()); + String rawMetadata = new String(config.getRawMetadata(), StandardCharsets.UTF_8); + return new StringBuilder() + .append(BluetoothBroadcastUtils.PREFIX_BTCC_AUDIO_LOCATION) + .append(METADATA_START).append(audioLocation).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BTCC_RAW_METADATA) + .append(METADATA_START).append(rawMetadata).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .toString(); + } + + private String convertAudioContentToString(BluetoothLeAudioContentMetadata audioContent) { + String rawMetadata = new String(audioContent.getRawMetadata(), StandardCharsets.UTF_8); + return new StringBuilder() + .append(BluetoothBroadcastUtils.PREFIX_BTAC_PROGRAM_INFO) + .append(METADATA_START).append(audioContent.getProgramInfo()).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BTAC_LANGUAGE) + .append(METADATA_START).append(audioContent.getLanguage()).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BTAC_RAW_METADATA) + .append(METADATA_START).append(rawMetadata).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .toString(); + } + + private String convertChannelToString(List<BluetoothLeBroadcastChannel> channelList) { + StringBuilder channelListBuilder = new StringBuilder(); + String channelString = ""; + for (BluetoothLeBroadcastChannel channel: channelList) { + String channelAudioCodec = convertAudioCodecConfigToString(channel.getCodecMetadata()); + channelString = new StringBuilder() + .append(BluetoothBroadcastUtils.PREFIX_BTBC_CHANNEL_INDEX) + .append(METADATA_START).append(channel.getChannelIndex()).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .append(BluetoothBroadcastUtils.PREFIX_BTBC_CODEC_CONFIG) + .append(METADATA_START).append(channelAudioCodec).append(METADATA_END) + .append(BluetoothBroadcastUtils.DELIMITER_QR_CODE) + .toString(); + channelListBuilder.append(channelString); + } + return channelListBuilder.toString(); + } + /** * Example : prefix is with the “BT:”, and end by the Android Version. * BT:T:<1>;D:<00:11:22:AA:BB:CC>;AS:<1>;B:…;V:T;; diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java index 298ee90d311d..bef1d9cbcde1 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManagerTest.java @@ -40,7 +40,6 @@ import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import java.util.Collection; -import java.util.HashMap; import java.util.Map; @RunWith(RobolectricTestRunner.class) @@ -503,8 +502,8 @@ public class CachedBluetoothDeviceManagerTest { CachedBluetoothDevice cachedDevice1 = mCachedDeviceManager.addDevice(mDevice1); CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); CachedBluetoothDevice cachedDevice3 = mCachedDeviceManager.addDevice(mDevice3); - cachedDevice1.setMemberDevice(cachedDevice2); - cachedDevice1.setMemberDevice(cachedDevice3); + cachedDevice1.addMemberDevice(cachedDevice2); + cachedDevice1.addMemberDevice(cachedDevice3); assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice2); assertThat(cachedDevice1.getMemberDevice()).contains(cachedDevice3); @@ -524,7 +523,7 @@ public class CachedBluetoothDeviceManagerTest { CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); cachedDevice1.setGroupId(1); cachedDevice2.setGroupId(1); - cachedDevice1.setMemberDevice(cachedDevice2); + cachedDevice1.addMemberDevice(cachedDevice2); // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice1); @@ -541,7 +540,7 @@ public class CachedBluetoothDeviceManagerTest { CachedBluetoothDevice cachedDevice2 = mCachedDeviceManager.addDevice(mDevice2); cachedDevice1.setGroupId(1); cachedDevice2.setGroupId(1); - cachedDevice1.setMemberDevice(cachedDevice2); + cachedDevice1.addMemberDevice(cachedDevice2); // Call onDeviceUnpaired for the one in mCachedDevices. mCachedDeviceManager.onDeviceUnpaired(cachedDevice2); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java index 61a28aab061f..9abb27e68398 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/FooterPreferenceTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; import android.view.LayoutInflater; +import android.view.View; import android.widget.TextView; import androidx.preference.PreferenceViewHolder; @@ -61,7 +62,7 @@ public class FooterPreferenceTest { mFooterPreference.onBindViewHolder(holder); assertThat(((TextView) holder.findViewById( - R.id.settingslib_learn_more)).getText().toString()) + R.id.settingslib_learn_more)).getText().toString()) .isEqualTo("Custom learn more"); } @@ -86,4 +87,11 @@ public class FooterPreferenceTest { assertThat(mFooterPreference.mLearnMoreListener).isNotNull(); } + + @Test + public void setIconVisibility_shouldReturnSameVisibilityType() { + mFooterPreference.setIconVisibility(View.GONE); + + assertThat(mFooterPreference.mIconVisibility).isEqualTo(View.GONE); + } } diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java index 3029781f3e99..5eaf553a2047 100644 --- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java +++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java @@ -120,6 +120,9 @@ public class SecureSettings { Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, + Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, Settings.Secure.VR_DISPLAY_MODE, Settings.Secure.NOTIFICATION_BADGING, Settings.Secure.NOTIFICATION_DISMISS_RTL, diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index a4da49713f87..9ee7b654046f 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -18,6 +18,7 @@ package android.provider.settings.validators; import static android.provider.settings.validators.SettingsValidators.ACCESSIBILITY_SHORTCUT_TARGET_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.ANY_INTEGER_VALIDATOR; +import static android.provider.settings.validators.SettingsValidators.ANY_STRING_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.BOOLEAN_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_COMPONENT_LIST_VALIDATOR; import static android.provider.settings.validators.SettingsValidators.COLON_SEPARATED_PACKAGE_LIST_VALIDATOR; @@ -176,6 +177,10 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_WAKE, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, ANY_STRING_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, ANY_STRING_VALIDATOR); + VALIDATORS.put(Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + ANY_STRING_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_SILENCE_ALERTS_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.ASSIST_GESTURE_WAKE_ENABLED, BOOLEAN_VALIDATOR); diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml index 96949995670f..c4baa52a6add 100644 --- a/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res-keyguard/values-sw600dp-land/dimens.xml @@ -22,6 +22,4 @@ <!-- Overload default clock widget parameters --> <dimen name="widget_big_font_size">88dp</dimen> - <dimen name="qs_panel_padding_top">16dp</dimen> - -</resources>
\ No newline at end of file +</resources> diff --git a/packages/SystemUI/res-product/values/strings.xml b/packages/SystemUI/res-product/values/strings.xml index c1e81bacbec5..b71caef3f593 100644 --- a/packages/SystemUI/res-product/values/strings.xml +++ b/packages/SystemUI/res-product/values/strings.xml @@ -130,4 +130,8 @@ <!-- Text shown when viewing global actions while phone is locked and additional controls are hidden [CHAR LIMIT=NONE] --> <string name="global_action_lock_message" product="device">Unlock your device for more options</string> + <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] --> + <string name="media_transfer_playing_this_device" product="default">Playing on this phone</string> + <!-- Text informing the user that their media is now playing on this tablet device. [CHAR LIMIT=50] --> + <string name="media_transfer_playing_this_device" product="tablet">Playing on this tablet</string> </resources> diff --git a/packages/SystemUI/res/layout/alert_dialog_systemui.xml b/packages/SystemUI/res/layout/alert_dialog_systemui.xml index 528f603c6a83..fd0623812745 100644 --- a/packages/SystemUI/res/layout/alert_dialog_systemui.xml +++ b/packages/SystemUI/res/layout/alert_dialog_systemui.xml @@ -1,7 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> - <!-- - ~ Copyright (C) 2021 The Android Open Source Project + ~ 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. @@ -15,88 +14,83 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<androidx.core.widget.NestedScrollView + +<com.android.internal.widget.AlertDialogLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@*android:id/parentPanel" android:layout_width="match_parent" - android:layout_height="wrap_content"> + android:layout_height="wrap_content" + android:gravity="center_horizontal|top" + android:orientation="vertical" + android:paddingTop="@dimen/dialog_top_padding" +> + + <include layout="@layout/alert_dialog_title_systemui" /> - <com.android.internal.widget.AlertDialogLayout - android:id="@*android:id/parentPanel" + <FrameLayout + android:id="@*android:id/contentPanel" android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_horizontal|top" - android:orientation="vertical" - android:paddingTop="@dimen/dialog_top_padding" - > + android:minHeight="48dp" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + > - <include layout="@layout/alert_dialog_title_systemui" /> - - <FrameLayout - android:id="@*android:id/contentPanel" + <ScrollView + android:id="@*android:id/scrollView" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="48dp" - android:paddingStart="@dimen/dialog_side_padding" - android:paddingEnd="@dimen/dialog_side_padding" - > + android:clipToPadding="false"> - <ScrollView - android:id="@*android:id/scrollView" + <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" - android:clipToPadding="false"> + android:orientation="vertical"> - <LinearLayout + <Space + android:id="@*android:id/textSpacerNoTitle" + android:visibility="gone" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:orientation="vertical"> + android:layout_height="0dp" /> - <Space - android:id="@*android:id/textSpacerNoTitle" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="0dp" /> + <TextView + android:id="@*android:id/message" + android:layout_width="match_parent" + android:layout_height="wrap_content" + style="@style/TextAppearance.Dialog.Body.Message" /> - <TextView - android:id="@*android:id/message" - android:layout_width="match_parent" - android:layout_height="wrap_content" - style="@style/TextAppearance.Dialog.Body.Message" /> + <Space + android:id="@*android:id/textSpacerNoButtons" + android:visibility="gone" + android:layout_width="match_parent" + android:layout_height="6dp" /> + </LinearLayout> + </ScrollView> + </FrameLayout> - <Space - android:id="@*android:id/textSpacerNoButtons" - android:visibility="gone" - android:layout_width="match_parent" - android:layout_height="6dp" /> - </LinearLayout> - </ScrollView> - </FrameLayout> + <FrameLayout + android:id="@*android:id/customPanel" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="48dp" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding" + > <FrameLayout - android:id="@*android:id/customPanel" + android:id="@*android:id/custom" android:layout_width="match_parent" - android:layout_height="wrap_content" - android:minHeight="48dp" - android:paddingStart="@dimen/dialog_side_padding" - android:paddingEnd="@dimen/dialog_side_padding" - > + android:layout_height="wrap_content" /> + </FrameLayout> - <FrameLayout - android:id="@*android:id/custom" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> - </FrameLayout> - - <FrameLayout + <FrameLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingStart="@dimen/dialog_side_padding" + android:paddingEnd="@dimen/dialog_side_padding"> + <include android:layout_width="match_parent" android:layout_height="wrap_content" - android:paddingStart="@dimen/dialog_side_padding" - android:paddingEnd="@dimen/dialog_side_padding"> - <include - android:layout_width="match_parent" - android:layout_height="wrap_content" - layout="@layout/alert_dialog_button_bar_systemui" /> - </FrameLayout> - </com.android.internal.widget.AlertDialogLayout> - -</androidx.core.widget.NestedScrollView>
\ No newline at end of file + layout="@layout/alert_dialog_button_bar_systemui" /> + </FrameLayout> +</com.android.internal.widget.AlertDialogLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/clipboard_overlay.xml b/packages/SystemUI/res/layout/clipboard_overlay.xml index b230438f66fd..10bb6cbb95aa 100644 --- a/packages/SystemUI/res/layout/clipboard_overlay.xml +++ b/packages/SystemUI/res/layout/clipboard_overlay.xml @@ -121,6 +121,30 @@ android:adjustViewBounds="true" android:layout_width="match_parent" android:layout_height="wrap_content"/> + <TextView + android:id="@+id/hidden_text_preview" + android:visibility="gone" + android:textFontWeight="500" + android:padding="8dp" + android:gravity="center" + android:textSize="14sp" + android:text="@string/clipboard_text_hidden" + android:textColor="?attr/overlayButtonTextColor" + android:background="?androidprv:attr/colorAccentSecondary" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_height="@dimen/clipboard_preview_size"/> + <TextView + android:id="@+id/hidden_image_preview" + android:visibility="gone" + android:textFontWeight="500" + android:padding="8dp" + android:gravity="center" + android:textSize="14sp" + android:text="@string/clipboard_text_hidden" + android:textColor="#ffffff" + android:background="#000000" + android:layout_width="@dimen/clipboard_preview_size" + android:layout_height="@dimen/clipboard_preview_size"/> </FrameLayout> <FrameLayout android:id="@+id/dismiss_button" @@ -141,4 +165,4 @@ android:layout_margin="@dimen/overlay_dismiss_button_margin" android:src="@drawable/overlay_cancel"/> </FrameLayout> -</com.android.systemui.screenshot.DraggableConstraintLayout> +</com.android.systemui.screenshot.DraggableConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/fgs_manager_app_item.xml b/packages/SystemUI/res/layout/fgs_manager_app_item.xml index 30bce9d84489..a6f659d049e4 100644 --- a/packages/SystemUI/res/layout/fgs_manager_app_item.xml +++ b/packages/SystemUI/res/layout/fgs_manager_app_item.xml @@ -50,7 +50,8 @@ <Button android:id="@+id/fgs_manager_app_item_stop_button" android:layout_width="wrap_content" - android:layout_height="48dp" + android:layout_height="wrap_content" + android:minHeight="48dp" android:text="@string/fgs_manager_app_item_stop_button_label" android:layout_marginStart="12dp" style="?android:attr/buttonBarNeutralButtonStyle" /> diff --git a/packages/SystemUI/res/layout/qs_paged_page.xml b/packages/SystemUI/res/layout/qs_paged_page.xml index 98804fc80b50..c366ceb4c190 100644 --- a/packages/SystemUI/res/layout/qs_paged_page.xml +++ b/packages/SystemUI/res/layout/qs_paged_page.xml @@ -19,7 +19,7 @@ android:id="@+id/tile_page" android:layout_width="match_parent" android:layout_height="match_parent" - android:paddingStart="@dimen/notification_side_paddings" - android:paddingEnd="@dimen/notification_side_paddings" + android:paddingStart="@dimen/qs_tiles_page_horizontal_margin" + android:paddingEnd="@dimen/qs_tiles_page_horizontal_margin" android:clipChildren="false" android:clipToPadding="false" /> diff --git a/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml b/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml new file mode 100644 index 000000000000..71bb938dfdf4 --- /dev/null +++ b/packages/SystemUI/res/layout/scrollable_alert_dialog_systemui.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + ~ 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. + --> +<androidx.core.widget.NestedScrollView + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <include layout="@layout/alert_dialog_systemui" /> + +</androidx.core.widget.NestedScrollView>
\ No newline at end of file diff --git a/packages/SystemUI/res/values-sw600dp-land/dimens.xml b/packages/SystemUI/res/values-sw600dp-land/dimens.xml index b3da144ac73b..6c42073b8805 100644 --- a/packages/SystemUI/res/values-sw600dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw600dp-land/dimens.xml @@ -21,13 +21,20 @@ <dimen name="keyguard_indication_margin_bottom">25dp</dimen> <dimen name="ambient_indication_margin_bottom">115dp</dimen> <dimen name="lock_icon_margin_bottom">60dp</dimen> - - <dimen name="qs_media_session_height_expanded">172dp</dimen> - <!-- margin from keyguard status bar to clock. For split shade it should be keyguard_split_shade_top_margin - status_bar_header_height_keyguard = 8dp --> <dimen name="keyguard_clock_top_margin">8dp</dimen> + <!-- QS--> + <dimen name="qs_panel_padding_top">16dp</dimen> + <dimen name="qs_content_horizontal_padding">24dp</dimen> + <dimen name="qs_horizontal_margin">24dp</dimen> + <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2, + otherwise full space between two pages is qs_horizontal_margin*2, and that makes tiles page + not appear immediately after user swipes to the side --> + <dimen name="qs_tiles_page_horizontal_margin">12dp</dimen> + <dimen name="qs_media_session_height_expanded">172dp</dimen> + <dimen name="split_shade_notifications_scrim_margin_bottom">16dp</dimen> <dimen name="notification_panel_margin_bottom">48dp</dimen> diff --git a/packages/SystemUI/res/values-sw720dp-land/dimens.xml b/packages/SystemUI/res/values-sw720dp-land/dimens.xml index 33d6f19b35d0..f45f106d391d 100644 --- a/packages/SystemUI/res/values-sw720dp-land/dimens.xml +++ b/packages/SystemUI/res/values-sw720dp-land/dimens.xml @@ -26,6 +26,12 @@ <dimen name="status_bar_header_height_keyguard">56dp</dimen> <dimen name="qs_media_session_height_expanded">251dp</dimen> + <dimen name="qs_content_horizontal_padding">40dp</dimen> + <dimen name="qs_horizontal_margin">40dp</dimen> + <!-- in split shade qs_tiles_page_horizontal_margin should be equal of qs_horizontal_margin/2, + otherwise full space between two pages is qs_horizontal_margin*2, and that makes tiles page + not appear immediately after user swipes to the side --> + <dimen name="qs_tiles_page_horizontal_margin">20dp</dimen> <dimen name="lockscreen_shade_max_over_scroll_amount">42dp</dimen> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index f77430bae4ab..a014efb7d176 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -478,6 +478,22 @@ <dimen name="qqs_layout_margin_top">16dp</dimen> <dimen name="qqs_layout_padding_bottom">24dp</dimen> + <!-- Most of the time it should be the same as notification_side_paddings as it's vertically + aligned with notifications. The exception is split shade when this value becomes + independent --> + <dimen name="qs_horizontal_margin">@dimen/notification_side_paddings</dimen> + + <!-- Most of the time it should be the same as notification_shade_content_margin_horizontal as + it's vertically aligned with notifications. The exception is split shade when this value + becomes independent --> + <dimen name="qs_content_horizontal_padding">@dimen/notification_shade_content_margin_horizontal</dimen> + + <!-- Most of the time it should be the same as notification_side_paddings as it's vertically + aligned with notifications. That's not the case on large screen when we have either split + shade and QS is not above notifications or in portrait shade when notification scrim is no + longer full width and next page of tiles should be at the edge of the screen --> + <dimen name="qs_tiles_page_horizontal_margin">@dimen/notification_side_paddings</dimen> + <dimen name="qs_customize_internal_side_paddings">8dp</dimen> <dimen name="qs_icon_size">20dp</dimen> <dimen name="qs_side_view_size">28dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 8f412e342fa8..2426f017e20e 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1211,7 +1211,7 @@ <string name="wallet_app_button_label">Show all</string> <!-- Label of the button underneath the card carousel prompting user unlock device. [CHAR LIMIT=NONE] --> <!-- Secondary label of the quick access wallet tile if no card. [CHAR LIMIT=NONE] --> - <string name="wallet_secondary_label_no_card">Add a card</string> + <string name="wallet_secondary_label_no_card">Tap to open</string> <!-- Secondary label of the quick access wallet tile if wallet is still updating. [CHAR LIMIT=NONE] --> <string name="wallet_secondary_label_updating">Updating</string> <!-- Secondary label of the quick access wallet tile if device locked. [CHAR LIMIT=NONE] --> @@ -2229,11 +2229,7 @@ <string name="media_move_closer_to_end_cast">Move closer to <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g> to play here</string> <!-- Text informing the user that their media is now playing on a different device (deviceName). [CHAR LIMIT=50] --> <string name="media_transfer_playing_different_device">Playing on <xliff:g id="deviceName" example="My Tablet">%1$s</xliff:g></string> - <!-- Text informing the user that their media is now playing on this device. [CHAR LIMIT=50] --> - <string name="media_transfer_playing_this_device" product="default">Playing on this phone</string> - <!-- Text informing the user that their media is now playing on this tablet device. [CHAR LIMIT=50] --> - <string name="media_transfer_playing_this_device" product="tablet">Playing on this tablet</string> - <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIMIT=50] --> + <!-- Text informing the user that the media transfer has failed because something went wrong. [CHAR LIsMIT=50] --> <string name="media_transfer_failed">Something went wrong. Try again.</string> <!-- Error message indicating that a control timed out while waiting for an update [CHAR_LIMIT=30] --> @@ -2486,6 +2482,8 @@ <string name="clipboard_edit_image_description">Edit copied image</string> <!-- Label for button to send copied content to a nearby device [CHAR LIMIT=NONE] --> <string name="clipboard_send_nearby_description">Send to nearby device</string> + <!-- Text informing user that copied content is hidden [CHAR LIMIT=NONE] --> + <string name="clipboard_text_hidden">Tap to view</string> <!-- Generic "add" string [CHAR LIMIT=NONE] --> <string name="add">Add</string> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index c93c0652a070..0c25f5473416 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -380,7 +380,7 @@ <item name="android:buttonBarNegativeButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> <item name="android:buttonBarNeutralButtonStyle">@style/Widget.Dialog.Button.BorderButton</item> <item name="android:colorBackground">?androidprv:attr/colorSurface</item> - <item name="android:alertDialogStyle">@style/AlertDialogStyle</item> + <item name="android:alertDialogStyle">@style/ScrollableAlertDialogStyle</item> <item name="android:buttonBarStyle">@style/ButtonBarStyle</item> <item name="android:buttonBarButtonStyle">@style/Widget.Dialog.Button.Large</item> </style> @@ -389,6 +389,10 @@ <item name="android:layout">@layout/alert_dialog_systemui</item> </style> + <style name="ScrollableAlertDialogStyle" parent="@androidprv:style/AlertDialog.DeviceDefault"> + <item name="android:layout">@layout/scrollable_alert_dialog_systemui</item> + </style> + <style name="ButtonBarStyle" parent="@androidprv:style/DeviceDefault.ButtonBar.AlertDialog"> <item name="android:paddingTop">@dimen/dialog_button_bar_top_padding</item> <item name="android:paddingBottom">@dimen/dialog_bottom_padding</item> @@ -994,6 +998,7 @@ <style name="Theme.SystemUI.Dialog.Cast"> <item name="android:textAppearanceMedium">@style/TextAppearance.CastItem</item> + <item name="android:alertDialogStyle">@style/AlertDialogStyle</item> </style> <!-- ************************************************************************************* --> @@ -1111,8 +1116,9 @@ </style> <style name="FgsManagerAppDuration"> - <item name="android:fontFamily">?android:attr/textAppearanceSmall</item> <item name="android:textDirection">locale</item> + <item name="android:textAppearance">?android:attr/textAppearanceSmall</item> + <item name="android:textColor">?android:attr/textColorSecondary</item> </style> <style name="BroadcastDialog"> diff --git a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt index f195d2094d6a..38fa35453418 100644 --- a/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt +++ b/packages/SystemUI/src/com/android/keyguard/ActiveUnlockConfig.kt @@ -16,14 +16,20 @@ package com.android.keyguard +import android.annotation.IntDef import android.content.ContentResolver import android.database.ContentObserver +import android.hardware.biometrics.BiometricFaceConstants.FACE_ERROR_TIMEOUT import android.net.Uri import android.os.Handler import android.os.UserHandle import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT +import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED import android.provider.Settings.Secure.ACTIVE_UNLOCK_ON_WAKE +import android.util.Log import com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton @@ -44,6 +50,20 @@ class ActiveUnlockConfig @Inject constructor( dumpManager: DumpManager ) : Dumpable { + companion object { + const val TAG = "ActiveUnlockConfig" + + const val BIOMETRIC_TYPE_NONE = 0 + const val BIOMETRIC_TYPE_ANY_FACE = 1 + const val BIOMETRIC_TYPE_ANY_FINGERPRINT = 2 + const val BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT = 3 + } + + @Retention(AnnotationRetention.SOURCE) + @IntDef(BIOMETRIC_TYPE_NONE, BIOMETRIC_TYPE_ANY_FACE, BIOMETRIC_TYPE_ANY_FINGERPRINT, + BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT) + annotation class BiometricType + /** * Indicates the origin for an active unlock request. */ @@ -51,35 +71,50 @@ class ActiveUnlockConfig @Inject constructor( WAKE, UNLOCK_INTENT, BIOMETRIC_FAIL, ASSISTANT } + var keyguardUpdateMonitor: KeyguardUpdateMonitor? = null private var requestActiveUnlockOnWakeup = false private var requestActiveUnlockOnUnlockIntent = false private var requestActiveUnlockOnBioFail = false + private var faceErrorsToTriggerBiometricFailOn = mutableSetOf(FACE_ERROR_TIMEOUT) + private var faceAcquireInfoToTriggerBiometricFailOn = mutableSetOf<Int>() + private var onUnlockIntentWhenBiometricEnrolled = mutableSetOf<Int>(BIOMETRIC_TYPE_NONE) + private val settingsObserver = object : ContentObserver(handler) { - private val wakeUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE) - private val unlockIntentUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT) - private val bioFailUri: Uri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL) + private val wakeUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_WAKE) + private val unlockIntentUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT) + private val bioFailUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL) + private val faceErrorsUri = secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ERRORS) + private val faceAcquireInfoUri = + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO) + private val unlockIntentWhenBiometricEnrolledUri = + secureSettings.getUriFor(ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED) fun register() { - contentResolver.registerContentObserver( - wakeUri, - false, - this, - UserHandle.USER_ALL) - contentResolver.registerContentObserver( - unlockIntentUri, - false, - this, - UserHandle.USER_ALL) - contentResolver.registerContentObserver( - bioFailUri, - false, - this, - UserHandle.USER_ALL) + registerUri( + listOf( + wakeUri, + unlockIntentUri, + bioFailUri, + faceErrorsUri, + faceAcquireInfoUri, + unlockIntentWhenBiometricEnrolledUri + ) + ) onChange(true, ArrayList(), 0, getCurrentUser()) } + private fun registerUri(uris: Collection<Uri>) { + for (uri in uris) { + contentResolver.registerContentObserver( + uri, + false, + this, + UserHandle.USER_ALL) + } + } + override fun onChange( selfChange: Boolean, uris: Collection<Uri>, @@ -104,6 +139,55 @@ class ActiveUnlockConfig @Inject constructor( requestActiveUnlockOnBioFail = secureSettings.getIntForUser( ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, getCurrentUser()) == 1 } + + if (selfChange || uris.contains(faceErrorsUri)) { + processStringArray( + secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ERRORS, + getCurrentUser()), + faceErrorsToTriggerBiometricFailOn, + setOf(FACE_ERROR_TIMEOUT)) + } + + if (selfChange || uris.contains(faceAcquireInfoUri)) { + processStringArray( + secureSettings.getStringForUser(ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, + getCurrentUser()), + faceAcquireInfoToTriggerBiometricFailOn, + setOf<Int>()) + } + + if (selfChange || uris.contains(unlockIntentWhenBiometricEnrolledUri)) { + processStringArray( + secureSettings.getStringForUser( + ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + getCurrentUser()), + onUnlockIntentWhenBiometricEnrolled, + setOf(BIOMETRIC_TYPE_NONE)) + } + } + + /** + * Convert a pipe-separated set of integers into a set of ints. + * @param stringSetting expected input are integers delineated by a pipe. For example, + * it may look something like this: "1|5|3". + * @param out updates the "out" Set will the integers between the pipes. + * @param default If stringSetting is null, "out" will be populated with values in "default" + */ + private fun processStringArray( + stringSetting: String?, + out: MutableSet<Int>, + default: Set<Int> + ) { + out.clear() + stringSetting?.let { + for (code: String in stringSetting.split("|")) { + try { + out.add(code.toInt()) + } catch (e: NumberFormatException) { + Log.e(TAG, "Passed an invalid setting=$code") + } + } + } ?: out.addAll(default) } } @@ -113,6 +197,30 @@ class ActiveUnlockConfig @Inject constructor( } /** + * If any active unlock triggers are enabled. + */ + fun isActiveUnlockEnabled(): Boolean { + return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent || + requestActiveUnlockOnBioFail + } + + /** + * Whether the face error code from {@link BiometricFaceConstants} should trigger + * active unlock on biometric failure. + */ + fun shouldRequestActiveUnlockOnFaceError(errorCode: Int): Boolean { + return faceErrorsToTriggerBiometricFailOn.contains(errorCode) + } + + /** + * Whether the face acquireInfo from {@link BiometricFaceConstants} should trigger + * active unlock on biometric failure. + */ + fun shouldRequestActiveUnlockOnFaceAcquireInfo(acquiredInfo: Int): Boolean { + return faceAcquireInfoToTriggerBiometricFailOn.contains(acquiredInfo) + } + + /** * Whether to trigger active unlock based on where the request is coming from and * the current settings. */ @@ -121,7 +229,8 @@ class ActiveUnlockConfig @Inject constructor( ACTIVE_UNLOCK_REQUEST_ORIGIN.WAKE -> requestActiveUnlockOnWakeup ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT -> - requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup + requestActiveUnlockOnUnlockIntent || requestActiveUnlockOnWakeup || + (shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()) ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL -> requestActiveUnlockOnBioFail || requestActiveUnlockOnUnlockIntent || @@ -131,17 +240,55 @@ class ActiveUnlockConfig @Inject constructor( } } - /** - * If any active unlock triggers are enabled. - */ - fun isActiveUnlockEnabled(): Boolean { - return requestActiveUnlockOnWakeup || requestActiveUnlockOnUnlockIntent || - requestActiveUnlockOnBioFail + private fun shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment(): Boolean { + if (!requestActiveUnlockOnBioFail) { + return false + } + + keyguardUpdateMonitor?.let { + val anyFaceEnrolled = it.isFaceEnrolled + val anyFingerprintEnrolled = + it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser()) + val udfpsEnrolled = it.isUdfpsEnrolled + + if (!anyFaceEnrolled && !anyFingerprintEnrolled) { + return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_NONE) + } + + if (!anyFaceEnrolled && anyFingerprintEnrolled) { + return onUnlockIntentWhenBiometricEnrolled.contains( + BIOMETRIC_TYPE_ANY_FINGERPRINT) || + (udfpsEnrolled && onUnlockIntentWhenBiometricEnrolled.contains( + BIOMETRIC_TYPE_UNDER_DISPLAY_FINGERPRINT)) + } + + if (!anyFingerprintEnrolled && anyFaceEnrolled) { + return onUnlockIntentWhenBiometricEnrolled.contains(BIOMETRIC_TYPE_ANY_FACE) + } + } + + return false } override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("Settings:") pw.println(" requestActiveUnlockOnWakeup=$requestActiveUnlockOnWakeup") pw.println(" requestActiveUnlockOnUnlockIntent=$requestActiveUnlockOnUnlockIntent") pw.println(" requestActiveUnlockOnBioFail=$requestActiveUnlockOnBioFail") + pw.println(" requestActiveUnlockOnUnlockIntentWhenBiometricEnrolled=" + + "$onUnlockIntentWhenBiometricEnrolled") + pw.println(" requestActiveUnlockOnFaceError=$faceErrorsToTriggerBiometricFailOn") + pw.println(" requestActiveUnlockOnFaceAcquireInfo=" + + "$faceAcquireInfoToTriggerBiometricFailOn") + + pw.println("Current state:") + keyguardUpdateMonitor?.let { + pw.println(" shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment=" + + "${shouldRequestActiveUnlockOnUnlockIntentFromBiometricEnrollment()}") + pw.println(" faceEnrolled=${it.isFaceEnrolled}") + pw.println(" fpEnrolled=${ + it.getCachedIsUnlockWithFingerprintPossible(getCurrentUser())}") + pw.println(" udfpsEnrolled=${it.isUdfpsEnrolled}") + } ?: pw.println(" keyguardUpdateMonitor is uninitialized") } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java index 6a68c70c6acb..98ac640bf703 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java @@ -28,7 +28,6 @@ import com.android.keyguard.KeyguardSecurityModel.SecurityMode; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; @@ -169,7 +168,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> private final EmergencyButtonController.Factory mEmergencyButtonControllerFactory; private final FalsingCollector mFalsingCollector; private final DevicePostureController mDevicePostureController; - private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final KeyguardViewController mKeyguardViewController; @Inject public Factory(KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -181,7 +180,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> TelephonyManager telephonyManager, FalsingCollector falsingCollector, EmergencyButtonController.Factory emergencyButtonControllerFactory, DevicePostureController devicePostureController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + KeyguardViewController keyguardViewController) { mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLockPatternUtils = lockPatternUtils; mLatencyTracker = latencyTracker; @@ -194,7 +193,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mEmergencyButtonControllerFactory = emergencyButtonControllerFactory; mFalsingCollector = falsingCollector; mDevicePostureController = devicePostureController; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mKeyguardViewController = keyguardViewController; } /** Create a new {@link KeyguardInputViewController}. */ @@ -215,7 +214,7 @@ public abstract class KeyguardInputViewController<T extends KeyguardInputView> mKeyguardUpdateMonitor, securityMode, mLockPatternUtils, keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker, mInputMethodManager, emergencyButtonController, mMainExecutor, mResources, - mFalsingCollector, mStatusBarKeyguardViewManager); + mFalsingCollector, mKeyguardViewController); } else if (keyguardInputView instanceof KeyguardPINView) { return new KeyguardPinViewController((KeyguardPINView) keyguardInputView, diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java index 19035264db7f..8f44e97ef231 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java @@ -43,7 +43,6 @@ import com.android.settingslib.Utils; import com.android.systemui.R; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.util.concurrency.DelayableExecutor; import java.util.List; @@ -56,7 +55,7 @@ public class KeyguardPasswordViewController private final KeyguardSecurityCallback mKeyguardSecurityCallback; private final InputMethodManager mInputMethodManager; private final DelayableExecutor mMainExecutor; - private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private final KeyguardViewController mKeyguardViewController; private final boolean mShowImeAtScreenOn; private EditText mPasswordEntry; private ImageView mSwitchImeButton; @@ -119,14 +118,14 @@ public class KeyguardPasswordViewController @Main DelayableExecutor mainExecutor, @Main Resources resources, FalsingCollector falsingCollector, - StatusBarKeyguardViewManager statusBarKeyguardViewManager) { + KeyguardViewController keyguardViewController) { super(view, keyguardUpdateMonitor, securityMode, lockPatternUtils, keyguardSecurityCallback, messageAreaControllerFactory, latencyTracker, falsingCollector, emergencyButtonController); mKeyguardSecurityCallback = keyguardSecurityCallback; mInputMethodManager = inputMethodManager; mMainExecutor = mainExecutor; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; + mKeyguardViewController = keyguardViewController; mShowImeAtScreenOn = resources.getBoolean(R.bool.kg_show_ime_at_screen_on); mPasswordEntry = mView.findViewById(mView.getPasswordTextViewId()); mSwitchImeButton = mView.findViewById(R.id.switch_ime_button); @@ -209,7 +208,7 @@ public class KeyguardPasswordViewController } private void showInput() { - if (!mStatusBarKeyguardViewManager.isBouncerShowing()) { + if (!mKeyguardViewController.isBouncerShowing()) { return; } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java index 41f92407a683..39c394981193 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPatternViewController.java @@ -282,7 +282,7 @@ public class KeyguardPatternViewController super.reloadColors(); mMessageAreaController.reloadColors(); int textColor = Utils.getColorAttr(mLockPatternView.getContext(), - android.R.attr.textColorPrimary).getDefaultColor(); + android.R.attr.textColorSecondary).getDefaultColor(); int errorColor = Utils.getColorError(mLockPatternView.getContext()).getDefaultColor(); mLockPatternView.setColors(textColor, textColor, errorColor); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index bbe9a362b1fa..121ac299ec5b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -56,7 +56,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.ContentObserver; import android.hardware.SensorPrivacyManager; -import android.hardware.biometrics.BiometricFaceConstants; import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricManager; import android.hardware.biometrics.BiometricSourceType; @@ -1615,7 +1614,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab mKeyguardBypassController.setUserHasDeviceEntryIntent(false); } - if (errMsgId == BiometricFaceConstants.FACE_ERROR_TIMEOUT) { + if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceError(errMsgId)) { requestActiveUnlock( ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL, "faceError-" + errMsgId); @@ -1625,6 +1624,13 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab @Override public void onAuthenticationAcquired(int acquireInfo) { handleFaceAcquired(acquireInfo); + + if (mActiveUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + acquireInfo)) { + requestActiveUnlock( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL, + "faceAcquireInfo-" + acquireInfo); + } } }; @@ -1639,6 +1645,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private boolean mFingerprintLockedOut; private boolean mFingerprintLockedOutPermanent; private boolean mFaceLockedOutPermanent; + private HashMap<Integer, Boolean> mIsUnlockWithFingerprintPossible = new HashMap<>(); private TelephonyManager mTelephonyManager; /** @@ -1889,6 +1896,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab dumpManager.registerDumpable(getClass().getName(), this); mSensorPrivacyManager = context.getSystemService(SensorPrivacyManager.class); mActiveUnlockConfig = activeUnlockConfiguration; + mActiveUnlockConfig.setKeyguardUpdateMonitor(this); mHandler = new Handler(mainLooper) { @Override @@ -2329,7 +2337,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (shouldTriggerActiveUnlock()) { - if (DEBUG) { + if (DEBUG_ACTIVE_UNLOCK) { Log.d("ActiveUnlock", "initiate active unlock triggerReason=" + reason); } mTrustManager.reportUserMayRequestUnlock(KeyguardUpdateMonitor.getCurrentUser()); @@ -2359,7 +2367,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } if (allowRequest && shouldTriggerActiveUnlock()) { - if (DEBUG) { + if (DEBUG_ACTIVE_UNLOCK) { Log.d("ActiveUnlock", "reportUserRequestedUnlock" + " origin=" + requestOrigin.name() + " reason=" + reason @@ -2777,8 +2785,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab } private boolean isUnlockWithFingerprintPossible(int userId) { - return mFpm != null && mFpm.isHardwareDetected() && !isFingerprintDisabled(userId) - && mFpm.hasEnrolledTemplates(userId); + mIsUnlockWithFingerprintPossible.put(userId, mFpm != null && mFpm.isHardwareDetected() + && !isFingerprintDisabled(userId) && mFpm.hasEnrolledTemplates(userId)); + return mIsUnlockWithFingerprintPossible.get(userId); + } + + /** + * Cached value for whether fingerprint is enrolled and possible to use for authentication. + * Note: checking fingerprint enrollment directly with the AuthController requires an IPC. + */ + public boolean getCachedIsUnlockWithFingerprintPossible(int userId) { + return mIsUnlockWithFingerprintPossible.get(userId); } private boolean isUnlockWithFacePossible(int userId) { diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java index e1913657b7cc..fdde40296511 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconView.java @@ -55,7 +55,7 @@ public class LockIconView extends FrameLayout implements Dumpable { @NonNull private final RectF mSensorRect; @NonNull private PointF mLockIconCenter = new PointF(0f, 0f); - private int mRadius; + private float mRadius; private int mLockIconPadding; private ImageView mLockIcon; @@ -126,7 +126,7 @@ public class LockIconView extends FrameLayout implements Dumpable { * Set the location of the lock icon. */ @VisibleForTesting - public void setCenterLocation(@NonNull PointF center, int radius, int drawablePadding) { + public void setCenterLocation(@NonNull PointF center, float radius, int drawablePadding) { mLockIconCenter = center; mRadius = radius; mLockIconPadding = drawablePadding; diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java index 2b217f02e834..d79b1454514e 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java @@ -188,6 +188,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme protected void onViewAttached() { updateIsUdfpsEnrolled(); updateConfiguration(); + updateLockIconLocation(); updateKeyguardShowing(); mUserUnlockedWithBiometric = false; @@ -340,20 +341,17 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mHeightPixels = bounds.bottom; mBottomPaddingPx = getResources().getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); - final int defaultPaddingPx = - getResources().getDimensionPixelSize(R.dimen.lock_icon_padding); - mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor()); - mUnlockedLabel = mView.getContext().getResources().getString( R.string.accessibility_unlock_button); mLockedLabel = mView.getContext() .getResources().getString(R.string.accessibility_lock_icon); - - updateLockIconLocation(); } private void updateLockIconLocation() { if (mUdfpsSupported) { + final int defaultPaddingPx = + getResources().getDimensionPixelSize(R.dimen.lock_icon_padding); + mScaledPaddingPx = (int) (defaultPaddingPx * mAuthController.getScaleFactor()); mView.setCenterLocation(mAuthController.getUdfpsLocation(), mAuthController.getUdfpsRadius(), mScaledPaddingPx); } else { @@ -362,8 +360,6 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mHeightPixels - mBottomPaddingPx - sLockIconRadiusPx), sLockIconRadiusPx, mScaledPaddingPx); } - - mView.getHitRect(mSensorTouchLocation); } @Override @@ -386,6 +382,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen); pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState)); pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount); + pw.println(" mSensorTouchLocation: " + mSensorTouchLocation); if (mView != null) { mView.dump(pw, args); @@ -672,6 +669,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme } private boolean inLockIconArea(MotionEvent event) { + mView.getHitRect(mSensorTouchLocation); return mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) && mView.getVisibility() == View.VISIBLE; } @@ -692,6 +690,7 @@ public class LockIconViewController extends ViewController<LockIconView> impleme mExecutor.execute(() -> { updateIsUdfpsEnrolled(); updateConfiguration(); + updateLockIconLocation(); }); } @@ -705,6 +704,11 @@ public class LockIconViewController extends ViewController<LockIconView> impleme public void onEnrollmentsChanged() { updateUdfpsConfig(); } + + @Override + public void onUdfpsLocationChanged() { + updateLockIconLocation(); + } }; private final View.OnClickListener mA11yClickListener = v -> onLongPress(); diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java index caf7ee4db2c3..c91c8992780c 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadAnimator.java @@ -30,7 +30,6 @@ import android.widget.TextView; import androidx.annotation.StyleRes; -import com.android.settingslib.Utils; import com.android.systemui.animation.Interpolators; /** @@ -42,24 +41,29 @@ class NumPadAnimator { private ValueAnimator mContractAnimator; private AnimatorSet mContractAnimatorSet; private GradientDrawable mBackground; - private int mNormalColor; - private int mHighlightColor; - private int mStyle; + private Drawable mImageButton; private TextView mDigitTextView; + private int mNormalBackgroundColor; + private int mPressedBackgroundColor; + private int mTextColorPrimary; + private int mTextColorPressed; + private int mStyle; private static final int EXPAND_ANIMATION_MS = 100; private static final int EXPAND_COLOR_ANIMATION_MS = 50; private static final int CONTRACT_ANIMATION_DELAY_MS = 33; private static final int CONTRACT_ANIMATION_MS = 417; - NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style) { - this(context, drawable, style, null); + NumPadAnimator(Context context, final Drawable drawable, + @StyleRes int style, Drawable buttonImage) { + this(context, drawable, style, null, buttonImage); } NumPadAnimator(Context context, final Drawable drawable, @StyleRes int style, - @Nullable TextView digitTextView) { + @Nullable TextView digitTextView, @Nullable Drawable buttonImage) { mStyle = style; mBackground = (GradientDrawable) drawable; mDigitTextView = digitTextView; + mImageButton = buttonImage; reloadColors(context); } @@ -88,26 +92,28 @@ class NumPadAnimator { * Reload colors from resources. **/ void reloadColors(Context context) { - int[] customAttrs = {android.R.attr.colorControlNormal, - android.R.attr.colorControlHighlight}; + boolean isNumPadKey = mImageButton == null; + int[] customAttrs = {android.R.attr.colorControlNormal}; ContextThemeWrapper ctw = new ContextThemeWrapper(context, mStyle); TypedArray a = ctw.obtainStyledAttributes(customAttrs); - mNormalColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0, + mNormalBackgroundColor = getPrivateAttrColorIfUnset(ctw, a, 0, 0, com.android.internal.R.attr.colorSurface); - mHighlightColor = a.getColor(1, 0); a.recycle(); - - mBackground.setColor(mNormalColor); - createAnimators(context); + mBackground.setColor(mNormalBackgroundColor); + + mPressedBackgroundColor = context.getColor(android.R.color.system_accent1_200); + mTextColorPrimary = isNumPadKey + ? com.android.settingslib.Utils + .getColorAttrDefaultColor(context, android.R.attr.textColorPrimary) + : com.android.settingslib.Utils + .getColorAttrDefaultColor(context, android.R.attr.textColorPrimaryInverse); + mTextColorPressed = com.android.settingslib.Utils + .getColorAttrDefaultColor(context, com.android.internal.R.attr.textColorOnAccent); + createAnimators(); } - private void createAnimators(Context context) { - int textColorPrimary = Utils.getColorAttrDefaultColor(context, - android.R.attr.textColorPrimary); - int textColorPrimaryInverse = Utils.getColorAttrDefaultColor(context, - android.R.attr.textColorPrimaryInverse); - + private void createAnimators() { // Actual values will be updated later, usually during an onLayout() call mExpandAnimator = ValueAnimator.ofFloat(0f, 1f); mExpandAnimator.setDuration(EXPAND_ANIMATION_MS); @@ -116,7 +122,7 @@ class NumPadAnimator { anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue())); ValueAnimator expandBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), - mNormalColor, mHighlightColor); + mNormalBackgroundColor, mPressedBackgroundColor); expandBackgroundColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS); expandBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); expandBackgroundColorAnimator.addUpdateListener( @@ -124,13 +130,16 @@ class NumPadAnimator { ValueAnimator expandTextColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), - textColorPrimary, textColorPrimaryInverse); + mTextColorPrimary, mTextColorPressed); expandTextColorAnimator.setInterpolator(Interpolators.LINEAR); expandTextColorAnimator.setDuration(EXPAND_COLOR_ANIMATION_MS); expandTextColorAnimator.addUpdateListener(valueAnimator -> { if (mDigitTextView != null) { mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue()); } + if (mImageButton != null) { + mImageButton.setTint((int) valueAnimator.getAnimatedValue()); + } }); mExpandAnimatorSet = new AnimatorSet(); @@ -144,7 +153,7 @@ class NumPadAnimator { mContractAnimator.addUpdateListener( anim -> mBackground.setCornerRadius((float) anim.getAnimatedValue())); ValueAnimator contractBackgroundColorAnimator = ValueAnimator.ofObject(new ArgbEvaluator(), - mHighlightColor, mNormalColor); + mPressedBackgroundColor, mNormalBackgroundColor); contractBackgroundColorAnimator.setInterpolator(Interpolators.LINEAR); contractBackgroundColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS); contractBackgroundColorAnimator.setDuration(CONTRACT_ANIMATION_MS); @@ -152,8 +161,8 @@ class NumPadAnimator { animator -> mBackground.setColor((int) animator.getAnimatedValue())); ValueAnimator contractTextColorAnimator = - ValueAnimator.ofObject(new ArgbEvaluator(), textColorPrimaryInverse, - textColorPrimary); + ValueAnimator.ofObject(new ArgbEvaluator(), mTextColorPressed, + mTextColorPrimary); contractTextColorAnimator.setInterpolator(Interpolators.LINEAR); contractTextColorAnimator.setStartDelay(CONTRACT_ANIMATION_DELAY_MS); contractTextColorAnimator.setDuration(CONTRACT_ANIMATION_MS); @@ -161,6 +170,9 @@ class NumPadAnimator { if (mDigitTextView != null) { mDigitTextView.setTextColor((int) valueAnimator.getAnimatedValue()); } + if (mImageButton != null) { + mImageButton.setTint((int) valueAnimator.getAnimatedValue()); + } }); mContractAnimatorSet = new AnimatorSet(); diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java index 7b98f2759d80..8099f75ed7d4 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadButton.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadButton.java @@ -42,7 +42,7 @@ public class NumPadButton extends AlphaOptimizedImageButton { Drawable background = getBackground(); if (background instanceof GradientDrawable) { mAnimator = new NumPadAnimator(context, background.mutate(), - attrs.getStyleAttribute()); + attrs.getStyleAttribute(), getDrawable()); } else { mAnimator = null; } diff --git a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java index f771c974fe60..4aed2513d4f2 100644 --- a/packages/SystemUI/src/com/android/keyguard/NumPadKey.java +++ b/packages/SystemUI/src/com/android/keyguard/NumPadKey.java @@ -134,7 +134,7 @@ public class NumPadKey extends ViewGroup { Drawable background = getBackground(); if (background instanceof GradientDrawable) { mAnimator = new NumPadAnimator(context, background.mutate(), - R.style.NumPadKey, mDigitText); + R.style.NumPadKey, mDigitText, null); } else { mAnimator = null; } diff --git a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java index 6e0613036678..334bb1ec12cb 100644 --- a/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/ViewMediatorCallback.java @@ -76,12 +76,6 @@ public interface ViewMediatorCallback { void playTrustedSound(); /** - * When the bouncer is shown or hides - * @param shown - */ - void onBouncerVisiblityChanged(boolean shown); - - /** * @return true if the screen is on */ boolean isScreenOn(); diff --git a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java index 8d6509874776..dd312186afee 100644 --- a/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java +++ b/packages/SystemUI/src/com/android/systemui/ScreenDecorations.java @@ -47,6 +47,7 @@ import android.hardware.graphics.common.AlphaInterpretation; import android.hardware.graphics.common.DisplayDecorationSupport; import android.os.Handler; import android.os.SystemProperties; +import android.os.Trace; import android.os.UserHandle; import android.provider.Settings.Secure; import android.util.DisplayUtils; @@ -899,8 +900,8 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab final DisplayInfo displayInfo = new DisplayInfo(); mContext.getDisplay().getDisplayInfo(displayInfo); return DisplayUtils.getPhysicalPixelDisplaySizeRatio( - stableDisplaySize.x, stableDisplaySize.y, displayInfo.logicalWidth, - displayInfo.logicalHeight); + stableDisplaySize.x, stableDisplaySize.y, displayInfo.getNaturalWidth(), + displayInfo.getNaturalHeight()); } @Override @@ -1067,15 +1068,22 @@ public class ScreenDecorations extends CoreStartable implements Tunable , Dumpab } private void updateLayoutParams() { - if (mOverlays == null) { - return; + //ToDo: We should skip unnecessary call to update view layout. + Trace.beginSection("ScreenDecorations#updateLayoutParams"); + if (mScreenDecorHwcWindow != null) { + mWindowManager.updateViewLayout(mScreenDecorHwcWindow, getHwcWindowLayoutParams()); } - for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { - if (mOverlays[i] == null) { - continue; + + if (mOverlays != null) { + for (int i = 0; i < BOUNDS_POSITION_LENGTH; i++) { + if (mOverlays[i] == null) { + continue; + } + mWindowManager.updateViewLayout( + mOverlays[i].getRootView(), getWindowLayoutParams(i)); } - mWindowManager.updateViewLayout(mOverlays[i].getRootView(), getWindowLayoutParams(i)); } + Trace.endSection(); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index d59ad9286b10..75339aaa843d 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -79,6 +79,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Objects; import java.util.Set; import javax.inject.Inject; @@ -278,6 +279,7 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba } }); mUdfpsController.setAuthControllerUpdateUdfpsLocation(this::updateUdfpsLocation); + mUdfpsController.setHalControlsIllumination(mUdfpsProps.get(0).halControlsIllumination); mUdfpsBounds = mUdfpsProps.get(0).getLocation().getRect(); updateUdfpsLocation(); } @@ -445,11 +447,11 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba /** * @return the radius of UDFPS on the screen in pixels */ - public int getUdfpsRadius() { + public float getUdfpsRadius() { if (mUdfpsController == null || mUdfpsBounds == null) { return -1; } - return mUdfpsBounds.height() / 2; + return mUdfpsBounds.height() / 2f; } /** @@ -633,11 +635,17 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba displayInfo.getNaturalHeight()); final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0); + final Rect previousUdfpsBounds = mUdfpsBounds; mUdfpsBounds = udfpsProp.getLocation().getRect(); mUdfpsBounds.scale(scaleFactor); mUdfpsController.updateOverlayParams(udfpsProp.sensorId, new UdfpsOverlayParams(mUdfpsBounds, displayInfo.getNaturalWidth(), displayInfo.getNaturalHeight(), scaleFactor, displayInfo.rotation)); + if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) { + for (Callback cb : mCallbacks) { + cb.onUdfpsLocationChanged(); + } + } } } @@ -1053,5 +1061,10 @@ public class AuthController extends CoreStartable implements CommandQueue.Callba * Called when the biometric prompt is no longer showing. */ default void onBiometricPromptDismissed() {} + + /** + * The location in pixels can change due to resolution changes. + */ + default void onUdfpsLocationChanged() {} } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 76c1dbcaf20c..86e501670440 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -80,6 +80,7 @@ class AuthRippleController @Inject constructor( @VisibleForTesting internal var startLightRevealScrimOnKeyguardFadingAway = false + var lightRevealScrimAnimator: ValueAnimator? = null var fingerprintSensorLocation: PointF? = null private var faceSensorLocation: PointF? = null private var circleReveal: LightRevealEffect? = null @@ -163,7 +164,8 @@ class AuthRippleController @Inject constructor( if (keyguardStateController.isKeyguardFadingAway) { val lightRevealScrim = centralSurfaces.lightRevealScrim if (startLightRevealScrimOnKeyguardFadingAway && lightRevealScrim != null) { - ValueAnimator.ofFloat(.1f, 1f).apply { + lightRevealScrimAnimator?.cancel() + lightRevealScrimAnimator = ValueAnimator.ofFloat(.1f, 1f).apply { interpolator = Interpolators.LINEAR_OUT_SLOW_IN duration = RIPPLE_ANIMATION_DURATION startDelay = keyguardStateController.keyguardFadingAwayDelay @@ -183,6 +185,8 @@ class AuthRippleController @Inject constructor( if (lightRevealScrim.revealEffect == circleReveal) { lightRevealScrim.revealEffect = LiftReveal } + + lightRevealScrimAnimator = null } }) start() @@ -192,6 +196,13 @@ class AuthRippleController @Inject constructor( } } + /** + * Whether we're animating the light reveal scrim from a call to [onKeyguardFadingAwayChanged]. + */ + fun isAnimatingLightRevealScrim(): Boolean { + return lightRevealScrimAnimator?.isRunning ?: false + } + override fun onStartedGoingToSleep() { // reset the light reveal start in case we were pending an unlock startLightRevealScrimOnKeyguardFadingAway = false @@ -324,15 +335,17 @@ class AuthRippleController @Inject constructor( updateSensorLocation() } - override fun onEnrollmentsChanged() { + override fun onUdfpsLocationChanged() { + updateUdfpsDependentParams() + updateSensorLocation() } } private fun updateUdfpsDependentParams() { authController.udfpsProps?.let { if (it.size > 0) { - udfpsRadius = it[0].location.sensorRadius.toFloat() udfpsController = udfpsControllerProvider.get() + udfpsRadius = authController.udfpsRadius if (mView.isAttachedToWindow) { udfpsController?.addCallback(udfpsControllerCallback) diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index 432d2930453c..a35f0427e55a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -131,6 +131,7 @@ public class UdfpsController implements DozeReceiver { // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple // sensors, this, in addition to a lot of the code here, will be updated. @VisibleForTesting int mSensorId; + private boolean mHalControlsIllumination; @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams(); // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this. @Nullable private Runnable mAuthControllerUpdateUdfpsLocation; @@ -201,9 +202,10 @@ public class UdfpsController implements DozeReceiver { mKeyguardUpdateMonitor, mDialogManager, mDumpManager, mLockscreenShadeTransitionController, mConfigurationController, mSystemClock, mKeyguardStateController, - mUnlockedScreenOffAnimationController, mHbmProvider, requestId, reason, - callback, (view, event, fromUdfpsView) -> onTouch(requestId, event, - fromUdfpsView), mActivityLaunchAnimator))); + mUnlockedScreenOffAnimationController, mHalControlsIllumination, + mHbmProvider, requestId, reason, callback, + (view, event, fromUdfpsView) -> onTouch(requestId, event, + fromUdfpsView), mActivityLaunchAnimator))); } @Override @@ -310,6 +312,11 @@ public class UdfpsController implements DozeReceiver { mAuthControllerUpdateUdfpsLocation = r; } + // TODO(b/229290039): UDFPS controller should manage its properties on its own. Remove this. + public void setHalControlsIllumination(boolean value) { + mHalControlsIllumination = value; + } + /** * Calculate the pointer speed given a velocity tracker and the pointer id. * This assumes that the velocity tracker has already been passed all relevant motion events. diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 9c8aee4e93a7..2d51c973b0b1 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -77,6 +77,7 @@ class UdfpsControllerOverlay( private val systemClock: SystemClock, private val keyguardStateController: KeyguardStateController, private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, + private val halControlsIllumination: Boolean, private var hbmProvider: UdfpsHbmProvider, val requestId: Long, @ShowReason val requestReason: Int, @@ -137,6 +138,7 @@ class UdfpsControllerOverlay( R.layout.udfps_view, null, false ) as UdfpsView).apply { overlayParams = params + halControlsIllumination = this@UdfpsControllerOverlay.halControlsIllumination setHbmProvider(hbmProvider) val animation = inflateUdfpsAnimation(this, controller) if (animation != null) { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java index 2f097924817e..8de721352069 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapter.java @@ -154,7 +154,9 @@ public class UdfpsDialogMeasureAdapter { //re-calculate the height of description View description = mView.findViewById(R.id.description); - totalHeight += measureDescription(description, displayHeight, width, totalHeight); + if (description != null && description.getVisibility() != View.GONE) { + totalHeight += measureDescription(description, displayHeight, width, totalHeight); + } return new AuthDialog.LayoutParams(width, totalHeight); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java index 38c937feb941..f26dd5f57061 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsHbmProvider.java @@ -34,8 +34,11 @@ public interface UdfpsHbmProvider { * invoked from the UI thread. * * @param onHbmEnabled A runnable that will be executed once HBM is enabled. + * + * TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense. + * This only makes sense now because vendor code may rely on the side effects of enableHbm. */ - void enableHbm(@Nullable Runnable onHbmEnabled); + void enableHbm(boolean halControlsIllumination, @Nullable Runnable onHbmEnabled); /** * UdfpsView will call this to disable HBM when illumination is no longer needed. @@ -46,8 +49,6 @@ public interface UdfpsHbmProvider { * The call must be made from the UI thread. The callback, if provided, will also be invoked * from the UI thread. * - * - * * @param onHbmDisabled A runnable that will be executed once HBM is disabled. */ void disableHbm(@Nullable Runnable onHbmDisabled); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt index 2aa345ab28dc..245c2252d57b 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt @@ -63,9 +63,12 @@ class UdfpsView( /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */ var animationViewController: UdfpsAnimationViewController<*>? = null - /** Parameters that affect the position and size of the overlay. Visible for testing. */ + /** Parameters that affect the position and size of the overlay. */ var overlayParams = UdfpsOverlayParams() + /** Whether the HAL is responsible for enabling and disabling of LHBM. */ + var halControlsIllumination: Boolean = true + /** Debug message. */ var debugMessage: String? = null set(value) { @@ -154,11 +157,17 @@ class UdfpsView( } private fun doIlluminate(onIlluminatedRunnable: Runnable?) { - hbmProvider?.enableHbm() { + // TODO(b/231335067): enableHbm with halControlsIllumination=true shouldn't make sense. + // This only makes sense now because vendor code may rely on the side effects of enableHbm. + hbmProvider?.enableHbm(halControlsIllumination) { if (onIlluminatedRunnable != null) { - // No framework API can reliably tell when a frame reaches the panel. A timeout - // is the safest solution. - postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs) + if (halControlsIllumination) { + onIlluminatedRunnable.run() + } else { + // No framework API can reliably tell when a frame reaches the panel. A timeout + // is the safest solution. + postDelayed(onIlluminatedRunnable, onIlluminatedDelayMs) + } } else { Log.w(TAG, "doIlluminate | onIlluminatedRunnable is null") } diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java index 726d00c230d7..ee8363fe50d4 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java @@ -38,6 +38,7 @@ import android.annotation.MainThread; import android.app.RemoteAction; import android.content.BroadcastReceiver; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; @@ -128,6 +129,8 @@ public class ClipboardOverlayController { private final View mClipboardPreview; private final ImageView mImagePreview; private final TextView mTextPreview; + private final TextView mHiddenTextPreview; + private final TextView mHiddenImagePreview; private final View mPreviewBorder; private final OverlayActionChip mEditChip; private final OverlayActionChip mRemoteCopyChip; @@ -186,6 +189,8 @@ public class ClipboardOverlayController { mClipboardPreview = requireNonNull(mView.findViewById(R.id.clipboard_preview)); mImagePreview = requireNonNull(mView.findViewById(R.id.image_preview)); mTextPreview = requireNonNull(mView.findViewById(R.id.text_preview)); + mHiddenTextPreview = requireNonNull(mView.findViewById(R.id.hidden_text_preview)); + mHiddenImagePreview = requireNonNull(mView.findViewById(R.id.hidden_image_preview)); mPreviewBorder = requireNonNull(mView.findViewById(R.id.preview_border)); mEditChip = requireNonNull(mView.findViewById(R.id.edit_chip)); mRemoteCopyChip = requireNonNull(mView.findViewById(R.id.remote_copy_chip)); @@ -270,21 +275,32 @@ public class ClipboardOverlayController { mExitAnimator.cancel(); } reset(); + + boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null + && clipData.getDescription().getExtras() + .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE); if (clipData == null || clipData.getItemCount() == 0) { - showTextPreview(mContext.getResources().getString( - R.string.clipboard_overlay_text_copied)); + showTextPreview( + mContext.getResources().getString(R.string.clipboard_overlay_text_copied), + mTextPreview); } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) { ClipData.Item item = clipData.getItemAt(0); if (item.getTextLinks() != null) { AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource)); } - showEditableText(item.getText()); + if (isSensitive) { + showEditableText( + mContext.getResources().getString(R.string.clipboard_text_hidden), true); + } else { + showEditableText(item.getText(), false); + } } else if (clipData.getItemAt(0).getUri() != null) { // How to handle non-image URIs? - showEditableImage(clipData.getItemAt(0).getUri()); + showEditableImage(clipData.getItemAt(0).getUri(), isSensitive); } else { showTextPreview( - mContext.getResources().getString(R.string.clipboard_overlay_text_copied)); + mContext.getResources().getString(R.string.clipboard_overlay_text_copied), + mTextPreview); } Intent remoteCopyIntent = getRemoteCopyIntent(clipData); // Only show remote copy if it's available. @@ -406,15 +422,23 @@ public class ClipboardOverlayController { animateOut(); } - private void showTextPreview(CharSequence text) { - mTextPreview.setVisibility(View.VISIBLE); + private void showSinglePreview(View v) { + mTextPreview.setVisibility(View.GONE); mImagePreview.setVisibility(View.GONE); - mTextPreview.setText(text.subSequence(0, Math.min(500, text.length()))); + mHiddenTextPreview.setVisibility(View.GONE); + mHiddenImagePreview.setVisibility(View.GONE); + v.setVisibility(View.VISIBLE); + } + + private void showTextPreview(CharSequence text, TextView textView) { + showSinglePreview(textView); + textView.setText(text.subSequence(0, Math.min(500, text.length()))); mEditChip.setVisibility(View.GONE); } - private void showEditableText(CharSequence text) { - showTextPreview(text); + private void showEditableText(CharSequence text, boolean hidden) { + TextView textView = hidden ? mHiddenTextPreview : mTextPreview; + showTextPreview(text, textView); mEditChip.setVisibility(View.VISIBLE); mActionContainerBackground.setVisibility(View.VISIBLE); mEditChip.setAlpha(1f); @@ -422,32 +446,36 @@ public class ClipboardOverlayController { mContext.getString(R.string.clipboard_edit_text_description)); View.OnClickListener listener = v -> editText(); mEditChip.setOnClickListener(listener); - mTextPreview.setOnClickListener(listener); + textView.setOnClickListener(listener); } - private void showEditableImage(Uri uri) { - ContentResolver resolver = mContext.getContentResolver(); - try { - int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale); - // The width of the view is capped, height maintains aspect ratio, so allow it to be - // taller if needed. - Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null); - mImagePreview.setImageBitmap(thumbnail); - } catch (IOException e) { - Log.e(TAG, "Thumbnail loading failed", e); - showTextPreview( - mContext.getResources().getString(R.string.clipboard_overlay_text_copied)); - return; - } - mTextPreview.setVisibility(View.GONE); - mImagePreview.setVisibility(View.VISIBLE); + private void showEditableImage(Uri uri, boolean isSensitive) { mEditChip.setAlpha(1f); mActionContainerBackground.setVisibility(View.VISIBLE); View.OnClickListener listener = v -> editImage(uri); + if (isSensitive) { + showSinglePreview(mHiddenImagePreview); + mHiddenImagePreview.setOnClickListener(listener); + } else { + showSinglePreview(mImagePreview); + ContentResolver resolver = mContext.getContentResolver(); + try { + int size = mContext.getResources().getDimensionPixelSize(R.dimen.overlay_x_scale); + // The width of the view is capped, height maintains aspect ratio, so allow it to be + // taller if needed. + Bitmap thumbnail = resolver.loadThumbnail(uri, new Size(size, size * 4), null); + mImagePreview.setImageBitmap(thumbnail); + } catch (IOException e) { + Log.e(TAG, "Thumbnail loading failed", e); + showTextPreview( + mContext.getResources().getString(R.string.clipboard_overlay_text_copied), + mTextPreview); + } + mImagePreview.setOnClickListener(listener); + } mEditChip.setOnClickListener(listener); mEditChip.setContentDescription( mContext.getString(R.string.clipboard_edit_image_description)); - mImagePreview.setOnClickListener(listener); } private Intent getRemoteCopyIntent(ClipData clipData) { diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java index 0d8987988f0a..b54b83274f45 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java @@ -20,10 +20,12 @@ import static java.util.Objects.requireNonNull; import android.app.Activity; import android.content.ClipData; +import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; +import android.os.PersistableBundle; import android.util.Log; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -41,6 +43,7 @@ public class EditTextActivity extends Activity private EditText mEditText; private ClipboardManager mClipboardManager; private TextView mAttribution; + private boolean mSensitive; @Override protected void onCreate(Bundle savedInstanceState) { @@ -72,6 +75,9 @@ public class EditTextActivity extends Activity } mEditText.setText(clip.getItemAt(0).getText()); mEditText.requestFocus(); + mSensitive = clip.getDescription().getExtras() != null + && clip.getDescription().getExtras() + .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE); mClipboardManager.addPrimaryClipChangedListener(this); } @@ -88,6 +94,9 @@ public class EditTextActivity extends Activity private void saveToClipboard() { ClipData clip = ClipData.newPlainText("text", mEditText.getText()); + PersistableBundle extras = new PersistableBundle(); + extras.putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, mSensitive); + clip.getDescription().setExtras(extras); mClipboardManager.setPrimaryClip(clip); hideImeAndFinish(); } diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt index f9115b20ca06..3eb58bba1ca4 100644 --- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt +++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlAdapter.kt @@ -343,7 +343,7 @@ private class ControlHolderAccessibilityDelegate( info.className = Switch::class.java.name } - override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean { + override fun performAccessibilityAction(host: View, action: Int, args: Bundle?): Boolean { if (super.performAccessibilityAction(host, action, args)) { return true } diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java index 5d154c3b4f6b..4e48a5261f6b 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIDefaultModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java @@ -38,7 +38,6 @@ import com.android.systemui.media.dagger.MediaModule; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.EnhancedEstimates; -import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.power.dagger.PowerModule; import com.android.systemui.qs.dagger.QSModule; import com.android.systemui.qs.tileimpl.QSFactoryImpl; @@ -80,8 +79,19 @@ import dagger.Module; import dagger.Provides; /** - * A dagger module for injecting default implementations of components of System UI that may be - * overridden by the System UI implementation. + * A dagger module for injecting default implementations of components of System UI. + * + * Variants of SystemUI should make a copy of this, include it in their component, and customize it + * as needed. + * + * This module might alternatively be named `AospSystemUIModule`, `PhoneSystemUIModule`, + * or `BasicSystemUIModule`. + * + * Nothing in the module should be strictly required. Each piece should either be swappable with + * a different implementation or entirely removable. + * + * This is different from {@link SystemUIModule} which should be used for pieces of required + * SystemUI code that variants of SystemUI _must_ include to function correctly. */ @Module(includes = { MediaModule.class, @@ -90,7 +100,7 @@ import dagger.Provides; StartCentralSurfacesModule.class, VolumeModule.class }) -public abstract class SystemUIDefaultModule { +public abstract class ReferenceSystemUIModule { @SysUISingleton @Provides @@ -101,9 +111,6 @@ public abstract class SystemUIDefaultModule { } @Binds - abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates); - - @Binds abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager( NotificationLockscreenUserManagerImpl notificationLockscreenUserManager); @@ -148,6 +155,7 @@ public abstract class SystemUIDefaultModule { return spC; } + /** */ @Binds @SysUISingleton public abstract QSFactory bindQSFactory(QSFactoryImpl qsFactoryImpl); diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java index 701972ab7264..5d34a6987b66 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SysUIComponent.java @@ -73,7 +73,7 @@ import dagger.Subcomponent; SystemUIBinder.class, SystemUIModule.class, SystemUICoreStartableModule.class, - SystemUIDefaultModule.class}) + ReferenceSystemUIModule.class}) public interface SysUIComponent { /** diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index bbeb66c5af52..535eff801878 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -100,8 +100,14 @@ import dagger.Module; import dagger.Provides; /** - * A dagger module for injecting components of System UI that are not overridden by the System UI - * implementation. + * A dagger module for injecting components of System UI that are required by System UI. + * + * If your feature can be excluded, subclassed, or re-implemented by a variant of SystemUI, put + * your Dagger Module in {@link ReferenceSystemUIModule} and/or any variant modules that + * rely on the feature. + * + * Adding an entry in this file means that _all_ variants of SystemUI will receive that code. They + * may not appreciate that. */ @Module(includes = { AppOpsModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java index 74044e2c2eb8..1cc5df5d04cf 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java @@ -39,7 +39,6 @@ import com.android.systemui.dock.DockManager; import com.android.systemui.doze.DozeMachine.State; import com.android.systemui.doze.dagger.DozeScope; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.Assert; @@ -94,7 +93,6 @@ public class DozeTriggers implements DozeMachine.Part { private final AuthController mAuthController; private final DelayableExecutor mMainExecutor; private final KeyguardStateController mKeyguardStateController; - private final BatteryController mBatteryController; private final UiEventLogger mUiEventLogger; private final DevicePostureController mDevicePostureController; @@ -186,8 +184,7 @@ public class DozeTriggers implements DozeMachine.Part { @Main DelayableExecutor mainExecutor, UiEventLogger uiEventLogger, KeyguardStateController keyguardStateController, - DevicePostureController devicePostureController, - BatteryController batteryController) { + DevicePostureController devicePostureController) { mContext = context; mDozeHost = dozeHost; mConfig = config; @@ -208,7 +205,6 @@ public class DozeTriggers implements DozeMachine.Part { mMainExecutor = mainExecutor; mUiEventLogger = uiEventLogger; mKeyguardStateController = keyguardStateController; - mBatteryController = batteryController; } private final DevicePostureController.Callback mDevicePostureCallback = posture -> { @@ -320,12 +316,7 @@ public class DozeTriggers implements DozeMachine.Part { gentleWakeUp(pulseReason); } else if (isPickup) { if (shouldDropPickupEvent()) { - mDozeLog.traceSensorEventDropped( - pulseReason, - "keyguardOccluded=" - + mKeyguardStateController.isOccluded() - + " pluggedInWireless=" - + mBatteryController.isPluggedInWireless()); + mDozeLog.traceSensorEventDropped(pulseReason, "keyguard occluded"); return; } gentleWakeUp(pulseReason); @@ -356,7 +347,7 @@ public class DozeTriggers implements DozeMachine.Part { } private boolean shouldDropPickupEvent() { - return mKeyguardStateController.isOccluded() || mBatteryController.isPluggedInWireless(); + return mKeyguardStateController.isOccluded(); } private void gentleWakeUp(int reason) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index fb09132684eb..14a7e3c7f013 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -44,6 +44,7 @@ import com.android.systemui.shared.system.QuickStepContract import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController import com.android.systemui.shared.system.smartspace.SmartspaceState +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import dagger.Lazy @@ -140,7 +141,8 @@ class KeyguardUnlockAnimationController @Inject constructor( keyguardViewMediator: Lazy<KeyguardViewMediator>, private val keyguardViewController: KeyguardViewController, private val featureFlags: FeatureFlags, - private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController> + private val biometricUnlockControllerLazy: Lazy<BiometricUnlockController>, + private val statusBarStateController: SysuiStatusBarStateController ) : KeyguardStateController.Callback, ISysuiUnlockAnimationController.Stub() { interface KeyguardUnlockAnimationListener { @@ -372,7 +374,8 @@ class KeyguardUnlockAnimationController @Inject constructor( * changed. */ override fun onKeyguardGoingAwayChanged() { - if (keyguardStateController.isKeyguardGoingAway) { + if (keyguardStateController.isKeyguardGoingAway + && !statusBarStateController.leaveOpenOnKeyguardHide()) { prepareForInWindowLauncherAnimations() } } @@ -813,6 +816,11 @@ class KeyguardUnlockAnimationController @Inject constructor( return false } + // The smartspace is not visible if the bouncer is showing, so don't shared element it. + if (keyguardStateController.isBouncerShowing) { + return false + } + // We started to swipe to dismiss, but now we're doing a fling animation to complete the // dismiss. In this case, the smartspace swiped away with the rest of the keyguard, so don't // do the shared element transition. diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 10ea1e06c6d7..4d59f1a8e363 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -779,16 +779,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, } @Override - public void onBouncerVisiblityChanged(boolean shown) { - synchronized (KeyguardViewMediator.this) { - if (shown) { - mPendingPinLock = false; - } - adjustStatusBarLocked(shown, false); - } - } - - @Override public void playTrustedSound() { KeyguardViewMediator.this.playTrustedSound(); } @@ -921,12 +911,12 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps, IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException { + setOccluded(false /* isOccluded */, true /* animate */); + if (apps == null || apps.length == 0 || apps[0] == null) { Log.d(TAG, "No apps provided to unocclude runner; " + "skipping animation and unoccluding."); - finishedCallback.onAnimationFinished(); - setOccluded(false /* isOccluded */, true /* animate */); return; } @@ -971,7 +961,6 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, @Override public void onAnimationEnd(Animator animation) { try { - setOccluded(false /* isOccluded */, true /* animate */); finishedCallback.onAnimationFinished(); mUnoccludeAnimator = null; } catch (RemoteException e) { @@ -989,6 +978,19 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, private DozeParameters mDozeParameters; private final KeyguardStateController mKeyguardStateController; + private final KeyguardStateController.Callback mKeyguardStateControllerCallback = + new KeyguardStateController.Callback() { + @Override + public void onBouncerShowingChanged() { + synchronized (KeyguardViewMediator.this) { + if (mKeyguardStateController.isBouncerShowing()) { + mPendingPinLock = false; + } + adjustStatusBarLocked(mKeyguardStateController.isBouncerShowing(), false); + } + } + }; + private final Lazy<KeyguardUnlockAnimationController> mKeyguardUnlockAnimationControllerLazy; private final InteractionJankMonitor mInteractionJankMonitor; private boolean mWallpaperSupportsAmbientMode; @@ -1059,6 +1061,7 @@ public class KeyguardViewMediator extends CoreStartable implements Dumpable, statusBarStateController.addCallback(this); mKeyguardStateController = keyguardStateController; + keyguardStateController.addCallback(mKeyguardStateControllerCallback); mKeyguardUnlockAnimationControllerLazy = keyguardUnlockAnimationControllerLazy; mScreenOffAnimationController = screenOffAnimationController; mInteractionJankMonitor = interactionJankMonitor; diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java index 2fec49955c2d..d65940172b17 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/MediaControlPanel.java @@ -636,6 +636,10 @@ public class MediaControlPanel { mIsArtworkBound = isArtworkBound; } + // Scrim bounds are set manually so it scales as expected + albumView.getForeground().setBounds(0, 0, + Math.max(width, height), Math.max(width, height)); + // Transition Colors to current color scheme mColorSchemeTransition.updateColorScheme(colorScheme, mIsArtworkBound); diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt index 1437c965512e..7eccb3b91bb5 100644 --- a/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/MediaViewController.kt @@ -278,13 +278,14 @@ class MediaViewController @Inject constructor( /** * Apply squishFraction to a copy of viewState such that the cached version is untouched. */ - private fun squishViewState( + @VisibleForTesting + internal fun squishViewState( viewState: TransitionViewState, squishFraction: Float ): TransitionViewState { val squishedViewState = viewState.copy() squishedViewState.height = (squishedViewState.height * squishFraction).toInt() - val albumArtViewState = viewState.widgetStates.get(R.id.album_art) + val albumArtViewState = squishedViewState.widgetStates.get(R.id.album_art) if (albumArtViewState != null) { albumArtViewState.height = squishedViewState.height } @@ -317,6 +318,7 @@ class MediaViewController @Inject constructor( if (transitionLayout == null) { return null } + // Not cached. Let's create a new measurement if (state.expansion == 0.0f || state.expansion == 1.0f) { result = transitionLayout!!.calculateViewState( diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java index 5bb6557c3fe7..8dcca3d55c28 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputBaseDialog.java @@ -449,7 +449,7 @@ public abstract class MediaOutputBaseDialog extends SystemUIDialog implements abstract int getStopButtonVisibility(); public CharSequence getStopButtonText() { - return mContext.getText(R.string.keyboard_key_media_stop); + return mContext.getText(R.string.media_output_dialog_button_stop_casting); } public void onStopButtonClick() { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java index e7f97d25fabe..58b6ad3e51e8 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java @@ -55,8 +55,6 @@ import android.view.WindowManager; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; -import androidx.mediarouter.media.MediaRouter; -import androidx.mediarouter.media.MediaRouterParams; import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.Utils; @@ -215,9 +213,8 @@ public class MediaOutputController implements LocalMediaManager.DeviceCallback, } boolean shouldShowLaunchSection() { - MediaRouterParams routerParams = MediaRouter.getInstance(mContext).getRouterParams(); - Log.d(TAG, "try to get routerParams: " + routerParams); - return routerParams != null && !routerParams.isMediaTransferReceiverEnabled(); + // TODO(b/231398073): Implements this when available. + return false; } void setRefreshing(boolean refreshing) { diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java index 9248433a4a90..026a3055b01b 100644 --- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java +++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialog.java @@ -104,7 +104,7 @@ public class MediaOutputDialog extends MediaOutputBaseDialog { @Override public CharSequence getStopButtonText() { - int resId = R.string.keyboard_key_media_stop; + int resId = R.string.media_output_dialog_button_stop_casting; if (isBroadcastSupported() && mMediaOutputController.isPlaying() && !mMediaOutputController.isBluetoothLeBroadcastEnabled()) { resId = R.string.media_output_broadcast; diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index c7cd48fd5adf..3c373f447c34 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -70,6 +70,7 @@ import android.graphics.Insets; import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.RectF; +import android.graphics.Region; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -91,6 +92,8 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.InternalInsetsInfo; +import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import android.view.WindowInsetsController.Appearance; import android.view.WindowInsetsController.Behavior; import android.view.WindowManager; @@ -121,10 +124,12 @@ import com.android.systemui.navigationbar.buttons.ButtonDispatcher; import com.android.systemui.navigationbar.buttons.DeadZone; import com.android.systemui.navigationbar.buttons.KeyButtonView; import com.android.systemui.navigationbar.buttons.RotationContextButton; +import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; +import com.android.systemui.settings.UserContextProvider; import com.android.systemui.shared.recents.utilities.Utilities; import com.android.systemui.shared.rotation.RotationButton; import com.android.systemui.shared.rotation.RotationButtonController; @@ -152,6 +157,7 @@ import com.android.wm.shell.pip.Pip; import java.io.PrintWriter; import java.util.Locale; +import java.util.Map; import java.util.Optional; import java.util.function.Consumer; @@ -198,11 +204,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final Optional<Recents> mRecentsOptional; private final DeviceConfigProxy mDeviceConfigProxy; private final NavigationBarTransitions mNavigationBarTransitions; + private final EdgeBackGestureHandler mEdgeBackGestureHandler; private final Optional<BackAnimation> mBackAnimation; private final Handler mHandler; private final UiEventLogger mUiEventLogger; private final NavBarHelper mNavBarHelper; private final NotificationShadeDepthController mNotificationShadeDepthController; + private final UserContextProvider mUserContextProvider; + private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener; private NavigationBarFrame mFrame; private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING; @@ -514,7 +523,9 @@ public class NavigationBar extends ViewController<NavigationBarView> implements DeadZone deadZone, DeviceConfigProxy deviceConfigProxy, NavigationBarTransitions navigationBarTransitions, - Optional<BackAnimation> backAnimation) { + EdgeBackGestureHandler edgeBackGestureHandler, + Optional<BackAnimation> backAnimation, + UserContextProvider userContextProvider) { super(navigationBarView); mFrame = navigationBarFrame; mContext = context; @@ -539,6 +550,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mDeadZone = deadZone; mDeviceConfigProxy = deviceConfigProxy; mNavigationBarTransitions = navigationBarTransitions; + mEdgeBackGestureHandler = edgeBackGestureHandler; mBackAnimation = backAnimation; mHandler = mainHandler; mUiEventLogger = uiEventLogger; @@ -550,7 +562,31 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mAutoHideControllerFactory = autoHideControllerFactory; mTelecomManagerOptional = telecomManagerOptional; mInputMethodManager = inputMethodManager; + mUserContextProvider = userContextProvider; + + mOnComputeInternalInsetsListener = info -> { + // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully + // gestural mode, the entire nav bar should be touchable. + if (!mEdgeBackGestureHandler.isHandlingGestures()) { + // We're in 2/3 button mode OR back button force-shown in SUW + if (!mImeVisible) { + // IME not showing, take all touches + info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); + } + if (!mView.isImeRenderingNavButtons()) { + // IME showing but not drawing any buttons, take all touches + info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); + } + } + // When in gestural and the IME is showing, don't use the nearest region since it will + // take gesture space away from the IME + info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); + info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */, + false /* inScreen */, false /* useNearestRegion */)); + }; + + mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler); mNavBarMode = mNavigationModeController.addListener(mModeChangedListener); } @@ -565,6 +601,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mView.setBarTransitions(mNavigationBarTransitions); mView.setTouchHandler(mTouchHandler); mView.setNavBarMode(mNavBarMode); + mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates); mView.updateRotationButton(); mView.setVisibility( @@ -642,11 +679,14 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mView.setNavBarMode(mNavBarMode); mView.setUpdateActiveTouchRegionsCallback( () -> mOverviewProxyService.onActiveNavBarRegionChanges( - mView.getButtonLocations( + getButtonLocations( true /* includeFloatingButtons */, true /* inScreen */, true /* useNearestRegion */))); + mView.getViewTreeObserver().addOnComputeInternalInsetsListener( + mOnComputeInternalInsetsListener); + mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater); mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener); @@ -717,6 +757,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener( mOrientationHandleGlobalLayoutListener); } + mView.getViewTreeObserver().removeOnComputeInternalInsetsListener( + mOnComputeInternalInsetsListener); mHandler.removeCallbacks(mAutoDim); mHandler.removeCallbacks(mOnVariableDurationHomeLongClick); mHandler.removeCallbacks(mEnableLayoutTransitions); @@ -1040,8 +1082,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } private void handleTransientChanged() { - mView.onTransientStateChanged(mTransientShown, - mTransientShownFromGestureOnSystemBar); + mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTransientShown); + final int transitionMode = transitionMode(mTransientShown, mAppearance); if (updateTransitionMode(transitionMode) && mLightBarController != null) { mLightBarController.onNavigationBarModeChanged(transitionMode); @@ -1512,35 +1554,36 @@ public class NavigationBar extends ViewController<NavigationBarView> implements int insetsHeight = -1; int gravity = Gravity.BOTTOM; boolean navBarCanMove = true; + final Context userContext = mUserContextProvider.createCurrentUserContext(mContext); if (mWindowManager != null && mWindowManager.getCurrentWindowMetrics() != null) { Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds(); navBarCanMove = displaySize.width() != displaySize.height() - && mContext.getResources().getBoolean( + && userContext.getResources().getBoolean( com.android.internal.R.bool.config_navBarCanMove); } if (!navBarCanMove) { - height = mContext.getResources().getDimensionPixelSize( + height = userContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_frame_height); - insetsHeight = mContext.getResources().getDimensionPixelSize( + insetsHeight = userContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_height); } else { switch (rotation) { case ROTATION_UNDEFINED: case Surface.ROTATION_0: case Surface.ROTATION_180: - height = mContext.getResources().getDimensionPixelSize( + height = userContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_frame_height); - insetsHeight = mContext.getResources().getDimensionPixelSize( + insetsHeight = userContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_height); break; case Surface.ROTATION_90: gravity = Gravity.RIGHT; - width = mContext.getResources().getDimensionPixelSize( + width = userContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_width); break; case Surface.ROTATION_270: gravity = Gravity.LEFT; - width = mContext.getResources().getDimensionPixelSize( + width = userContext.getResources().getDimensionPixelSize( com.android.internal.R.dimen.navigation_bar_width); break; } @@ -1565,12 +1608,12 @@ public class NavigationBar extends ViewController<NavigationBarView> implements lp.providedInternalInsets[ITYPE_NAVIGATION_BAR] = null; } lp.token = new Binder(); - lp.accessibilityTitle = mContext.getString(R.string.nav_bar); + lp.accessibilityTitle = userContext.getString(R.string.nav_bar); lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT; lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; lp.windowAnimations = 0; - lp.setTitle("NavigationBar" + mContext.getDisplayId()); + lp.setTitle("NavigationBar" + userContext.getDisplayId()); lp.setFitInsetsTypes(0 /* types */); lp.setTrustedOverlay(); return lp; @@ -1634,6 +1677,79 @@ public class NavigationBar extends ViewController<NavigationBarView> implements mNavigationIconHints = hints; } + /** + * @param includeFloatingButtons Whether to include the floating rotation and overlay button in + * the region for all the buttons + * @param inScreenSpace Whether to return values in screen space or window space + * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds + * @return + */ + Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace, + boolean useNearestRegion) { + if (useNearestRegion && !inScreenSpace) { + // We currently don't support getting the nearest region in anything but screen space + useNearestRegion = false; + } + Region region = new Region(); + Map<View, Rect> touchRegionCache = mView.getButtonTouchRegionCache(); + updateButtonLocation( + region, touchRegionCache, mView.getBackButton(), inScreenSpace, useNearestRegion); + updateButtonLocation( + region, touchRegionCache, mView.getHomeButton(), inScreenSpace, useNearestRegion); + updateButtonLocation(region, touchRegionCache, mView.getRecentsButton(), inScreenSpace, + useNearestRegion); + updateButtonLocation(region, touchRegionCache, mView.getImeSwitchButton(), inScreenSpace, + useNearestRegion); + updateButtonLocation( + region, touchRegionCache, mView.getAccessibilityButton(), inScreenSpace, + useNearestRegion); + if (includeFloatingButtons && mView.getFloatingRotationButton().isVisible()) { + // Note: this button is floating so the nearest region doesn't apply + updateButtonLocation( + region, mView.getFloatingRotationButton().getCurrentView(), inScreenSpace); + } else { + updateButtonLocation(region, touchRegionCache, mView.getRotateSuggestionButton(), + inScreenSpace, useNearestRegion); + } + return region; + } + + private void updateButtonLocation( + Region region, + Map<View, Rect> touchRegionCache, + ButtonDispatcher button, + boolean inScreenSpace, + boolean useNearestRegion) { + if (button == null) { + return; + } + View view = button.getCurrentView(); + if (view == null || !button.isVisible()) { + return; + } + // If the button is tappable from perspective of NearestTouchFrame, then we'll + // include the regions where the tap is valid instead of just the button layout location + if (useNearestRegion && touchRegionCache.containsKey(view)) { + region.op(touchRegionCache.get(view), Region.Op.UNION); + return; + } + updateButtonLocation(region, view, inScreenSpace); + } + + private void updateButtonLocation(Region region, View view, boolean inScreenSpace) { + Rect bounds = new Rect(); + if (inScreenSpace) { + view.getBoundsOnScreen(bounds); + } else { + int[] location = new int[2]; + view.getLocationInWindow(location); + bounds.set(location[0], location[1], + location[0] + view.getWidth(), + location[1] + view.getHeight()); + } + region.op(bounds, Region.Op.UNION); + } + private final ModeChangedListener mModeChangedListener = new ModeChangedListener() { @Override public void onNavigationModeChanged(int mode) { diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java index 93bf136e88f1..f6bfd6c8f9c5 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarModule.java @@ -24,6 +24,7 @@ import android.view.WindowManager; import com.android.systemui.R; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope; +import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import dagger.Module; import dagger.Provides; @@ -55,6 +56,14 @@ public interface NavigationBarModule { return barView.findViewById(R.id.navigation_bar_view); } + /** */ + @Provides + @NavigationBarScope + static EdgeBackGestureHandler provideEdgeBackGestureHandler( + EdgeBackGestureHandler.Factory factory, @DisplayId Context context) { + return factory.create(context); + } + /** A WindowManager specific to the display's context. */ @Provides @NavigationBarScope diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java index 6e00ebc1dc5d..a13c199df41e 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBarView.java @@ -41,8 +41,6 @@ import android.content.res.Configuration; import android.graphics.Canvas; import android.graphics.Point; import android.graphics.Rect; -import android.graphics.Region; -import android.graphics.Region.Op; import android.os.Bundle; import android.util.AttributeSet; import android.util.Log; @@ -53,8 +51,6 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver.InternalInsetsInfo; -import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; import android.view.WindowInsets; import android.view.WindowInsetsController.Behavior; import android.view.WindowManager; @@ -93,7 +89,6 @@ import com.android.wm.shell.back.BackAnimation; import com.android.wm.shell.pip.Pip; import java.io.PrintWriter; -import java.util.HashMap; import java.util.Map; import java.util.Optional; import java.util.concurrent.Executor; @@ -123,11 +118,6 @@ public class NavigationBarView extends FrameLayout { private int mNavBarMode; private boolean mImeDrawsImeNavBar; - private final Region mTmpRegion = new Region(); - private final int[] mTmpPosition = new int[2]; - private Rect mTmpBounds = new Rect(); - private Map<View, Rect> mButtonFullTouchableRegions = new HashMap<>(); - private KeyButtonDrawable mBackIcon; private KeyButtonDrawable mHomeDefaultIcon; private KeyButtonDrawable mRecentIcon; @@ -138,7 +128,6 @@ public class NavigationBarView extends FrameLayout { private EdgeBackGestureHandler mEdgeBackGestureHandler; private final DeadZone mDeadZone; - private boolean mDeadZoneConsuming = false; private NavigationBarTransitions mBarTransitions; @Nullable private AutoHideController mAutoHideController; @@ -152,7 +141,6 @@ public class NavigationBarView extends FrameLayout { private boolean mUseCarModeUi = false; private boolean mInCarMode = false; private boolean mDockedStackExists; - private boolean mImeVisible; private boolean mScreenOn = true; private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>(); @@ -272,31 +260,6 @@ public class NavigationBarView extends FrameLayout { } }; - private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> { - // When the nav bar is in 2-button or 3-button mode, or when the back button is force-shown - // while in gesture nav in SUW, the entire nav bar should be touchable. - if (!mEdgeBackGestureHandler.isHandlingGestures()) { - // We're in 2/3 button mode OR back button force-shown in SUW - if (!mImeVisible) { - // IME not showing, take all touches - info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); - return; - } - - if (!isImeRenderingNavButtons()) { - // IME showing but not drawing any buttons, take all touches - info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME); - return; - } - } - - // When in gestural and the IME is showing, don't use the nearest region since it will take - // gesture space away from the IME - info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION); - info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */, - false /* inScreen */, false /* useNearestRegion */)); - }; - private final RotationButtonUpdatesCallback mRotationButtonListener = new RotationButtonUpdatesCallback() { @Override @@ -315,13 +278,6 @@ public class NavigationBarView extends FrameLayout { } }; - private final Consumer<Boolean> mNavbarOverlayVisibilityChangeCallback = (visible) -> { - if (visible && mAutoHideController != null) { - mAutoHideController.touchAutoHide(); - } - notifyActiveTouchRegions(); - }; - public NavigationBarView(Context context, AttributeSet attrs) { super(context, attrs); @@ -380,9 +336,6 @@ public class NavigationBarView extends FrameLayout { mNavColorSampleMargin = getResources() .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin); - mEdgeBackGestureHandler = Dependency.get(EdgeBackGestureHandler.Factory.class) - .create(mContext); - mEdgeBackGestureHandler.setStateChangeCallback(this::updateStates); Executor backgroundExecutor = Dependency.get(Dependency.BACKGROUND_EXECUTOR); mRegionSamplingHelper = new RegionSamplingHelper(this, new RegionSamplingHelper.SamplingCallback() { @@ -409,6 +362,10 @@ public class NavigationBarView extends FrameLayout { }, backgroundExecutor); } + public void setEdgeBackGestureHandler(EdgeBackGestureHandler edgeBackGestureHandler) { + mEdgeBackGestureHandler = edgeBackGestureHandler; + } + void setBarTransitions(NavigationBarTransitions navigationBarTransitions) { mBarTransitions = navigationBarTransitions; } @@ -681,8 +638,7 @@ public class NavigationBarView extends FrameLayout { if (!visible) { mTransitionListener.onBackAltCleared(); } - mImeVisible = visible; - mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible); + mRotationButtonController.getRotationButton().setCanShowRotationButton(!visible); } void setDisabledFlags(int disabledFlags, SysUiState sysUiState) { @@ -774,7 +730,7 @@ public class NavigationBarView extends FrameLayout { /** * Returns whether the IME is currently visible and drawing the nav buttons. */ - private boolean isImeRenderingNavButtons() { + boolean isImeRenderingNavButtons() { return mImeDrawsImeNavBar && mImeCanRenderGesturalNavButtons && (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0; @@ -1003,75 +959,14 @@ public class NavigationBarView extends FrameLayout { notifyActiveTouchRegions(); } - private void updateButtonTouchRegionCache() { + Map<View, Rect> getButtonTouchRegionCache() { FrameLayout navBarLayout = mIsVertical ? mNavigationInflaterView.mVertical : mNavigationInflaterView.mHorizontal; - mButtonFullTouchableRegions = ((NearestTouchFrame) navBarLayout + return ((NearestTouchFrame) navBarLayout .findViewById(R.id.nav_buttons)).getFullTouchableChildRegions(); } - /** - * @param includeFloatingButtons Whether to include the floating rotation and overlay button in - * the region for all the buttons - * @param inScreenSpace Whether to return values in screen space or window space - * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds - * @return - */ - Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace, - boolean useNearestRegion) { - // TODO: move this method to NavigationBar. - // TODO: don't use member variables for temp storage like mTmpRegion. - if (useNearestRegion && !inScreenSpace) { - // We currently don't support getting the nearest region in anything but screen space - useNearestRegion = false; - } - mTmpRegion.setEmpty(); - updateButtonTouchRegionCache(); - updateButtonLocation(getBackButton(), inScreenSpace, useNearestRegion); - updateButtonLocation(getHomeButton(), inScreenSpace, useNearestRegion); - updateButtonLocation(getRecentsButton(), inScreenSpace, useNearestRegion); - updateButtonLocation(getImeSwitchButton(), inScreenSpace, useNearestRegion); - updateButtonLocation(getAccessibilityButton(), inScreenSpace, useNearestRegion); - if (includeFloatingButtons && mFloatingRotationButton.isVisible()) { - // Note: this button is floating so the nearest region doesn't apply - updateButtonLocation(mFloatingRotationButton.getCurrentView(), inScreenSpace); - } else { - updateButtonLocation(getRotateSuggestionButton(), inScreenSpace, useNearestRegion); - } - return mTmpRegion; - } - - private void updateButtonLocation(ButtonDispatcher button, boolean inScreenSpace, - boolean useNearestRegion) { - if (button == null) { - return; - } - View view = button.getCurrentView(); - if (view == null || !button.isVisible()) { - return; - } - // If the button is tappable from perspective of NearestTouchFrame, then we'll - // include the regions where the tap is valid instead of just the button layout location - if (useNearestRegion && mButtonFullTouchableRegions.containsKey(view)) { - mTmpRegion.op(mButtonFullTouchableRegions.get(view), Op.UNION); - return; - } - updateButtonLocation(view, inScreenSpace); - } - - private void updateButtonLocation(View view, boolean inScreenSpace) { - if (inScreenSpace) { - view.getBoundsOnScreen(mTmpBounds); - } else { - view.getLocationInWindow(mTmpPosition); - mTmpBounds.set(mTmpPosition[0], mTmpPosition[1], - mTmpPosition[0] + view.getWidth(), - mTmpPosition[1] + view.getHeight()); - } - mTmpRegion.op(mTmpBounds, Op.UNION); - } - private void updateOrientationViews() { mHorizontal = findViewById(R.id.horizontal); mVertical = findViewById(R.id.vertical); @@ -1272,7 +1167,6 @@ public class NavigationBarView extends FrameLayout { mRotationButtonController.registerListeners(); } - getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener); updateNavButtonIcons(); } @@ -1288,8 +1182,6 @@ public class NavigationBarView extends FrameLayout { } mEdgeBackGestureHandler.onNavBarDetached(); - getViewTreeObserver().removeOnComputeInternalInsetsListener( - mOnComputeInternalInsetsListener); } public void dump(PrintWriter pw) { diff --git a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java index 8b8941a9112d..3709a86f2fa5 100644 --- a/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java +++ b/packages/SystemUI/src/com/android/systemui/power/dagger/PowerModule.java @@ -16,6 +16,8 @@ package com.android.systemui.power.dagger; +import com.android.systemui.power.EnhancedEstimates; +import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.power.PowerNotificationWarnings; import com.android.systemui.power.PowerUI; @@ -28,5 +30,9 @@ import dagger.Module; public interface PowerModule { /** */ @Binds + EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates); + + /** */ + @Binds PowerUI.WarningsUI provideWarningsUi(PowerNotificationWarnings controllerImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt index 3eb4b10151d9..3e8cdf3a3592 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt @@ -420,7 +420,7 @@ class FgsManagerController @Inject constructor( PowerExemptionManager.REASON_SYSTEM_UID, PowerExemptionManager.REASON_DEVICE_DEMO_MODE -> UIControl.HIDE_ENTRY - PowerExemptionManager.REASON_ALLOWLISTED_PACKAGE, + PowerExemptionManager.REASON_SYSTEM_ALLOW_LISTED, PowerExemptionManager.REASON_DEVICE_OWNER, PowerExemptionManager.REASON_DISALLOW_APPS_CONTROL, PowerExemptionManager.REASON_DPO_PROTECTED_APP, diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java index 7d3df6ef6b07..5d2060d8043e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSContainerImpl.java @@ -47,9 +47,10 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { private QSCustomizer mQSCustomizer; private NonInterceptingScrollView mQSPanelContainer; - private int mSideMargins; + private int mHorizontalMargins; + private int mTilesPageMargin; private boolean mQsDisabled; - private int mContentPadding = -1; + private int mContentHorizontalPadding = -1; private boolean mClippingEnabled; public QSContainerImpl(Context context, AttributeSet attrs) { @@ -145,12 +146,17 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { mQSPanelContainer.getPaddingEnd(), bottomPadding); - int sideMargins = getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); - int padding = getResources().getDimensionPixelSize( - R.dimen.notification_shade_content_margin_horizontal); - boolean marginsChanged = padding != mContentPadding || sideMargins != mSideMargins; - mContentPadding = padding; - mSideMargins = sideMargins; + int horizontalMargins = getResources().getDimensionPixelSize(R.dimen.qs_horizontal_margin); + int horizontalPadding = getResources().getDimensionPixelSize( + R.dimen.qs_content_horizontal_padding); + int tilesPageMargin = getResources().getDimensionPixelSize( + R.dimen.qs_tiles_page_horizontal_margin); + boolean marginsChanged = horizontalPadding != mContentHorizontalPadding + || horizontalMargins != mHorizontalMargins + || tilesPageMargin != mTilesPageMargin; + mContentHorizontalPadding = horizontalPadding; + mHorizontalMargins = horizontalMargins; + mTilesPageMargin = tilesPageMargin; if (marginsChanged) { updatePaddingsAndMargins(qsPanelController, quickStatusBarHeaderController); } @@ -198,22 +204,22 @@ public class QSContainerImpl extends FrameLayout implements Dumpable { // Only padding for FooterActionsView, no margin. That way, the background goes // all the way to the edge. LayoutParams lp = (LayoutParams) view.getLayoutParams(); - lp.rightMargin = mSideMargins; - lp.leftMargin = mSideMargins; + lp.rightMargin = mHorizontalMargins; + lp.leftMargin = mHorizontalMargins; } if (view == mQSPanelContainer) { // QS panel lays out some of its content full width - qsPanelController.setContentMargins(mContentPadding, mContentPadding); - // Set it as double the side margin (to simulate end margin of current page + - // start margin of next page). - qsPanelController.setPageMargin(mSideMargins); + qsPanelController.setContentMargins(mContentHorizontalPadding, + mContentHorizontalPadding); + qsPanelController.setPageMargin(mTilesPageMargin); } else if (view == mHeader) { - quickStatusBarHeaderController.setContentMargins(mContentPadding, mContentPadding); + quickStatusBarHeaderController.setContentMargins(mContentHorizontalPadding, + mContentHorizontalPadding); } else { view.setPaddingRelative( - mContentPadding, + mContentHorizontalPadding, view.getPaddingTop(), - mContentPadding, + mContentHorizontalPadding, view.getPaddingBottom()); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java index 772e9faf6feb..248c78e557cc 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/QuickAccessWalletTile.java @@ -147,16 +147,9 @@ public class QuickAccessWalletTile extends QSTileImpl<QSTile.State> { if (mController.getWalletClient().isWalletServiceAvailable() && mController.getWalletClient().isWalletFeatureAvailable()) { if (mSelectedCard != null) { - if (isDeviceLocked) { - state.state = Tile.STATE_INACTIVE; - state.secondaryLabel = - mContext.getString(R.string.wallet_secondary_label_device_locked); - state.sideViewCustomDrawable = null; - } else { - state.state = Tile.STATE_ACTIVE; - state.secondaryLabel = mSelectedCard.getContentDescription(); - state.sideViewCustomDrawable = mCardViewDrawable; - } + state.state = isDeviceLocked ? Tile.STATE_INACTIVE : Tile.STATE_ACTIVE; + state.secondaryLabel = mSelectedCard.getContentDescription(); + state.sideViewCustomDrawable = mCardViewDrawable; } else { state.state = Tile.STATE_INACTIVE; state.secondaryLabel = diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java index f4dd415cb678..d99c1d1daf7e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/SensorPrivacyToggleTile.java @@ -92,15 +92,15 @@ public abstract class SensorPrivacyToggleTile extends QSTileImpl<QSTile.BooleanS @Override protected void handleClick(@Nullable View view) { - if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) { - mActivityStarter.postQSRunnableDismissingKeyguard(() -> { - mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), - !mSensorPrivacyController.isSensorBlocked(getSensorId())); - }); + boolean blocked = mSensorPrivacyController.isSensorBlocked(getSensorId()); + if (mSensorPrivacyController.requiresAuthentication() + && mKeyguard.isMethodSecure() + && mKeyguard.isShowing()) { + mActivityStarter.postQSRunnableDismissingKeyguard(() -> + mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), !blocked)); return; } - mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), - !mSensorPrivacyController.isSensorBlocked(getSensorId())); + mSensorPrivacyController.setSensorBlocked(QS_TILE, getSensorId(), !blocked); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt index dae375ad7cc7..2d1d8b75b1bc 100644 --- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt +++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/SensorUseStartedActivity.kt @@ -134,7 +134,9 @@ class SensorUseStartedActivity @Inject constructor( override fun onClick(dialog: DialogInterface?, which: Int) { when (which) { BUTTON_POSITIVE -> { - if (keyguardStateController.isMethodSecure && keyguardStateController.isShowing) { + if (sensorPrivacyController.requiresAuthentication() && + keyguardStateController.isMethodSecure && + keyguardStateController.isShowing) { keyguardDismissUtil.executeWhenUnlocked({ bgHandler.postDelayed({ disableSensorPrivacy() diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt index 27af15222327..dae851252271 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserContextProvider.kt @@ -23,4 +23,10 @@ import android.content.Context */ interface UserContextProvider { val userContext: Context + + /** + * Creates the {@code context} with the current user. + * @see Context#createContextAsUser(UserHandle, int) + */ + fun createCurrentUserContext(context: Context): Context }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt index 3a6248b793d0..80d5f1681a79 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt @@ -127,6 +127,12 @@ class UserTrackerImpl internal constructor( } } + override fun createCurrentUserContext(context: Context): Context { + synchronized(mutex) { + return context.createContextAsUser(userHandle, 0) + } + } + private fun setUserIdInternal(user: Int): Pair<Context, List<UserInfo>> { val profiles = userManager.getProfiles(user) val handle = UserHandle(user) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java index ebd610bb0af4..0c9e1ec1ff77 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShadeWindowController.java @@ -180,13 +180,6 @@ public interface NotificationShadeWindowController extends RemoteInputController default void setRequestTopUi(boolean requestTopUi, String componentTag) {} /** - * Under low light conditions, we might want to increase the display brightness on devices that - * don't have an IR camera. - * @param brightness float from 0 to 1 or {@code LayoutParams.BRIGHTNESS_OVERRIDE_NONE} - */ - default void setFaceAuthDisplayBrightness(float brightness) {} - - /** * If {@link LightRevealScrim} obscures the UI. * @param opaque if the scrim is opaque */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt index a390e9f9b09d..15ad312b413e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinator.kt @@ -20,11 +20,13 @@ import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.coordinator.dagger.CoordinatorScope +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController import com.android.systemui.statusbar.notification.dagger.PeopleHeader +import com.android.systemui.statusbar.notification.icon.ConversationIconManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.PeopleNotificationType import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_NON_PERSON @@ -39,12 +41,40 @@ import javax.inject.Inject @CoordinatorScope class ConversationCoordinator @Inject constructor( private val peopleNotificationIdentifier: PeopleNotificationIdentifier, + private val conversationIconManager: ConversationIconManager, @PeopleHeader peopleHeaderController: NodeController ) : Coordinator { + private val promotedEntriesToSummaryOfSameChannel = + mutableMapOf<NotificationEntry, NotificationEntry>() + + private val onBeforeRenderListListener = OnBeforeRenderListListener { _ -> + val unimportantSummaries = promotedEntriesToSummaryOfSameChannel + .mapNotNull { (promoted, summary) -> + val originalGroup = summary.parent + when { + originalGroup == null -> null + originalGroup == promoted.parent -> null + originalGroup.parent == null -> null + originalGroup.summary != summary -> null + originalGroup.children.any { it.channel == summary.channel } -> null + else -> summary.key + } + } + conversationIconManager.setUnimportantConversations(unimportantSummaries) + promotedEntriesToSummaryOfSameChannel.clear() + } + private val notificationPromoter = object : NotifPromoter(TAG) { override fun shouldPromoteToTopLevel(entry: NotificationEntry): Boolean { - return entry.channel?.isImportantConversation == true + val shouldPromote = entry.channel?.isImportantConversation == true + if (shouldPromote) { + val summary = entry.parent?.summary + if (summary != null && entry.channel == summary.channel) { + promotedEntriesToSummaryOfSameChannel[entry] = summary + } + } + return shouldPromote } } @@ -67,6 +97,7 @@ class ConversationCoordinator @Inject constructor( override fun attach(pipeline: NotifPipeline) { pipeline.addPromoter(notificationPromoter) + pipeline.addOnBeforeRenderListListener(onBeforeRenderListListener) } private fun isConversation(entry: ListEntry): Boolean = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java index 34c8044ef0d3..d96590a82547 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java @@ -70,6 +70,8 @@ import com.android.systemui.statusbar.notification.collection.render.GroupMember import com.android.systemui.statusbar.notification.collection.render.NotifGutsViewManager; import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource; import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider; +import com.android.systemui.statusbar.notification.icon.ConversationIconManager; +import com.android.systemui.statusbar.notification.icon.IconManager; import com.android.systemui.statusbar.notification.init.NotificationsController; import com.android.systemui.statusbar.notification.init.NotificationsControllerImpl; import com.android.systemui.statusbar.notification.init.NotificationsControllerStub; @@ -370,6 +372,10 @@ public interface NotificationsModule { /** */ @Binds + ConversationIconManager bindConversationIconManager(IconManager iconManager); + + /** */ + @Binds BindEventManager bindBindEventManagerImpl(BindEventManagerImpl bindEventManagerImpl); /** */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt index 5375ac345e50..d8965418b4c4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/IconManager.kt @@ -27,6 +27,7 @@ import android.view.View import android.widget.ImageView import com.android.internal.statusbar.StatusBarIcon import com.android.systemui.R +import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.notification.InflationException import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -44,11 +45,14 @@ import javax.inject.Inject * TODO: Much of this code was copied whole-sale in order to get it out of NotificationEntry. * Long-term, it should probably live somewhere in the content inflation pipeline. */ +@SysUISingleton class IconManager @Inject constructor( private val notifCollection: CommonNotifCollection, private val launcherApps: LauncherApps, private val iconBuilder: IconBuilder -) { +) : ConversationIconManager { + private var unimportantConversationKeys: Set<String> = emptySet() + fun attach() { notifCollection.addCollectionListener(entryListener) } @@ -63,16 +67,8 @@ class IconManager @Inject constructor( } override fun onRankingApplied() { - // When the sensitivity changes OR when the isImportantConversation status changes, - // we need to update the icons - for (entry in notifCollection.allNotifs) { - val isImportant = isImportantConversation(entry) - if (entry.icons.areIconsAvailable && - isImportant != entry.icons.isImportantConversation) { - updateIconsSafe(entry) - } - entry.icons.isImportantConversation = isImportant - } + // rankings affect whether a conversation is important, which can change the icons + recalculateForImportantConversationChange() } } @@ -80,6 +76,18 @@ class IconManager @Inject constructor( entry -> updateIconsSafe(entry) } + private fun recalculateForImportantConversationChange() { + for (entry in notifCollection.allNotifs) { + val isImportant = isImportantConversation(entry) + if (entry.icons.areIconsAvailable && + isImportant != entry.icons.isImportantConversation + ) { + updateIconsSafe(entry) + } + entry.icons.isImportantConversation = isImportant + } + } + /** * Inflate icon views for each icon variant and assign appropriate icons to them. Stores the * result in [NotificationEntry.getIcons]. @@ -306,8 +314,28 @@ class IconManager @Inject constructor( } private fun isImportantConversation(entry: NotificationEntry): Boolean { - return entry.ranking.channel != null && entry.ranking.channel.isImportantConversation + return entry.ranking.channel != null && + entry.ranking.channel.isImportantConversation && + entry.key !in unimportantConversationKeys + } + + override fun setUnimportantConversations(keys: Collection<String>) { + val newKeys = keys.toSet() + val changed = unimportantConversationKeys != newKeys + unimportantConversationKeys = newKeys + if (changed) { + recalculateForImportantConversationChange() + } } } -private const val TAG = "IconManager"
\ No newline at end of file +private const val TAG = "IconManager" + +interface ConversationIconManager { + /** + * Sets the complete current set of notification keys which should (for the purposes of icon + * presentation) be considered unimportant. This tells the icon manager to remove the avatar + * of a group from which the priority notification has been removed. + */ + fun setUnimportantConversations(keys: Collection<String>) +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt index 5646545dcd23..cff42f29a626 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProvider.kt @@ -18,6 +18,8 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.statusbar.NotificationLockscreenUserManager +import com.android.systemui.statusbar.StatusBarState +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.notification.collection.ListEntry import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.provider.HighPriorityProvider @@ -72,7 +74,7 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( private val lockscreenUserManager: NotificationLockscreenUserManager, private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val highPriorityProvider: HighPriorityProvider, - private val statusBarStateController: StatusBarStateController, + private val statusBarStateController: SysuiStatusBarStateController, private val broadcastDispatcher: BroadcastDispatcher, private val secureSettings: SecureSettings, private val globalSettings: GlobalSettings @@ -105,7 +107,8 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( if (uri == showSilentNotifsUri) { readShowSilentNotificationSetting() } - if (keyguardStateController.isShowing) { + if (statusBarStateController.getCurrentOrUpcomingState() + == StatusBarState.KEYGUARD) { notifyStateChanged("Settings $uri changed") } } @@ -131,13 +134,14 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( // register (maybe) public mode changed callbacks: statusBarStateController.addCallback(object : StatusBarStateController.StateListener { - override fun onStateChanged(state: Int) { - notifyStateChanged("onStatusBarStateChanged") + override fun onUpcomingStateChanged(state: Int) { + notifyStateChanged("onStatusBarUpcomingStateChanged") } }) broadcastDispatcher.registerReceiver(object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { - if (keyguardStateController.isShowing) { + if (statusBarStateController.getCurrentOrUpcomingState() + == StatusBarState.KEYGUARD) { // maybe public mode changed notifyStateChanged(intent.action!!) } @@ -159,17 +163,28 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( override fun shouldHideNotification(entry: NotificationEntry): Boolean = when { // Keyguard state doesn't matter if the keyguard is not showing. - !keyguardStateController.isShowing -> false + statusBarStateController.getCurrentOrUpcomingState() != StatusBarState.KEYGUARD -> false // Notifications not allowed on the lockscreen, always hide. !lockscreenUserManager.shouldShowLockscreenNotifications() -> true // User settings do not allow this notification on the lockscreen, so hide it. userSettingsDisallowNotification(entry) -> true + // if entry is silent, apply custom logic to see if should hide + shouldHideIfEntrySilent(entry) -> true + else -> false + } + + private fun shouldHideIfEntrySilent(entry: ListEntry): Boolean = when { + // Show if high priority (not hidden) + highPriorityProvider.isHighPriority(entry) -> false + // Ambient notifications are hidden always from lock screen + entry.representativeEntry?.isAmbient == true -> true + // [Now notification is silent] + // Hide regardless of parent priority if user wants silent notifs hidden + hideSilentNotificationsOnLockscreen -> true // Parent priority is high enough to be shown on the lockscreen, do not hide. - entry.parent?.let(::priorityExceedsLockscreenShowingThreshold) == true -> false - // Entry priority is high enough to be shown on the lockscreen, do not hide. - priorityExceedsLockscreenShowingThreshold(entry) -> false - // Priority is too low, hide. - else -> true + entry.parent?.let(::shouldHideIfEntrySilent) == false -> false + // Show when silent notifications are allowed on lockscreen + else -> false } private fun userSettingsDisallowNotification(entry: NotificationEntry): Boolean { @@ -193,11 +208,6 @@ private class KeyguardNotificationVisibilityProviderImpl @Inject constructor( } } - private fun priorityExceedsLockscreenShowingThreshold(entry: ListEntry): Boolean = when { - hideSilentNotificationsOnLockscreen -> highPriorityProvider.isHighPriority(entry) - else -> entry.representativeEntry?.ranking?.isAmbient == false - } - private fun readShowSilentNotificationSetting() { val showSilentNotifs = secureSettings.getBoolForUser(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index ef24d7793562..5c12671726f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1022,6 +1022,23 @@ public class CentralSurfacesImpl extends CoreStartable implements public void onUnlockedChanged() { logStateToEventlog(); } + + @Override + public void onKeyguardGoingAwayChanged() { + // The light reveal scrim should always be fully revealed by the time the keyguard + // is done going away. Double check that this is true. + if (!mKeyguardStateController.isKeyguardGoingAway()) { + if (mLightRevealScrim.getRevealAmount() != 1f) { + Log.e(TAG, "Keyguard is done going away, but someone left the light reveal " + + "scrim at reveal amount: " + mLightRevealScrim.getRevealAmount()); + } + + // If the auth ripple is still playing, let it finish. + if (!mAuthRippleController.isAnimatingLightRevealScrim()) { + mLightRevealScrim.setRevealAmount(1f); + } + } + } }); startKeyguard(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java index 69beaf56519f..0b721383e2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java @@ -220,7 +220,7 @@ public class KeyguardBouncer { DejankUtils.postAfterTraversal(mShowRunnable); } - mCallback.onBouncerVisiblityChanged(true /* shown */); + mKeyguardStateController.notifyBouncerShowing(true /* showing */); dispatchStartingToShow(); } finally { Trace.endSection(); @@ -334,7 +334,7 @@ public class KeyguardBouncer { } mIsScrimmed = false; mFalsingCollector.onBouncerHidden(); - mCallback.onBouncerVisiblityChanged(false /* shown */); + mKeyguardStateController.notifyBouncerShowing(false /* showing */); cancelShowRunnable(); if (mKeyguardViewController != null) { mKeyguardViewController.cancelDismissAction(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java index 83970dc15e61..629aa0317d24 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithm.java @@ -220,7 +220,7 @@ public class KeyguardClockPositionAlgorithm { } } - public float getMinStackScrollerPadding() { + public float getLockscreenMinStackScrollerPadding() { if (mBypassEnabled) { return mUnlockedStackScrollerPadding; } else if (mIsSplitShade) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java index 985df423d744..74b9c71d2198 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationPanelViewController.java @@ -24,6 +24,7 @@ import static androidx.constraintlayout.widget.ConstraintSet.PARENT_ID; import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; import static com.android.keyguard.KeyguardClockSwitch.LARGE; import static com.android.keyguard.KeyguardClockSwitch.SMALL; +import static com.android.systemui.animation.Interpolators.EMPHASIZED_ACCELERATE; import static com.android.systemui.animation.Interpolators.EMPHASIZED_DECELERATE; import static com.android.systemui.classifier.Classifier.QS_COLLAPSE; import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS; @@ -325,6 +326,11 @@ public class NotificationPanelViewController extends PanelViewController { private boolean mShouldUseSplitNotificationShade; // The bottom padding reserved for elements of the keyguard measuring notifications private float mKeyguardNotificationBottomPadding; + /** + * The top padding from where notification should start in lockscreen. + * Should be static also during animations and should match the Y of the first notification. + */ + private float mKeyguardNotificationTopPadding; // Current max allowed keyguard notifications determined by measuring the panel private int mMaxAllowedKeyguardNotifications; @@ -936,7 +942,7 @@ public class NotificationPanelViewController extends PanelViewController { // the launcher icons animation starts, so use that as our // duration. .setDuration(unlockAnimationStartDelay) - .setInterpolator(EMPHASIZED_DECELERATE) + .setInterpolator(EMPHASIZED_ACCELERATE) .withEndAction(() -> { instantCollapse(); mView.setAlpha(1f); @@ -1513,7 +1519,10 @@ public class NotificationPanelViewController extends PanelViewController { */ @VisibleForTesting float getSpaceForLockscreenNotifications() { - float topPadding = mNotificationStackScrollLayoutController.getTopPadding(); + float staticTopPadding = mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding() + // getMinStackScrollerPadding is from the top of the screen, + // but we need it from the top of the NSSL. + - mNotificationStackScrollLayoutController.getTop(); // Space between bottom of notifications and top of lock icon or udfps background. float lockIconPadding = mLockIconViewController.getTop(); @@ -1525,11 +1534,15 @@ public class NotificationPanelViewController extends PanelViewController { float bottomPadding = Math.max(lockIconPadding, Math.max(mIndicationBottomPadding, mAmbientIndicationBottomPadding)); + mKeyguardNotificationBottomPadding = bottomPadding; + mKeyguardNotificationTopPadding = staticTopPadding; + // To debug the available space, enable debug lines in this class. If you change how the + // available space is calculated, please also update those lines. float availableSpace = mNotificationStackScrollLayoutController.getHeight() - - topPadding + - staticTopPadding - bottomPadding; return availableSpace; } @@ -4923,6 +4936,20 @@ public class NotificationPanelViewController extends PanelViewController { drawDebugInfo(canvas, (int) mLockIconViewController.getTop(), Color.GRAY, "mLockIconViewController.getTop()"); + if (mKeyguardShowing) { + // Notifications have the space between those two lines. + drawDebugInfo(canvas, + mNotificationStackScrollLayoutController.getTop() + + (int) mKeyguardNotificationTopPadding, + Color.RED, + "NSSL.getTop() + mKeyguardNotificationTopPadding"); + + drawDebugInfo(canvas, mNotificationStackScrollLayoutController.getBottom() - + (int) mKeyguardNotificationBottomPadding, + Color.RED, + "NSSL.getBottom() - mKeyguardNotificationBottomPadding"); + } + mDebugPaint.setColor(Color.CYAN); canvas.drawLine(0, mClockPositionResult.stackScrollerPadding, mView.getWidth(), mNotificationStackScrollLayoutController.getTopPadding(), mDebugPaint); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 24660b261c51..faae4bbbafd0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java @@ -111,7 +111,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final SysuiColorExtractor mColorExtractor; private final ScreenOffAnimationController mScreenOffAnimationController; - private float mFaceAuthDisplayBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE; /** * Layout params would be aggregated and dispatched all at once if this is > 0. * @@ -266,12 +265,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mScreenBrightnessDoze = value / 255f; } - @Override - public void setFaceAuthDisplayBrightness(float brightness) { - mFaceAuthDisplayBrightness = brightness; - apply(mCurrentState); - } - private void setKeyguardDark(boolean dark) { int vis = mNotificationShadeView.getSystemUiVisibility(); if (dark) { @@ -340,7 +333,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } private void adjustScreenOrientation(State state) { - if (state.isKeyguardShowingAndNotOccluded() || state.mDozing) { + if (state.mBouncerShowing || state.isKeyguardShowingAndNotOccluded() || state.mDozing) { if (mKeyguardStateController.isKeyguardScreenRotationAllowed()) { mLpChanged.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; } else { @@ -455,7 +448,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private void applyWindowLayoutParams() { if (mDeferWindowLayoutParams == 0 && mLp != null && mLp.copyFrom(mLpChanged) != 0) { + Trace.beginSection("updateViewLayout"); mWindowManager.updateViewLayout(mNotificationShadeView, mLp); + Trace.endSection(); } } @@ -523,7 +518,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW if (state.mForceDozeBrightness) { mLpChanged.screenBrightness = mScreenBrightnessDoze; } else { - mLpChanged.screenBrightness = mFaceAuthDisplayBrightness; + mLpChanged.screenBrightness = LayoutParams.BRIGHTNESS_OVERRIDE_NONE; } } @@ -572,6 +567,10 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW @Override public void setPanelVisible(boolean visible) { + if (mCurrentState.mPanelVisible == visible + && mCurrentState.mNotificationShadeFocusable == visible) { + return; + } mCurrentState.mPanelVisible = visible; mCurrentState.mNotificationShadeFocusable = visible; apply(mCurrentState); @@ -626,8 +625,14 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW @Override public void setScrimsVisibility(int scrimsVisibility) { + if (scrimsVisibility == mCurrentState.mScrimsVisibility) { + return; + } + boolean wasExpanded = isExpanded(mCurrentState); mCurrentState.mScrimsVisibility = scrimsVisibility; - apply(mCurrentState); + if (wasExpanded != isExpanded(mCurrentState)) { + apply(mCurrentState); + } mScrimsVisibilityListener.accept(scrimsVisibility); } @@ -687,6 +692,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW @Override public void setPanelExpanded(boolean isExpanded) { + if (mCurrentState.mPanelExpanded == isExpanded) { + return; + } mCurrentState.mPanelExpanded = isExpanded; apply(mCurrentState); } @@ -703,6 +711,9 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW */ @Override public void setForceDozeBrightness(boolean forceDozeBrightness) { + if (mCurrentState.mForceDozeBrightness == forceDozeBrightness) { + return; + } mCurrentState.mForceDozeBrightness = forceDozeBrightness; apply(mCurrentState); } @@ -841,7 +852,6 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW boolean mLightRevealScrimOpaque; boolean mForceCollapsed; boolean mForceDozeBrightness; - int mFaceAuthDisplayBrightness; boolean mForceUserActivity; boolean mLaunchingActivity; boolean mBackdropShowing; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt index 935f87dc8221..c1d0769eaa44 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationController.kt @@ -175,28 +175,36 @@ class UnlockedScreenOffAnimationController @Inject constructor( .setDuration(duration.toLong()) .setInterpolator(Interpolators.FAST_OUT_SLOW_IN) .alpha(1f) - .setListener(object : AnimatorListenerAdapter() { - override fun onAnimationEnd(animation: Animator?) { - aodUiAnimationPlaying = false + .withEndAction { + aodUiAnimationPlaying = false + + // Lock the keyguard if it was waiting for the screen off animation to end. + keyguardViewMediatorLazy.get().maybeHandlePendingLock() - // Lock the keyguard if it was waiting for the screen off animation to end. - keyguardViewMediatorLazy.get().maybeHandlePendingLock() + // Tell the CentralSurfaces to become keyguard for real - we waited on that + // since it is slow and would have caused the animation to jank. + mCentralSurfaces.updateIsKeyguard() - // Tell the CentralSurfaces to become keyguard for real - we waited on that - // since it is slow and would have caused the animation to jank. - mCentralSurfaces.updateIsKeyguard() + // Run the callback given to us by the KeyguardVisibilityHelper. + after.run() - // Run the callback given to us by the KeyguardVisibilityHelper. - after.run() + // Done going to sleep, reset this flag. + decidedToAnimateGoingToSleep = null - // Done going to sleep, reset this flag. + // We need to unset the listener. These are persistent for future animators + keyguardView.animate().setListener(null) + interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) + } + .setListener(object : AnimatorListenerAdapter() { + override fun onAnimationCancel(animation: Animator?) { + // If we're cancelled, reset state flags/listeners. The end action above + // will not be called, which is what we want since that will finish the + // screen off animation and show the lockscreen, which we don't want if we + // were cancelled. + aodUiAnimationPlaying = false decidedToAnimateGoingToSleep = null - // We need to unset the listener. These are persistent for future animators keyguardView.animate().setListener(null) - interactionJankMonitor.end(CUJ_SCREEN_OFF_SHOW_AOD) - } - override fun onAnimationCancel(animation: Animator?) { interactionJankMonitor.cancel(CUJ_SCREEN_OFF_SHOW_AOD) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java index 1e73d593c8de..eb08f37503c6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyController.java @@ -37,6 +37,11 @@ public interface IndividualSensorPrivacyController extends void suppressSensorPrivacyReminders(int sensor, boolean suppress); + /** + * @return whether lock screen authentication is required to change the toggle state + */ + boolean requiresAuthentication(); + interface Callback { void onSensorBlockedChanged(@Sensor int sensor, boolean blocked); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java index e4c444da5da9..fffd839fcf11 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/IndividualSensorPrivacyControllerImpl.java @@ -37,6 +37,7 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr private final @NonNull SensorPrivacyManager mSensorPrivacyManager; private final SparseBooleanArray mSoftwareToggleState = new SparseBooleanArray(); private final SparseBooleanArray mHardwareToggleState = new SparseBooleanArray(); + private Boolean mRequiresAuthentication; private final Set<Callback> mCallbacks = new ArraySet<>(); public IndividualSensorPrivacyControllerImpl( @@ -96,6 +97,11 @@ public class IndividualSensorPrivacyControllerImpl implements IndividualSensorPr } @Override + public boolean requiresAuthentication() { + return mSensorPrivacyManager.requiresAuthentication(); + } + + @Override public void addCallback(@NonNull Callback listener) { mCallbacks.add(listener); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java index 233778dbfeb6..15ee553da457 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateController.java @@ -46,6 +46,11 @@ public interface KeyguardStateController extends CallbackController<Callback> { boolean isShowing(); /** + * Whether the bouncer (PIN/password entry) is currently visible. + */ + boolean isBouncerShowing(); + + /** * If swiping up will unlock without asking for a password. * @see #isUnlocked() */ @@ -186,6 +191,8 @@ public interface KeyguardStateController extends CallbackController<Callback> { default void notifyKeyguardDoneFading() {} /** **/ default void notifyKeyguardState(boolean showing, boolean occluded) {} + /** **/ + default void notifyBouncerShowing(boolean showing) {} /** * Updates the keyguard state to reflect that it's in the process of being dismissed, either by @@ -231,6 +238,11 @@ public interface KeyguardStateController extends CallbackController<Callback> { default void onKeyguardShowingChanged() {} /** + * Called when the bouncer (PIN/password entry) is shown or hidden. + */ + default void onBouncerShowingChanged() {} + + /** * Triggered when the device was just unlocked and the lock screen is being dismissed. */ default void onKeyguardFadingAwayChanged() {} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index be5da377e496..77e285d1e15d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -63,6 +63,7 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum private boolean mCanDismissLockScreen; private boolean mShowing; + private boolean mBouncerShowing; private boolean mSecure; private boolean mOccluded; @@ -153,6 +154,11 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override + public boolean isBouncerShowing() { + return mBouncerShowing; + } + + @Override public boolean isMethodSecure() { return mSecure; } @@ -328,6 +334,15 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override + public void notifyBouncerShowing(boolean showing) { + if (mBouncerShowing != showing) { + mBouncerShowing = showing; + + new ArrayList<>(mCallbacks).forEach(Callback::onBouncerShowingChanged); + } + } + + @Override public void notifyPanelFlingEnd() { mFlingingToDismissKeyguard = false; mFlingingToDismissKeyguardDuringSwipeGesture = false; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java index 37517219f103..5a33603d81ce 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/RemoteInputView.java @@ -269,8 +269,12 @@ public class RemoteInputView extends LinearLayout implements View.OnClickListene super.onEnd(animation); if (animation.getTypeMask() == WindowInsets.Type.ime()) { mEntry.mRemoteEditImeAnimatingAway = false; - mEntry.mRemoteEditImeVisible = - mEditText.getRootWindowInsets().isVisible(WindowInsets.Type.ime()); + WindowInsets editTextRootWindowInsets = mEditText.getRootWindowInsets(); + if (editTextRootWindowInsets == null) { + Log.w(TAG, "onEnd called on detached view", new Exception()); + } + mEntry.mRemoteEditImeVisible = editTextRootWindowInsets != null + && editTextRootWindowInsets.isVisible(WindowInsets.Type.ime()); if (!mEntry.mRemoteEditImeVisible && !mEditText.mShowImeOnInputConnection) { mController.removeRemoteInput(mEntry, mToken); } diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java index b7f90a479518..4685c148e7e5 100644 --- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java @@ -38,7 +38,6 @@ import com.android.systemui.doze.DozeHost; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.EnhancedEstimates; -import com.android.systemui.power.EnhancedEstimatesImpl; import com.android.systemui.power.dagger.PowerModule; import com.android.systemui.qs.dagger.QSModule; import com.android.systemui.qs.tileimpl.QSFactoryImpl; @@ -102,9 +101,6 @@ public abstract class TvSystemUIModule { } @Binds - abstract EnhancedEstimates bindEnhancedEstimates(EnhancedEstimatesImpl enhancedEstimates); - - @Binds abstract NotificationLockscreenUserManager bindNotificationLockscreenUserManager( NotificationLockscreenUserManagerImpl notificationLockscreenUserManager); diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java index 5b5dca30620a..d54de3fa9a3f 100644 --- a/packages/SystemUI/src/com/android/systemui/util/Utils.java +++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java @@ -21,9 +21,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.content.res.TypedArray; import android.provider.Settings; -import android.view.ContextThemeWrapper; import android.view.DisplayCutout; import com.android.internal.policy.SystemBarUtils; @@ -35,6 +33,8 @@ import java.util.function.Consumer; public class Utils { + private static Boolean sUseQsMediaPlayer = null; + /** * Allows lambda iteration over a list. It is done in reverse order so it is safe * to add or remove items during the iteration. Skips over null items. @@ -81,9 +81,16 @@ public class Utils { * Off by default, but can be disabled by setting to 0 */ public static boolean useQsMediaPlayer(Context context) { - int flag = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); - return flag > 0; + // TODO(b/192412820): Replace SHOW_MEDIA_ON_QUICK_SETTINGS with compile-time value + // Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS can't be toggled at runtime, so simply + // cache the first result we fetch and use that going forward. Do this to avoid unnecessary + // binder calls which may happen on the critical path. + if (sUseQsMediaPlayer == null) { + int flag = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1); + sUseQsMediaPlayer = flag > 0; + } + return sUseQsMediaPlayer; } /** diff --git a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java index acff8712e92e..ebdddbf66091 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/controller/QuickAccessWalletController.java @@ -180,7 +180,7 @@ public class QuickAccessWalletController { int iconSizePx = mContext.getResources().getDimensionPixelSize(R.dimen.wallet_icon_size); GetWalletCardsRequest request = new GetWalletCardsRequest(cardWidth, cardHeight, iconSizePx, /* maxCards= */ 1); - mQuickAccessWalletClient.getWalletCards(mExecutor, request, cardsRetriever); + mQuickAccessWalletClient.getWalletCards(mCallbackExecutor, request, cardsRetriever); } /** diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt index 747649006b45..39cc34bb7e26 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/ActiveUnlockConfigTest.kt @@ -18,6 +18,7 @@ package com.android.keyguard import android.content.ContentResolver import android.database.ContentObserver +import android.hardware.biometrics.BiometricFaceConstants import android.net.Uri import android.os.Handler import android.os.UserHandle @@ -44,18 +45,20 @@ class ActiveUnlockConfigTest : SysuiTestCase() { private val fakeWakeUri = Uri.Builder().appendPath("wake").build() private val fakeUnlockIntentUri = Uri.Builder().appendPath("unlock-intent").build() private val fakeBioFailUri = Uri.Builder().appendPath("bio-fail").build() + private val fakeFaceErrorsUri = Uri.Builder().appendPath("face-errors").build() + private val fakeFaceAcquiredUri = Uri.Builder().appendPath("face-acquired").build() + private val fakeUnlockIntentBioEnroll = Uri.Builder().appendPath("unlock-intent-bio").build() @Mock private lateinit var secureSettings: SecureSettings - @Mock private lateinit var contentResolver: ContentResolver - @Mock private lateinit var handler: Handler - @Mock private lateinit var dumpManager: DumpManager + @Mock + private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Captor private lateinit var settingsObserverCaptor: ArgumentCaptor<ContentObserver> @@ -72,6 +75,13 @@ class ActiveUnlockConfigTest : SysuiTestCase() { .thenReturn(fakeUnlockIntentUri) `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL)) .thenReturn(fakeBioFailUri) + `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS)) + .thenReturn(fakeFaceErrorsUri) + `when`(secureSettings.getUriFor(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO)) + .thenReturn(fakeFaceAcquiredUri) + `when`(secureSettings.getUriFor( + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED)) + .thenReturn(fakeUnlockIntentBioEnroll) activeUnlockConfig = ActiveUnlockConfig( handler, @@ -99,12 +109,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN unlock on wake is allowed `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_WAKE, 0, 0)).thenReturn(1) - settingsObserverCaptor.value.onChange( - false, - listOf(fakeWakeUri), - 0, - 0 - ) + updateSetting(fakeWakeUri) // THEN active unlock triggers allowed on: wake, unlock-intent, and biometric failure assertTrue( @@ -134,12 +139,7 @@ class ActiveUnlockConfigTest : SysuiTestCase() { // WHEN unlock on biometric failed is allowed `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT, 0, 0)).thenReturn(1) - settingsObserverCaptor.value.onChange( - false, - listOf(fakeUnlockIntentUri), - 0, - 0 - ) + updateSetting(fakeUnlockIntentUri) // THEN active unlock triggers allowed on: biometric failure ONLY assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -154,19 +154,19 @@ class ActiveUnlockConfigTest : SysuiTestCase() { fun testOnBioFailSettingChanged() { verifyRegisterSettingObserver() - // GIVEN no active unlock settings enabled + // GIVEN no active unlock settings enabled and triggering unlock intent on biometric + // enrollment setting is disabled (empty string is disabled, null would use the default) + `when`(secureSettings.getStringForUser( + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + 0)).thenReturn("") + updateSetting(fakeUnlockIntentBioEnroll) assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) // WHEN unlock on biometric failed is allowed `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, 0, 0)).thenReturn(1) - settingsObserverCaptor.value.onChange( - false, - listOf(fakeBioFailUri), - 0, - 0 - ) + updateSetting(fakeBioFailUri) // THEN active unlock triggers allowed on: biometric failure ONLY assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( @@ -177,21 +177,146 @@ class ActiveUnlockConfigTest : SysuiTestCase() { ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.BIOMETRIC_FAIL)) } - private fun verifyRegisterSettingObserver() { - verify(contentResolver).registerContentObserver( - eq(fakeWakeUri), - eq(false), - capture(settingsObserverCaptor), - eq(UserHandle.USER_ALL)) + @Test + fun testFaceErrorSettingsChanged() { + verifyRegisterSettingObserver() - verify(contentResolver).registerContentObserver( - eq(fakeUnlockIntentUri), - eq(false), - capture(settingsObserverCaptor), - eq(UserHandle.USER_ALL)) + // GIVEN unlock on biometric fail + `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + 0, 0)).thenReturn(1) + updateSetting(fakeBioFailUri) + + // WHEN face error timeout (3), allow trigger active unlock + `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ERRORS, + 0)).thenReturn("3") + updateSetting(fakeFaceAcquiredUri) + + // THEN active unlock triggers allowed on error TIMEOUT + assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( + BiometricFaceConstants.FACE_ERROR_TIMEOUT)) + + assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceError( + BiometricFaceConstants.FACE_ERROR_CANCELED)) + } + + @Test + fun testFaceAcquiredSettingsChanged() { + verifyRegisterSettingObserver() + + // GIVEN unlock on biometric fail + `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + 0, 0)).thenReturn(1) + updateSetting(fakeBioFailUri) + + // WHEN face acquiredMsg DARK_GLASSESand MOUTH_COVERING are allowed to trigger + `when`(secureSettings.getStringForUser(Settings.Secure.ACTIVE_UNLOCK_ON_FACE_ACQUIRE_INFO, + 0)).thenReturn( + "${BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED}" + + "|${BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED}") + updateSetting(fakeFaceAcquiredUri) + + // THEN active unlock triggers allowed on acquired messages DARK_GLASSES & MOUTH_COVERING + assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_MOUTH_COVERING_DETECTED)) + assertTrue(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_DARK_GLASSES_DETECTED)) + + assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_GOOD)) + assertFalse(activeUnlockConfig.shouldRequestActiveUnlockOnFaceAcquireInfo( + BiometricFaceConstants.FACE_ACQUIRED_NOT_DETECTED)) + } + + @Test + fun testTriggerOnUnlockIntentWhenBiometricEnrolledNone() { + verifyRegisterSettingObserver() + + // GIVEN unlock on biometric fail + `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + 0, 0)).thenReturn(1) + updateSetting(fakeBioFailUri) + + // GIVEN fingerprint and face are NOT enrolled + activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor + `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false) + `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false) + + // WHEN unlock intent is allowed when NO biometrics are enrolled (0) + `when`(secureSettings.getStringForUser( + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + 0)).thenReturn("${ActiveUnlockConfig.BIOMETRIC_TYPE_NONE}") + updateSetting(fakeUnlockIntentBioEnroll) + + // THEN active unlock triggers allowed on unlock intent + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + } + + @Test + fun testTriggerOnUnlockIntentWhenBiometricEnrolledFingerprintOrFaceOnly() { + verifyRegisterSettingObserver() + + // GIVEN unlock on biometric fail + `when`(secureSettings.getIntForUser(Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL, + 0, 0)).thenReturn(1) + updateSetting(fakeBioFailUri) + + // GIVEN fingerprint and face are both enrolled + activeUnlockConfig.keyguardUpdateMonitor = keyguardUpdateMonitor + `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true) + `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true) + + // WHEN unlock intent is allowed when ONLY fingerprint is enrolled or NO biometircs + // are enrolled + `when`(secureSettings.getStringForUser( + Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT_WHEN_BIOMETRIC_ENROLLED, + 0)).thenReturn( + "${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FACE}" + + "|${ActiveUnlockConfig.BIOMETRIC_TYPE_ANY_FINGERPRINT}") + updateSetting(fakeUnlockIntentBioEnroll) + + // THEN active unlock triggers NOT allowed on unlock intent + assertFalse(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + + // WHEN fingerprint ONLY enrolled + `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(false) + `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(true) + + // THEN active unlock triggers allowed on unlock intent + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + + // WHEN face ONLY enrolled + `when`(keyguardUpdateMonitor.isFaceEnrolled()).thenReturn(true) + `when`(keyguardUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(0)).thenReturn(false) + + // THEN active unlock triggers allowed on unlock intent + assertTrue(activeUnlockConfig.shouldAllowActiveUnlockFromOrigin( + ActiveUnlockConfig.ACTIVE_UNLOCK_REQUEST_ORIGIN.UNLOCK_INTENT)) + } + + private fun updateSetting(uri: Uri) { + settingsObserverCaptor.value.onChange( + false, + listOf(uri), + 0, + 0 /* flags */ + ) + } + + private fun verifyRegisterSettingObserver() { + verifyRegisterSettingObserver(fakeWakeUri) + verifyRegisterSettingObserver(fakeUnlockIntentUri) + verifyRegisterSettingObserver(fakeBioFailUri) + verifyRegisterSettingObserver(fakeFaceErrorsUri) + verifyRegisterSettingObserver(fakeFaceAcquiredUri) + verifyRegisterSettingObserver(fakeUnlockIntentBioEnroll) + } + private fun verifyRegisterSettingObserver(uri: Uri) { verify(contentResolver).registerContentObserver( - eq(fakeBioFailUri), + eq(uri), eq(false), capture(settingsObserverCaptor), eq(UserHandle.USER_ALL)) diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt index aff94eb7aef5..a1e23a201b72 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPasswordViewControllerTest.kt @@ -25,7 +25,6 @@ import com.android.internal.widget.LockPatternUtils import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.util.concurrency.DelayableExecutor import org.junit.Before import org.junit.Test @@ -63,7 +62,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { @Mock lateinit var falsingCollector: FalsingCollector @Mock - lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager + lateinit var keyguardViewController: KeyguardViewController @Mock private lateinit var mKeyguardMessageArea: KeyguardMessageArea @Mock @@ -92,13 +91,13 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { mainExecutor, mContext.resources, falsingCollector, - statusBarKeyguardViewManager + keyguardViewController ) } @Test fun testFocusWhenBouncerIsShown() { - Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(true) + Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(true) Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) keyguardPasswordView.post { verify(keyguardPasswordView).requestFocus() } @@ -106,7 +105,7 @@ class KeyguardPasswordViewControllerTest : SysuiTestCase() { @Test fun testDoNotFocusWhenBouncerIsHidden() { - Mockito.`when`(statusBarKeyguardViewManager.isBouncerShowing).thenReturn(false) + Mockito.`when`(keyguardViewController.isBouncerShowing).thenReturn(false) Mockito.`when`(keyguardPasswordView.isShown).thenReturn(true) keyguardPasswordViewController.onResume(KeyguardSecurityView.VIEW_REVEALED) verify(keyguardPasswordView, never()).requestFocus() diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java index 7b7dfdce90b2..4d3343059718 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java @@ -51,7 +51,6 @@ import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.log.SessionTracker; import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.UserSwitcherController; @@ -125,7 +124,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { @Mock private SessionTracker mSessionTracker; @Mock - private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; + private KeyguardViewController mKeyguardViewController; private Configuration mConfiguration; private KeyguardSecurityContainerController mKeyguardSecurityContainerController; @@ -153,7 +152,7 @@ public class KeyguardSecurityContainerControllerTest extends SysuiTestCase { (KeyguardPasswordView) mKeyguardPasswordView, mKeyguardUpdateMonitor, SecurityMode.Password, mLockPatternUtils, null, mKeyguardMessageAreaControllerFactory, null, null, mEmergencyButtonController, - null, mock(Resources.class), null, mStatusBarKeyguardViewManager); + null, mock(Resources.class), null, mKeyguardViewController); mKeyguardSecurityContainerController = new KeyguardSecurityContainerController.Factory( mView, mAdminSecondaryLockScreenControllerFactory, mLockPatternUtils, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt index 57f36172c998..92c2a1b9b23a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt @@ -29,6 +29,7 @@ fun SensorLocationInternal.asFingerprintSensorProperties( @FingerprintSensorProperties.SensorType sensorType: Int = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, maxEnrollmentsPerUser: Int = 1, + halControlsIllumination: Boolean = true, info: List<ComponentInfoInternal> = listOf(ComponentInfoInternal("a", "b", "c", "d", "e")), resetLockoutRequiresHardwareAuthToken: Boolean = false ) = FingerprintSensorPropertiesInternal( @@ -37,6 +38,7 @@ fun SensorLocationInternal.asFingerprintSensorProperties( maxEnrollmentsPerUser, info, sensorType, + halControlsIllumination, resetLockoutRequiresHardwareAuthToken, listOf(this) ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt index 102f37c4a037..dec2b82ed88f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SidefpsControllerTest.kt @@ -185,6 +185,7 @@ class SidefpsControllerTest : SysuiTestCase() { 5 /* maxEnrollmentsPerUser */, listOf() /* componentInfo */, FingerprintSensorProperties.TYPE_POWER_BUTTON, + true /* halControlsIllumination */, true /* resetLockoutRequiresHardwareAuthToken */, locations ) diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index a57b011d7125..fc5ccbcb4a76 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -65,6 +65,7 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever +private const val HAL_CONTROLS_ILLUMINATION = true private const val REQUEST_ID = 2L // Dimensions for the current display resolution. @@ -129,8 +130,8 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { statusBarStateController, panelExpansionStateManager, statusBarKeyguardViewManager, keyguardUpdateMonitor, dialogManager, dumpManager, transitionController, configurationController, systemClock, keyguardStateController, - unlockedScreenOffAnimationController, hbmProvider, REQUEST_ID, reason, - controllerCallback, onTouch, activityLaunchAnimator) + unlockedScreenOffAnimationController, HAL_CONTROLS_ILLUMINATION, hbmProvider, + REQUEST_ID, reason, controllerCallback, onTouch, activityLaunchAnimator) block() } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java index 27755edecba6..cd646c665d03 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDialogMeasureAdapterTest.java @@ -62,6 +62,7 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase { 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, componentInfo, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, + true /* halControlsIllumination */, true /* resetLockoutRequiresHardwareAuthToken */, List.of(new SensorLocationInternal("" /* displayId */, sensorLocationX, sensorLocationY, sensorRadius))); @@ -127,6 +128,7 @@ public class UdfpsDialogMeasureAdapterTest extends SysuiTestCase { 0 /* sensorId */, SensorProperties.STRENGTH_STRONG, 5 /* maxEnrollmentsPerUser */, componentInfo, FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, + true /* halControlsIllumination */, true /* resetLockoutRequiresHardwareAuthToken */, List.of(new SensorLocationInternal("" /* displayId */, sensorLocationX, sensorLocationY, sensorRadius))); diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt index 744af589dfac..0327cfcf3450 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt @@ -36,6 +36,7 @@ import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.Mock import org.mockito.Mockito.never import org.mockito.Mockito.nullable @@ -43,7 +44,6 @@ import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit import org.mockito.Mockito.`when` as whenever -private const val DISPLAY_ID = "" // default display id private const val SENSOR_X = 50 private const val SENSOR_Y = 250 private const val SENSOR_RADIUS = 10 @@ -146,7 +146,7 @@ class UdfpsViewTest : SysuiTestCase() { view.startIllumination(onDone) val illuminator = withArgCaptor<Runnable> { - verify(hbmProvider).enableHbm(capture()) + verify(hbmProvider).enableHbm(anyBoolean(), capture()) } assertThat(view.isIlluminationRequested).isTrue() diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java index ae387e86de9d..4eeb4acf18b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeTriggersTest.java @@ -45,7 +45,6 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dock.DockManager; import com.android.systemui.doze.DozeTriggers.DozingUpdateUiEvent; import com.android.systemui.statusbar.phone.DozeParameters; -import com.android.systemui.statusbar.policy.BatteryController; import com.android.systemui.statusbar.policy.DevicePostureController; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.util.concurrency.FakeExecutor; @@ -91,8 +90,6 @@ public class DozeTriggersTest extends SysuiTestCase { private KeyguardStateController mKeyguardStateController; @Mock private DevicePostureController mDevicePostureController; - @Mock - private BatteryController mBatteryController; private DozeTriggers mTriggers; private FakeSensorManager mSensors; @@ -125,7 +122,7 @@ public class DozeTriggersTest extends SysuiTestCase { asyncSensorManager, wakeLock, mDockManager, mProximitySensor, mProximityCheck, mock(DozeLog.class), mBroadcastDispatcher, new FakeSettings(), mAuthController, mExecutor, mUiEventLogger, mKeyguardStateController, - mDevicePostureController, mBatteryController); + mDevicePostureController); mTriggers.setDozeMachine(mMachine); waitForSensorManager(); } @@ -233,9 +230,7 @@ public class DozeTriggersTest extends SysuiTestCase { when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); // WHEN the pick up gesture is triggered and keyguard isn't occluded - // and device isn't on a wireless charger when(mKeyguardStateController.isOccluded()).thenReturn(false); - when(mBatteryController.isPluggedInWireless()).thenReturn(false); mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null); // THEN wakeup @@ -249,22 +244,6 @@ public class DozeTriggersTest extends SysuiTestCase { // WHEN the pick up gesture is triggered and keyguard IS occluded when(mKeyguardStateController.isOccluded()).thenReturn(true); - when(mBatteryController.isPluggedInWireless()).thenReturn(false); - mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null); - - // THEN never wakeup - verify(mMachine, never()).wakeUp(); - } - - @Test - public void testPickupGestureWirelessCharger() { - // GIVEN device is in doze (screen blank, but running doze sensors) - when(mMachine.getState()).thenReturn(DozeMachine.State.DOZE); - - // WHEN the pick up gesture is triggered - // and device IS on a wireless charger - when(mKeyguardStateController.isOccluded()).thenReturn(false); - when(mBatteryController.isPluggedInWireless()).thenReturn(true); mTriggers.onSensor(DozeLog.REASON_SENSOR_PICKUP, 100, 100, null); // THEN never wakeup diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt index 2d8c4d5dceb0..2abc666e87fb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardUnlockAnimationControllerTest.kt @@ -14,6 +14,7 @@ import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardViewController import com.android.systemui.SysuiTestCase import com.android.systemui.flags.FeatureFlags +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.statusbar.phone.BiometricUnlockController import com.android.systemui.statusbar.policy.KeyguardStateController import junit.framework.Assert.assertEquals @@ -49,6 +50,8 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { private lateinit var biometricUnlockController: BiometricUnlockController @Mock private lateinit var surfaceTransactionApplier: SyncRtSurfaceTransactionApplier + @Mock + private lateinit var statusBarStateController: SysuiStatusBarStateController private lateinit var remoteAnimationTarget: RemoteAnimationTarget @@ -57,7 +60,7 @@ class KeyguardUnlockAnimationControllerTest : SysuiTestCase() { MockitoAnnotations.initMocks(this) keyguardUnlockAnimationController = KeyguardUnlockAnimationController( context, keyguardStateController, { keyguardViewMediator }, keyguardViewController, - featureFlags, { biometricUnlockController } + featureFlags, { biometricUnlockController }, statusBarStateController ) `when`(keyguardViewController.viewRootImpl).thenReturn(mock(ViewRootImpl::class.java)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index be923a68391c..c532ed5ab651 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -34,8 +34,6 @@ import android.graphics.PointF; import android.graphics.Rect; import android.graphics.drawable.AnimatedStateListDrawable; import android.hardware.biometrics.BiometricSourceType; -import android.hardware.biometrics.SensorLocationInternal; -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.Pair; @@ -77,9 +75,6 @@ import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; import org.mockito.quality.Strictness; -import java.util.ArrayList; -import java.util.List; - @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -182,7 +177,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Test public void testUpdateFingerprintLocationOnInit() { // GIVEN fp sensor location is available pre-attached - Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location + Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location // WHEN lock icon view controller is initialized and attached mLockIconViewController.init(); @@ -197,7 +192,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { @Test public void testUpdatePaddingBasedOnResolutionScale() { // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5 - Pair<Integer, PointF> udfps = setupUdfps(); // first = radius, second = udfps location + Pair<Float, PointF> udfps = setupUdfps(); // first = radius, second = udfps location when(mAuthController.getScaleFactor()).thenReturn(5f); // WHEN lock icon view controller is initialized and attached @@ -215,20 +210,19 @@ public class LockIconViewControllerTest extends SysuiTestCase { // GIVEN fp sensor location is not available pre-init when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); - when(mAuthController.getUdfpsProps()).thenReturn(null); mLockIconViewController.init(); captureAttachListener(); mAttachListener.onViewAttachedToWindow(mLockIconView); - // GIVEN fp sensor location is available post-atttached + // GIVEN fp sensor location is available post-attached captureAuthControllerCallback(); - Pair<Integer, PointF> udfps = setupUdfps(); + Pair<Float, PointF> udfps = setupUdfps(); // WHEN all authenticators are registered mAuthControllerCallback.onAllAuthenticatorsRegistered(); mDelayableExecutor.runAllReady(); - // THEN lock icon view location is updated with the same coordinates as fpProps + // THEN lock icon view location is updated with the same coordinates as auth controller vals verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), eq(PADDING)); } @@ -402,20 +396,10 @@ public class LockIconViewControllerTest extends SysuiTestCase { verify(mLockIconView).setTranslationX(0); } - private Pair<Integer, PointF> setupUdfps() { + private Pair<Float, PointF> setupUdfps() { when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true); final PointF udfpsLocation = new PointF(50, 75); - final int radius = 33; - final FingerprintSensorPropertiesInternal fpProps = - new FingerprintSensorPropertiesInternal( - /* sensorId */ 0, - /* strength */ 0, - /* max enrollments per user */ 5, - /* component info */ new ArrayList<>(), - /* sensorType */ 3, - /* resetLockoutRequiresHwToken */ false, - List.of(new SensorLocationInternal("" /* displayId */, - (int) udfpsLocation.x, (int) udfpsLocation.y, radius))); + final float radius = 33f; when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation); when(mAuthController.getUdfpsRadius()).thenReturn(radius); diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt index f9fb8657d0f6..0ed579f9c10b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaControlPanelTest.kt @@ -21,7 +21,6 @@ import android.animation.AnimatorSet import android.app.PendingIntent import android.app.smartspace.SmartspaceAction import android.content.Context -import org.mockito.Mockito.`when` as whenever import android.content.Intent import android.content.pm.ApplicationInfo import android.content.pm.PackageManager @@ -67,8 +66,8 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.mockito.KotlinArgumentCaptor -import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.withArgCaptor @@ -92,6 +91,7 @@ import org.mockito.Mockito.reset import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.junit.MockitoJUnit +import org.mockito.Mockito.`when` as whenever private const val KEY = "TEST_KEY" private const val PACKAGE = "PKG" @@ -339,6 +339,7 @@ public class MediaControlPanelTest : SysuiTestCase() { whenever(viewHolder.player).thenReturn(view) whenever(viewHolder.appIcon).thenReturn(appIcon) whenever(viewHolder.albumView).thenReturn(albumView) + whenever(albumView.foreground).thenReturn(mock(Drawable::class.java)) whenever(viewHolder.titleText).thenReturn(titleText) whenever(viewHolder.artistText).thenReturn(artistText) whenever(seamlessBackground.getDrawable(0)).thenReturn(mock(GradientDrawable::class.java)) diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt index 604e1f3ac9eb..18178097d5e3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/MediaViewControllerTest.kt @@ -4,16 +4,22 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View import androidx.test.filters.SmallTest +import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.util.animation.MeasurementInput import com.android.systemui.util.animation.TransitionLayout import com.android.systemui.util.animation.TransitionViewState +import com.android.systemui.util.animation.WidgetState import junit.framework.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever /** * Tests for {@link MediaViewController}. @@ -31,6 +37,9 @@ class MediaViewControllerTest : SysuiTestCase() { private lateinit var mediaViewController: MediaViewController private val mediaHostStateHolder = MediaHost.MediaHostStateHolder() private var transitionLayout = TransitionLayout(context, /* attrs */ null, /* defStyleAttr */ 0) + @Mock private lateinit var mockViewState: TransitionViewState + @Mock private lateinit var mockCopiedState: TransitionViewState + @Mock private lateinit var mockWidgetState: WidgetState @Before fun setUp() { @@ -63,4 +72,15 @@ class MediaViewControllerTest : SysuiTestCase() { mediaHostStateHolder.squishFraction = 0.5f assertTrue(mediaViewController.obtainViewState(mediaHostStateHolder)!!.height == 50) } + + @Test + fun testSquish_DoesNotMutateViewState() { + whenever(mockViewState.copy()).thenReturn(mockCopiedState) + whenever(mockCopiedState.widgetStates) + .thenReturn(mutableMapOf(R.id.album_art to mockWidgetState)) + + mediaViewController.squishViewState(mockViewState, 0.5f) + verify(mockViewState, times(1)).copy() + verifyNoMoreInteractions(mockViewState) + } }
\ No newline at end of file diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt index 06d45de699e3..a07447521ab8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/ResumeMediaBrowserTest.kt @@ -27,6 +27,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.util.mockito.mock import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -136,6 +137,24 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { } @Test + fun testConnection_calledTwice_oldBrowserDisconnected() { + val oldBrowser = mock<MediaBrowser>() + whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser) + + // When testConnection can connect to the service + setupBrowserConnection() + resumeBrowser.testConnection() + + // And testConnection is called again + val newBrowser = mock<MediaBrowser>() + whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser) + resumeBrowser.testConnection() + + // Then we disconnect the old browser + verify(oldBrowser).disconnect() + } + + @Test fun testFindRecentMedia_connectionFails_error() { // When findRecentMedia is called and we cannot connect setupBrowserFailed() @@ -169,6 +188,24 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { } @Test + fun testFindRecentMedia_calledTwice_oldBrowserDisconnected() { + val oldBrowser = mock<MediaBrowser>() + whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser) + + // When findRecentMedia is called and we connect + setupBrowserConnection() + resumeBrowser.findRecentMedia() + + // And findRecentMedia is called again + val newBrowser = mock<MediaBrowser>() + whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser) + resumeBrowser.findRecentMedia() + + // Then we disconnect the old browser + verify(oldBrowser).disconnect() + } + + @Test fun testFindRecentMedia_noChildren_error() { // When findRecentMedia is called and we connect, but do not get any results setupBrowserConnectionNoResults() @@ -223,6 +260,24 @@ public class ResumeMediaBrowserTest : SysuiTestCase() { verify(transportControls).play() } + @Test + fun testRestart_calledTwice_oldBrowserDisconnected() { + val oldBrowser = mock<MediaBrowser>() + whenever(browserFactory.create(any(), any(), any())).thenReturn(oldBrowser) + + // When restart is called and we connect successfully + setupBrowserConnection() + resumeBrowser.restart() + + // And restart is called again + val newBrowser = mock<MediaBrowser>() + whenever(browserFactory.create(any(), any(), any())).thenReturn(newBrowser) + resumeBrowser.restart() + + // Then we disconnect the old browser + verify(oldBrowser).disconnect() + } + /** * Helper function to mock a failed connection */ diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java index 3c6aa4803e86..a52608733ca4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java @@ -60,6 +60,7 @@ import android.view.Display; import android.view.DisplayInfo; import android.view.MotionEvent; import android.view.View; +import android.view.ViewTreeObserver; import android.view.WindowInsets; import android.view.WindowManager; import android.view.WindowMetrics; @@ -85,6 +86,7 @@ import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.recents.OverviewProxyService; import com.android.systemui.recents.Recents; +import com.android.systemui.settings.UserContextProvider; import com.android.systemui.settings.UserTracker; import com.android.systemui.shared.rotation.RotationButtonController; import com.android.systemui.statusbar.CommandQueue; @@ -163,7 +165,7 @@ public class NavigationBarTest extends SysuiTestCase { @Mock private UiEventLogger mUiEventLogger; @Mock - EdgeBackGestureHandler.Factory mEdgeBackGestureHandlerFactory; + private ViewTreeObserver mViewTreeObserver; @Mock EdgeBackGestureHandler mEdgeBackGestureHandler; NavBarHelper mNavBarHelper; @@ -187,6 +189,8 @@ public class NavigationBarTest extends SysuiTestCase { private DeadZone mDeadZone; @Mock private CentralSurfaces mCentralSurfaces; + @Mock + private UserContextProvider mUserContextProvider; private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake(); @Rule @@ -196,8 +200,6 @@ public class NavigationBarTest extends SysuiTestCase { public void setup() throws Exception { MockitoAnnotations.initMocks(this); - when(mEdgeBackGestureHandlerFactory.create(any(Context.class))) - .thenReturn(mEdgeBackGestureHandler); when(mLightBarcontrollerFactory.create(any(Context.class))).thenReturn(mLightBarController); when(mAutoHideControllerFactory.create(any(Context.class))).thenReturn(mAutoHideController); when(mNavigationBarView.getHomeButton()).thenReturn(mHomeButton); @@ -210,6 +212,9 @@ public class NavigationBarTest extends SysuiTestCase { when(mNavigationBarTransitions.getLightTransitionsController()) .thenReturn(mLightBarTransitionsController); when(mStatusBarKeyguardViewManager.isNavBarVisible()).thenReturn(true); + when(mNavigationBarView.getViewTreeObserver()).thenReturn(mViewTreeObserver); + when(mUserContextProvider.createCurrentUserContext(any(Context.class))) + .thenReturn(mContext); setupSysuiDependency(); // This class inflates views that call Dependency.get, thus these injections are still // necessary. @@ -217,8 +222,6 @@ public class NavigationBarTest extends SysuiTestCase { mDependency.injectMockDependency(KeyguardStateController.class); mDependency.injectTestDependency(StatusBarStateController.class, mStatusBarStateController); mDependency.injectMockDependency(NavigationBarController.class); - mDependency.injectTestDependency(EdgeBackGestureHandler.Factory.class, - mEdgeBackGestureHandlerFactory); mDependency.injectTestDependency(OverviewProxyService.class, mOverviewProxyService); mDependency.injectTestDependency(NavigationModeController.class, mNavigationModeController); TestableLooper.get(this).runWithLooper(() -> { @@ -458,7 +461,9 @@ public class NavigationBarTest extends SysuiTestCase { mDeadZone, mDeviceConfigProxyFake, mNavigationBarTransitions, - Optional.of(mock(BackAnimation.class)))); + mEdgeBackGestureHandler, + Optional.of(mock(BackAnimation.class)), + mUserContextProvider)); } private void processAllMessages() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java index c88ceac458ee..4f6475f0148b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/QuickAccessWalletTileTest.java @@ -90,6 +90,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private static final String CARD_ID = "card_id"; private static final String LABEL = "QAW"; + private static final String CARD_DESCRIPTION = "•••• 1234"; private static final Icon CARD_IMAGE = Icon.createWithBitmap(Bitmap.createBitmap(70, 50, Bitmap.Config.ARGB_8888)); @@ -282,9 +283,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mTile.handleUpdateState(state, null); assertEquals(Tile.STATE_ACTIVE, state.state); - assertEquals( - "•••• 1234", - state.secondaryLabel); + assertEquals(CARD_DESCRIPTION, state.secondaryLabel); assertNotNull(state.stateDescription); assertNotNull(state.sideViewCustomDrawable); } @@ -298,11 +297,9 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mTile.handleUpdateState(state, null); assertEquals(Tile.STATE_INACTIVE, state.state); - assertEquals( - mContext.getString(R.string.wallet_secondary_label_device_locked), - state.secondaryLabel); + assertEquals(CARD_DESCRIPTION, state.secondaryLabel); assertNotNull(state.stateDescription); - assertNull(state.sideViewCustomDrawable); + assertNotNull(state.sideViewCustomDrawable); } @Test @@ -314,9 +311,7 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { mTile.handleUpdateState(state, null); assertEquals(Tile.STATE_ACTIVE, state.state); - assertEquals( - "•••• 1234", - state.secondaryLabel); + assertEquals(CARD_DESCRIPTION, state.secondaryLabel); assertNotNull(state.stateDescription); assertNotNull(state.sideViewCustomDrawable); } @@ -426,6 +421,6 @@ public class QuickAccessWalletTileTest extends SysuiTestCase { private WalletCard createWalletCard(Context context) { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); - return new WalletCard.Builder(CARD_ID, CARD_IMAGE, "•••• 1234", pendingIntent).build(); + return new WalletCard.Builder(CARD_ID, CARD_IMAGE, CARD_DESCRIPTION, pendingIntent).build(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt index 540d2918319f..1cce3b5b9e77 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationSectionsFeatureManagerTest.kt @@ -21,10 +21,13 @@ import android.provider.Settings import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest +import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.config.sysui.SystemUiDeviceConfigFlags.NOTIFICATIONS_USE_PEOPLE_FILTERING import com.android.systemui.SysuiTestCase import com.android.systemui.util.DeviceConfigProxyFake +import com.android.systemui.util.Utils +import com.android.systemui.util.mockito.any import org.junit.After import org.junit.Assert.assertFalse @@ -32,28 +35,33 @@ import org.junit.Assert.assertTrue import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.Mockito.`when` +import org.mockito.MockitoSession +import org.mockito.quality.Strictness @RunWith(AndroidTestingRunner::class) @SmallTest class NotificationSectionsFeatureManagerTest : SysuiTestCase() { var manager: NotificationSectionsFeatureManager? = null val proxyFake = DeviceConfigProxyFake() - var originalQsMediaPlayer: Int = 0 + private lateinit var staticMockSession: MockitoSession @Before public fun setup() { manager = NotificationSectionsFeatureManager(proxyFake, mContext) manager!!.clearCache() - originalQsMediaPlayer = Settings.Global.getInt(context.getContentResolver(), - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 1) + staticMockSession = ExtendedMockito.mockitoSession() + .mockStatic<Utils>(Utils::class.java) + .strictness(Strictness.LENIENT) + .startMocking() + `when`(Utils.useQsMediaPlayer(any())).thenReturn(false) Settings.Global.putInt(context.getContentResolver(), Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, 0) } @After public fun teardown() { - Settings.Global.putInt(context.getContentResolver(), - Settings.Global.SHOW_MEDIA_ON_QUICK_SETTINGS, originalQsMediaPlayer) + staticMockSession.finishMocking() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java index b45d78d5502d..4b458f5a9123 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/GroupEntryBuilder.java @@ -88,4 +88,7 @@ public class GroupEntryBuilder { return this; } + public static List<NotificationEntry> getRawChildren(GroupEntry groupEntry) { + return groupEntry.getRawChildren(); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt index 7692a05eb5fc..742fcf5e03c3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/ConversationCoordinatorTest.kt @@ -21,17 +21,22 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.notification.collection.GroupEntry +import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.collection.listbuilder.NotifSection +import com.android.systemui.statusbar.notification.collection.listbuilder.OnBeforeRenderListListener import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifComparator import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifSectioner import com.android.systemui.statusbar.notification.collection.render.NodeController +import com.android.systemui.statusbar.notification.icon.ConversationIconManager import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_IMPORTANT_PERSON import com.android.systemui.statusbar.notification.people.PeopleNotificationIdentifier.Companion.TYPE_PERSON +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import org.junit.Assert.assertFalse @@ -52,8 +57,10 @@ class ConversationCoordinatorTest : SysuiTestCase() { private lateinit var promoter: NotifPromoter private lateinit var peopleSectioner: NotifSectioner private lateinit var peopleComparator: NotifComparator + private lateinit var beforeRenderListListener: OnBeforeRenderListListener @Mock private lateinit var pipeline: NotifPipeline + @Mock private lateinit var conversationIconManager: ConversationIconManager @Mock private lateinit var peopleNotificationIdentifier: PeopleNotificationIdentifier @Mock private lateinit var channel: NotificationChannel @Mock private lateinit var headerController: NodeController @@ -66,7 +73,11 @@ class ConversationCoordinatorTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - coordinator = ConversationCoordinator(peopleNotificationIdentifier, headerController) + coordinator = ConversationCoordinator( + peopleNotificationIdentifier, + conversationIconManager, + headerController + ) whenever(channel.isImportantConversation).thenReturn(true) coordinator.attach(pipeline) @@ -75,6 +86,9 @@ class ConversationCoordinatorTest : SysuiTestCase() { promoter = withArgCaptor { verify(pipeline).addPromoter(capture()) } + beforeRenderListListener = withArgCaptor { + verify(pipeline).addOnBeforeRenderListListener(capture()) + } peopleSectioner = coordinator.sectioner peopleComparator = peopleSectioner.comparator!! @@ -96,6 +110,25 @@ class ConversationCoordinatorTest : SysuiTestCase() { } @Test + fun testPromotedImportantConversationsMakesSummaryUnimportant() { + val altChildA = NotificationEntryBuilder().setTag("A").build() + val altChildB = NotificationEntryBuilder().setTag("B").build() + val summary = NotificationEntryBuilder().setId(2).setChannel(channel).build() + val groupEntry = GroupEntryBuilder() + .setParent(GroupEntry.ROOT_ENTRY) + .setSummary(summary) + .setChildren(listOf(entry, altChildA, altChildB)) + .build() + assertTrue(promoter.shouldPromoteToTopLevel(entry)) + assertFalse(promoter.shouldPromoteToTopLevel(altChildA)) + assertFalse(promoter.shouldPromoteToTopLevel(altChildB)) + NotificationEntryBuilder.setNewParent(entry, GroupEntry.ROOT_ENTRY) + GroupEntryBuilder.getRawChildren(groupEntry).remove(entry) + beforeRenderListListener.onBeforeRenderList(listOf(entry, groupEntry)) + verify(conversationIconManager).setUnimportantConversations(eq(listOf(summary.key))) + } + + @Test fun testInPeopleSection() { whenever(peopleNotificationIdentifier.getPeopleNotificationType(entry)) .thenReturn(TYPE_PERSON) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java index cf996073f6a0..3a85972aa470 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/KeyguardNotificationVisibilityProviderTest.java @@ -22,11 +22,14 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; +import static com.android.systemui.statusbar.StatusBarState.KEYGUARD; +import static com.android.systemui.statusbar.StatusBarState.SHADE; import static com.android.systemui.statusbar.notification.collection.EntryUtilKt.modifyEntry; import static com.android.systemui.util.mockito.KotlinMockitoHelpersKt.argThat; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.isNull; @@ -55,6 +58,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.statusbar.NotificationLockscreenUserManager; import com.android.systemui.statusbar.RankingBuilder; +import com.android.systemui.statusbar.SysuiStatusBarStateController; import com.android.systemui.statusbar.notification.collection.GroupEntry; import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder; import com.android.systemui.statusbar.notification.collection.NotificationEntry; @@ -90,7 +94,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Mock private NotificationLockscreenUserManager mLockscreenUserManager; @Mock private KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Mock private HighPriorityProvider mHighPriorityProvider; - @Mock private StatusBarStateController mStatusBarStateController; + @Mock private SysuiStatusBarStateController mStatusBarStateController; @Mock private BroadcastDispatcher mBroadcastDispatcher; private final FakeSettings mFakeSettings = new FakeSettings(); @@ -178,7 +182,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - callback.onStateChanged(0); + callback.onUpcomingStateChanged(0); verify(listener).accept(anyString()); } @@ -199,7 +203,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); callback.onReceive(mContext, new Intent(Intent.ACTION_USER_SWITCHED)); verify(listener).accept(anyString()); @@ -207,7 +211,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void notifyListeners_onSettingChange_lockScreenShowNotifs() { - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); @@ -218,7 +222,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void notifyListeners_onSettingChange_lockScreenAllowPrivateNotifs() { - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); @@ -228,8 +232,43 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { } @Test + public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() { + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); + mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + GroupEntry parent = new GroupEntryBuilder() + .setKey("parent") + .addChild(mEntry) + .setSummary(new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)) + .setImportance(IMPORTANCE_LOW) + .build()) + .build(); + mEntry = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)) + .setImportance(IMPORTANCE_LOW) + .setParent(parent) + .build(); + when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test + public void hideSilentNotificationsPerUserSetting() { + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); + mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, true); + mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); + mEntry = new NotificationEntryBuilder() + .setUser(new UserHandle(NOTIF_USER_ID)) + .setImportance(IMPORTANCE_LOW) + .build(); + when(mHighPriorityProvider.isHighPriority(any())).thenReturn(false); + assertTrue(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); + } + + @Test public void notifyListeners_onSettingChange_zenMode() { - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); @@ -240,7 +279,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @Test public void notifyListeners_onSettingChange_lockScreenShowSilentNotifs() { - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); Consumer<String> listener = mock(Consumer.class); mKeyguardNotificationVisibilityProvider.addOnStateChangedListener(listener); @@ -262,7 +301,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { public void keyguardNotShowing() { // GIVEN the lockscreen isn't showing setupUnfilteredState(mEntry); - when(mKeyguardStateController.isShowing()).thenReturn(false); + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(SHADE); // THEN don't filter out the entry assertFalse(mKeyguardNotificationVisibilityProvider.shouldHideNotification(mEntry)); @@ -384,8 +423,8 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { mFakeSettings.putBool(Settings.Secure.LOCK_SCREEN_SHOW_SILENT_NOTIFICATIONS, false); when(mHighPriorityProvider.isHighPriority(parent)).thenReturn(true); - // THEN don't filter out the entry - assertFalse( + // THEN filter out the entry regardless of parent + assertTrue( mKeyguardNotificationVisibilityProvider.shouldHideNotification(entryWithParent)); // WHEN its parent doesn't exceed threshold to show on lockscreen @@ -404,7 +443,7 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { */ private void setupUnfilteredState(NotificationEntry entry) { // keyguard is showing - when(mKeyguardStateController.isShowing()).thenReturn(true); + when(mStatusBarStateController.getCurrentOrUpcomingState()).thenReturn(KEYGUARD); // show notifications on the lockscreen when(mLockscreenUserManager.shouldShowLockscreenNotifications()).thenReturn(true); @@ -452,11 +491,11 @@ public class KeyguardNotificationVisibilityProviderTest extends SysuiTestCase { @BindsInstance NotificationLockscreenUserManager lockscreenUserManager, @BindsInstance KeyguardUpdateMonitor keyguardUpdateMonitor, @BindsInstance HighPriorityProvider highPriorityProvider, - @BindsInstance StatusBarStateController statusBarStateController, + @BindsInstance SysuiStatusBarStateController statusBarStateController, @BindsInstance BroadcastDispatcher broadcastDispatcher, @BindsInstance SecureSettings secureSettings, @BindsInstance GlobalSettings globalSettings ); } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java index 4986792373db..f43c2a183465 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardBouncerTest.java @@ -160,7 +160,7 @@ public class KeyguardBouncerTest extends SysuiTestCase { @Test public void testShow_notifiesVisibility() { mBouncer.show(true); - verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(true)); + verify(mKeyguardStateController).notifyBouncerShowing(eq(true)); verify(mExpansionCallback).onStartingToShow(); // Not called again when visible @@ -238,7 +238,7 @@ public class KeyguardBouncerTest extends SysuiTestCase { @Test public void testHide_notifiesVisibility() { mBouncer.hide(false); - verify(mViewMediatorCallback).onBouncerVisiblityChanged(eq(false)); + verify(mKeyguardStateController).notifyBouncerShowing(eq(false)); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java index 6d3a5fecc826..ec20271ea43b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/KeyguardClockPositionAlgorithmTest.java @@ -336,7 +336,7 @@ public class KeyguardClockPositionAlgorithmTest extends SysuiTestCase { // WHEN the position algorithm is run positionClock(); // THEN the padding DOESN'T adjust for keyguard status height. - assertThat(mClockPositionAlgorithm.getMinStackScrollerPadding()) + assertThat(mClockPositionAlgorithm.getLockscreenMinStackScrollerPadding()) .isEqualTo(mKeyguardStatusBarHeaderHeight); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java index 94e6b9a12a93..11f96cec6369 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationPanelViewControllerTest.java @@ -610,13 +610,13 @@ public class NotificationPanelViewControllerTest extends SysuiTestCase { when(mLockIconViewController.getTop()).thenReturn(80f); when(mResources.getDimensionPixelSize(R.dimen.shelf_and_lock_icon_overlap)).thenReturn(5); - // Available space (100 - 10 - 15 = 75) + // Available space (100 - 0 - 15 = 85) when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(100); - when(mNotificationStackScrollLayoutController.getTopPadding()).thenReturn(10); + when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); mNotificationPanelViewController.updateResources(); assertThat(mNotificationPanelViewController.getSpaceForLockscreenNotifications()) - .isEqualTo(75); + .isEqualTo(85); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index ddccd834f76e..c402d2e47cf3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java @@ -26,10 +26,12 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; import android.app.IActivityManager; @@ -103,6 +105,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); + verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any()); } @Test @@ -174,6 +177,14 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { } @Test + public void setScrimsVisibility_earlyReturn() { + clearInvocations(mWindowManager); + mNotificationShadeWindowController.setScrimsVisibility(ScrimController.TRANSPARENT); + // Abort early if value didn't change + verify(mWindowManager, never()).updateViewLayout(any(), mLayoutParameters.capture()); + } + + @Test public void attach_animatingKeyguardAndSurface_wallpaperVisible() { clearInvocations(mWindowManager); when(mKeyguardViewMediator.isShowingAndNotOccluded()).thenReturn(true); @@ -221,6 +232,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { public void setPanelExpanded_notFocusable_altFocusable_whenPanelIsOpen() { mNotificationShadeWindowController.setPanelExpanded(true); clearInvocations(mWindowManager); + mNotificationShadeWindowController.setPanelExpanded(true); + verifyNoMoreInteractions(mWindowManager); mNotificationShadeWindowController.setNotificationShadeFocusable(true); verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); @@ -287,6 +300,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { public void batchApplyWindowLayoutParams_doesNotDispatchEvents() { mNotificationShadeWindowController.setForceDozeBrightness(true); verify(mWindowManager).updateViewLayout(any(), any()); + mNotificationShadeWindowController.setForceDozeBrightness(true); + verifyNoMoreInteractions(mWindowManager); clearInvocations(mWindowManager); mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> { @@ -295,4 +310,17 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { }); verify(mWindowManager).updateViewLayout(any(), any()); } + + @Test + public void bouncerShowing_OrientationNoSensor() { + mNotificationShadeWindowController.setKeyguardShowing(true); + mNotificationShadeWindowController.setKeyguardOccluded(true); + mNotificationShadeWindowController.setBouncerShowing(true); + when(mKeyguardStateController.isKeyguardScreenRotationAllowed()).thenReturn(false); + mNotificationShadeWindowController.onConfigChanged(new Configuration()); + + verify(mWindowManager, atLeastOnce()).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat(mLayoutParameters.getValue().screenOrientation) + .isEqualTo(ActivityInfo.SCREEN_ORIENTATION_NOSENSOR); + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt index 0936b773d4b3..011279721fd2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/UnlockedScreenOffAnimationControllerTest.kt @@ -117,11 +117,18 @@ class UnlockedScreenOffAnimationControllerTest : SysuiTestCase() { val keyguardSpy = spy(keyguardView) Mockito.`when`(keyguardSpy.animate()).thenReturn(animator) val listener = ArgumentCaptor.forClass(Animator.AnimatorListener::class.java) + val endAction = ArgumentCaptor.forClass(Runnable::class.java) controller.animateInKeyguard(keyguardSpy, Runnable {}) Mockito.verify(animator).setListener(listener.capture()) - // Verify that the listener is cleared when it ends - listener.value.onAnimationEnd(null) + Mockito.verify(animator).withEndAction(endAction.capture()) + + // Verify that the listener is cleared if we cancel it. + listener.value.onAnimationCancel(null) Mockito.verify(animator).setListener(null) + + // Verify that the listener is also cleared if the end action is triggered. + endAction.value.run() + verify(animator, times(2)).setListener(null) } /** diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt index e9d16a6738ca..64a93cf2c8dc 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImplTest.kt @@ -53,7 +53,6 @@ import org.mockito.Mock import org.mockito.Mockito import org.mockito.MockitoAnnotations import org.mockito.Mockito.never -import org.mockito.Mockito.times import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.ArgumentMatchers.anyInt diff --git a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java index aaea4ecdc08d..95b62a12c621 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java +++ b/packages/SystemUI/tests/src/com/android/systemui/utils/leaks/FakeKeyguardStateController.java @@ -49,6 +49,11 @@ public class FakeKeyguardStateController implements KeyguardStateController { } @Override + public boolean isBouncerShowing() { + return false; + } + + @Override public boolean canDismissLockScreen() { return false; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 193879e5c55c..238a4d37a872 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -57,6 +57,7 @@ import android.content.IntentFilter; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; @@ -70,6 +71,8 @@ import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Pair; +import android.util.SparseArray; import android.view.View; import android.view.WindowManager; @@ -147,6 +150,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -1010,7 +1014,7 @@ public class BubblesTest extends SysuiTestCase { assertBubbleNotificationSuppressedFromShade(mBubbleEntry); // Should notify delegate that shade state changed - verify(mBubbleController).onBubbleNotificationSuppressionChanged( + verify(mBubbleController).onBubbleMetadataFlagChanged( mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @@ -1027,7 +1031,7 @@ public class BubblesTest extends SysuiTestCase { assertBubbleNotificationSuppressedFromShade(mBubbleEntry); // Should notify delegate that shade state changed - verify(mBubbleController).onBubbleNotificationSuppressionChanged( + verify(mBubbleController).onBubbleMetadataFlagChanged( mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @@ -1447,6 +1451,69 @@ public class BubblesTest extends SysuiTestCase { assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE); } + @Test + public void testSetShouldAutoExpand_notifiesFlagChanged() { + mEntryListener.onPendingEntryAdded(mRow); + + assertTrue(mBubbleController.hasBubbles()); + Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey()); + assertThat(b.shouldAutoExpand()).isFalse(); + + // Set it to the same thing + b.setShouldAutoExpand(false); + + // Verify it doesn't notify + verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any()); + + // Set it to something different + b.setShouldAutoExpand(true); + verify(mBubbleController).onBubbleMetadataFlagChanged(b); + } + + @Test + public void testUpdateBubble_skipsDndSuppressListNotifs() { + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + mBubbleEntry.getBubbleMetadata().setFlags( + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + + mBubbleController.updateBubble(mBubbleEntry); + + Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey()); + assertThat(b.shouldAutoExpand()).isFalse(); + assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull(); + } + + @Test + public void testOnRankingUpdate_DndSuppressListNotif() { + // It's in the stack + mBubbleController.updateBubble(mBubbleEntry); + assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue(); + + // Set current user profile + SparseArray<UserInfo> userInfos = new SparseArray<>(); + userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(), + mock(UserInfo.class)); + mBubbleController.onCurrentProfilesChanged(userInfos); + + // Send ranking update that the notif is suppressed from the list. + HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>(); + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true); + entryDataByKey.put(mBubbleEntry.getKey(), pair); + + NotificationListenerService.RankingMap rankingMap = + mock(NotificationListenerService.RankingMap.class); + when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() }); + mBubbleController.onRankingUpdated(rankingMap, entryDataByKey); + + // Should no longer be in the stack + assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse(); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java index 02d869172030..dff89e0a5558 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/NewNotifPipelineBubblesTest.java @@ -50,6 +50,7 @@ import android.content.BroadcastReceiver; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.LauncherApps; +import android.content.pm.UserInfo; import android.hardware.display.AmbientDisplayConfiguration; import android.os.Handler; import android.os.PowerManager; @@ -59,6 +60,8 @@ import android.service.notification.NotificationListenerService; import android.service.notification.ZenModeConfig; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.util.Pair; +import android.util.SparseArray; import android.view.View; import android.view.WindowManager; @@ -128,6 +131,7 @@ import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import java.util.HashMap; import java.util.List; import java.util.Optional; @@ -880,7 +884,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertBubbleNotificationSuppressedFromShade(mBubbleEntry); // Should notify delegate that shade state changed - verify(mBubbleController).onBubbleNotificationSuppressionChanged( + verify(mBubbleController).onBubbleMetadataFlagChanged( mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @@ -897,7 +901,7 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertBubbleNotificationSuppressedFromShade(mBubbleEntry); // Should notify delegate that shade state changed - verify(mBubbleController).onBubbleNotificationSuppressionChanged( + verify(mBubbleController).onBubbleMetadataFlagChanged( mBubbleData.getBubbleInStackWithKey(mRow.getKey())); } @@ -1267,6 +1271,69 @@ public class NewNotifPipelineBubblesTest extends SysuiTestCase { assertThat(stackView.getVisibility()).isEqualTo(View.VISIBLE); } + @Test + public void testSetShouldAutoExpand_notifiesFlagChanged() { + mBubbleController.updateBubble(mBubbleEntry); + + assertTrue(mBubbleController.hasBubbles()); + Bubble b = mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey()); + assertThat(b.shouldAutoExpand()).isFalse(); + + // Set it to the same thing + b.setShouldAutoExpand(false); + + // Verify it doesn't notify + verify(mBubbleController, never()).onBubbleMetadataFlagChanged(any()); + + // Set it to something different + b.setShouldAutoExpand(true); + verify(mBubbleController).onBubbleMetadataFlagChanged(b); + } + + @Test + public void testUpdateBubble_skipsDndSuppressListNotifs() { + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + mBubbleEntry.getBubbleMetadata().setFlags( + Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); + + mBubbleController.updateBubble(mBubbleEntry); + + Bubble b = mBubbleData.getPendingBubbleWithKey(mBubbleEntry.getKey()); + assertThat(b.shouldAutoExpand()).isFalse(); + assertThat(mBubbleData.getBubbleInStackWithKey(mBubbleEntry.getKey())).isNull(); + } + + @Test + public void testOnRankingUpdate_DndSuppressListNotif() { + // It's in the stack + mBubbleController.updateBubble(mBubbleEntry); + assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isTrue(); + + // Set current user profile + SparseArray<UserInfo> userInfos = new SparseArray<>(); + userInfos.put(mBubbleEntry.getStatusBarNotification().getUser().getIdentifier(), + mock(UserInfo.class)); + mBubbleController.onCurrentProfilesChanged(userInfos); + + // Send ranking update that the notif is suppressed from the list. + HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey = new HashMap<>(); + mBubbleEntry = new BubbleEntry(mRow.getSbn(), mRow.getRanking(), mRow.isDismissable(), + mRow.shouldSuppressNotificationDot(), true /* DndSuppressNotifFromList */, + mRow.shouldSuppressPeek()); + Pair<BubbleEntry, Boolean> pair = new Pair(mBubbleEntry, true); + entryDataByKey.put(mBubbleEntry.getKey(), pair); + + NotificationListenerService.RankingMap rankingMap = + mock(NotificationListenerService.RankingMap.class); + when(rankingMap.getOrderedKeys()).thenReturn(new String[] { mBubbleEntry.getKey() }); + mBubbleController.onRankingUpdated(rankingMap, entryDataByKey); + + // Should no longer be in the stack + assertThat(mBubbleData.hasBubbleInStackWithKey(mBubbleEntry.getKey())).isFalse(); + } + /** * Sets the bubble metadata flags for this entry. These flags are normally set by * NotificationManagerService when the notification is sent, however, these tests do not diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 649328d1eef3..9b29bae1fc46 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -242,6 +242,9 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ int getCurrentUserIdLocked(); + Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec( + int windowId); + boolean isAccessibilityButtonShown(); /** @@ -551,8 +554,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); - final MagnificationSpec spec; - final float[] transformMatrix; synchronized (mLock) { mUsesAccessibilityCache = true; if (!hasRightsToCurrentUserLocked()) { @@ -576,11 +577,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } - final Pair<float[], MagnificationSpec> transformMatrixAndSpec = - getTransformMatrixAndSpecLocked(resolvedWindowId); - transformMatrix = transformMatrixAndSpec.first; - spec = transformMatrixAndSpec.second; } + final Pair<float[], MagnificationSpec> transformMatrixAndSpec = + getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId); + final float[] transformMatrix = transformMatrixAndSpec.first; + final MagnificationSpec spec = transformMatrixAndSpec.second; if (!mSecurityPolicy.checkAccessibilityAccess(this)) { return null; } @@ -628,8 +629,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); - final MagnificationSpec spec; - final float [] transformMatrix; synchronized (mLock) { mUsesAccessibilityCache = true; if (!hasRightsToCurrentUserLocked()) { @@ -653,11 +652,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } - final Pair<float[], MagnificationSpec> transformMatrixAndSpec = - getTransformMatrixAndSpecLocked(resolvedWindowId); - transformMatrix = transformMatrixAndSpec.first; - spec = transformMatrixAndSpec.second; } + final Pair<float[], MagnificationSpec> transformMatrixAndSpec = + getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId); + final float[] transformMatrix = transformMatrixAndSpec.first; + final MagnificationSpec spec = transformMatrixAndSpec.second; if (!mSecurityPolicy.checkAccessibilityAccess(this)) { return null; } @@ -706,8 +705,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); - final MagnificationSpec spec; - final float[] transformMatrix; synchronized (mLock) { mUsesAccessibilityCache = true; if (!hasRightsToCurrentUserLocked()) { @@ -731,11 +728,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } - final Pair<float[], MagnificationSpec> transformMatrixAndSpec = - getTransformMatrixAndSpecLocked(resolvedWindowId); - transformMatrix = transformMatrixAndSpec.first; - spec = transformMatrixAndSpec.second; } + final Pair<float[], MagnificationSpec> transformMatrixAndSpec = + getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId); + final float[] transformMatrix = transformMatrixAndSpec.first; + final MagnificationSpec spec = transformMatrixAndSpec.second; if (!mSecurityPolicy.checkAccessibilityAccess(this)) { return null; } @@ -786,8 +783,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); - final MagnificationSpec spec; - final float[] transformMatrix; synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return null; @@ -811,11 +806,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } - final Pair<float[], MagnificationSpec> transformMatrixAndSpec = - getTransformMatrixAndSpecLocked(resolvedWindowId); - transformMatrix = transformMatrixAndSpec.first; - spec = transformMatrixAndSpec.second; } + final Pair<float[], MagnificationSpec> transformMatrixAndSpec = + getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId); + final float[] transformMatrix = transformMatrixAndSpec.first; + final MagnificationSpec spec = transformMatrixAndSpec.second; if (!mSecurityPolicy.checkAccessibilityAccess(this)) { return null; } @@ -865,8 +860,6 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int resolvedWindowId; RemoteAccessibilityConnection connection; Region partialInteractiveRegion = Region.obtain(); - final MagnificationSpec spec; - final float[] transformMatrix; synchronized (mLock) { if (!hasRightsToCurrentUserLocked()) { return null; @@ -889,12 +882,11 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ partialInteractiveRegion.recycle(); partialInteractiveRegion = null; } - - final Pair<float[], MagnificationSpec> transformMatrixAndSpec = - getTransformMatrixAndSpecLocked(resolvedWindowId); - transformMatrix = transformMatrixAndSpec.first; - spec = transformMatrixAndSpec.second; } + final Pair<float[], MagnificationSpec> transformMatrixAndSpec = + getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId); + final float[] transformMatrix = transformMatrixAndSpec.first; + final MagnificationSpec spec = transformMatrixAndSpec.second; if (!mSecurityPolicy.checkAccessibilityAccess(this)) { return null; } @@ -1672,21 +1664,10 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ mInvocationHandler.startInputLocked(connection, editorInfo, restarting); } - - @Nullable - Pair<float[], MagnificationSpec> getTransformMatrixAndSpecLocked(int resolvedWindowId) { - final WindowInfo windowInfo = - mA11yWindowManager.findWindowInfoByIdLocked(resolvedWindowId); - if (windowInfo == null) { - Slog.w(LOG_TAG, "getTransformMatrixAndSpec, windowInfo is null window id = " - + resolvedWindowId); - return new Pair<>(null, null); - } - - final MagnificationSpec spec = new MagnificationSpec(); - spec.setTo(windowInfo.mMagnificationSpec); - return new Pair<>(windowInfo.mTransformMatrix, spec); + private Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec( + int resolvedWindowId) { + return mSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(resolvedWindowId); } /** diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 4806514a0e13..99c849527034 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -68,6 +68,7 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.database.ContentObserver; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; @@ -99,6 +100,7 @@ import android.text.TextUtils; import android.text.TextUtils.SimpleStringSplitter; import android.util.ArraySet; import android.util.IntArray; +import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.view.Display; @@ -458,6 +460,41 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } @Override + public Pair<float[], MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec( + int windowId) { + WindowInfo windowInfo; + synchronized (mLock) { + windowInfo = mA11yWindowManager.findWindowInfoByIdLocked(windowId); + } + if (windowInfo != null) { + final MagnificationSpec spec = new MagnificationSpec(); + spec.setTo(windowInfo.mMagnificationSpec); + return new Pair<>(windowInfo.mTransformMatrix, spec); + } else { + // If the framework doesn't track windows, we fall back to get the pair of + // transformation matrix and MagnificationSpe from the WindowManagerService's + // WindowState. + IBinder token; + synchronized (mLock) { + token = mA11yWindowManager.getWindowTokenForUserAndWindowIdLocked(mCurrentUserId, + windowId); + } + Pair<Matrix, MagnificationSpec> pair = + mWindowManagerService.getWindowTransformationMatrixAndMagnificationSpec(token); + final float[] outTransformationMatrix = new float[9]; + final Matrix tmpMatrix = pair.first; + final MagnificationSpec spec = pair.second; + if (!spec.isNop()) { + tmpMatrix.postScale(spec.scale, spec.scale); + tmpMatrix.postTranslate(spec.offsetX, spec.offsetY); + } + tmpMatrix.getValues(outTransformationMatrix); + + return new Pair<>(outTransformationMatrix, pair.second); + } + } + + @Override public void onServiceInfoChangedLocked(AccessibilityUserState userState) { mSecurityPolicy.onBoundServicesChangedLocked(userState.mUserId, userState.mBoundServices); @@ -3750,12 +3787,12 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub boundsInScreenBeforeMagnification.centerY()); // Invert magnification if needed. - final WindowInfo windowInfo = mA11yWindowManager.findWindowInfoByIdLocked( - focus.getWindowId()); + final Pair<float[], MagnificationSpec> pair = + getWindowTransformationMatrixAndMagnificationSpec(focus.getWindowId()); MagnificationSpec spec = null; - if (windowInfo != null) { + if (pair != null && pair.second != null) { spec = new MagnificationSpec(); - spec.setTo(windowInfo.mMagnificationSpec); + spec.setTo(pair.second); } if (spec != null && !spec.isNop()) { diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index f18d13d30da6..6d3620fa2ee6 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -1559,9 +1559,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.e(TAG, "Error sending input show up notification", e); } } + } + + // AutoFillUiCallback + @Override + public void requestFallbackFromFillDialog() { + setFillDialogDisabled(); synchronized (mLock) { - // stop to show fill dialog - mSessionFlags.mFillDialogDisabled = true; + if (mCurrentViewId == null) { + return; + } + final ViewState currentView = mViewStates.get(mCurrentViewId); + currentView.maybeCallOnFillReady(mFlags); } } @@ -3208,16 +3217,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - if (requestShowFillDialog(response, filledId, filterText, flags)) { - synchronized (mLock) { - final ViewState currentView = mViewStates.get(mCurrentViewId); - currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); - mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG); + final AutofillId[] ids = response.getFillDialogTriggerIds(); + if (ids != null && ArrayUtils.contains(ids, filledId)) { + if (requestShowFillDialog(response, filledId, filterText, flags)) { + synchronized (mLock) { + final ViewState currentView = mViewStates.get(mCurrentViewId); + currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN); + mService.logDatasetShown(id, mClientState, UI_TYPE_DIALOG); + } + // Just show fill dialog once, so disabled after shown. + // Note: Cannot disable before requestShowFillDialog() because the method + // need to check whether fill dialog enabled. + setFillDialogDisabled(); + return; + } else { + setFillDialogDisabled(); } - return; - } - setFillDialogDisabled(); + } if (response.supportsInlineSuggestions()) { synchronized (mLock) { @@ -3324,15 +3341,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - final AutofillId[] ids = response.getFillDialogTriggerIds(); - if (ids == null || !ArrayUtils.contains(ids, filledId)) { - return false; - } - final Drawable serviceIcon = getServiceIcon(); getUiForShowing().showFillDialog(filledId, response, filterText, - mService.getServicePackageName(), mComponentName, serviceIcon, this); + mService.getServicePackageName(), mComponentName, serviceIcon, this, + id, mCompatMode); return true; } diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java index 57768ef32391..3ab873de4bb5 100644 --- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java +++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java @@ -97,6 +97,7 @@ public final class AutoFillUI { void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent); void cancelSession(); void requestShowSoftInput(AutofillId id); + void requestFallbackFromFillDialog(); } public AutoFillUI(@NonNull Context context) { @@ -388,13 +389,19 @@ public final class AutoFillUI { public void showFillDialog(@NonNull AutofillId focusedId, @NonNull FillResponse response, @Nullable String filterText, @Nullable String servicePackageName, @NonNull ComponentName componentName, @Nullable Drawable serviceIcon, - @NonNull AutoFillUiCallback callback) { + @NonNull AutoFillUiCallback callback, int sessionId, boolean compatMode) { if (sVerbose) { Slog.v(TAG, "showFillDialog for " + componentName.toShortString() + ": " + response); } - // TODO: enable LogMaker + final LogMaker log = Helper + .newLogMaker(MetricsEvent.AUTOFILL_FILL_UI, componentName, servicePackageName, + sessionId, compatMode) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FILTERTEXT_LEN, + filterText == null ? 0 : filterText.length()) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, + response.getDatasets() == null ? 0 : response.getDatasets().size()); mHandler.post(() -> { if (callback != mCallback) { @@ -406,6 +413,7 @@ public final class AutoFillUI { mUiModeMgr.isNightMode(), new DialogFillUi.UiCallback() { @Override public void onResponsePicked(FillResponse response) { + log(MetricsEvent.TYPE_DETAIL); hideFillDialogUiThread(callback); if (mCallback != null) { mCallback.authenticate(response.getRequestId(), @@ -417,6 +425,7 @@ public final class AutoFillUI { @Override public void onDatasetPicked(Dataset dataset) { + log(MetricsEvent.TYPE_ACTION); hideFillDialogUiThread(callback); if (mCallback != null) { final int datasetIndex = response.getDatasets().indexOf(dataset); @@ -426,15 +435,29 @@ public final class AutoFillUI { } @Override + public void onDismissed() { + log(MetricsEvent.TYPE_DISMISS); + hideFillDialogUiThread(callback); + callback.requestShowSoftInput(focusedId); + } + + @Override public void onCanceled() { + log(MetricsEvent.TYPE_CLOSE); hideFillDialogUiThread(callback); callback.requestShowSoftInput(focusedId); + callback.requestFallbackFromFillDialog(); } @Override public void startIntentSender(IntentSender intentSender) { mCallback.startIntentSenderAndFinishSession(intentSender); } + + private void log(int type) { + log.setType(type); + mMetricsLogger.write(log); + } }); }); } diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java index f9f5289ec6bf..5a1a1ae3908d 100644 --- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java +++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java @@ -81,6 +81,7 @@ final class DialogFillUi { interface UiCallback { void onResponsePicked(@NonNull FillResponse response); void onDatasetPicked(@NonNull Dataset dataset); + void onDismissed(); void onCanceled(); void startIntentSender(IntentSender intentSender); } @@ -144,6 +145,7 @@ final class DialogFillUi { mDialog = new Dialog(mContext, mThemeId); mDialog.setContentView(decor); setDialogParamsAsBottomSheet(); + mDialog.setOnCancelListener((d) -> mCallback.onCanceled()); show(); } @@ -220,7 +222,7 @@ final class DialogFillUi { final TextView noButton = decor.findViewById(R.id.autofill_dialog_no); // set "No thinks" by default noButton.setText(R.string.autofill_save_no); - noButton.setOnClickListener((v) -> mCallback.onCanceled()); + noButton.setOnClickListener((v) -> mCallback.onDismissed()); } private void setContinueButton(View decor, View.OnClickListener listener) { diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index b4c107c1a2c1..3b11038daf7e 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -294,6 +294,15 @@ public class CompanionDeviceManagerService extends SystemService { private boolean onCompanionApplicationBindingDiedInternal( @UserIdInt int userId, @NonNull String packageName) { + // Update the current connected devices sets when binderDied, so that application is able + // to call notifyDeviceAppeared after re-launch the application. + for (AssociationInfo ai : + mAssociationStore.getAssociationsForPackage(userId, packageName)) { + int id = ai.getId(); + Slog.i(TAG, "Removing association id: " + id + " for package: " + + packageName + " due to binderDied."); + mDevicePresenceMonitor.removeDeviceFromMonitoring(id); + } // TODO(b/218613015): implement. return false; } @@ -723,9 +732,12 @@ public class CompanionDeviceManagerService extends SystemService { String[] args, ShellCallback callback, ResultReceiver resultReceiver) throws RemoteException { enforceCallerCanManageCompanionDevice(getContext(), "onShellCommand"); - new CompanionDeviceShellCommand( - CompanionDeviceManagerService.this, mAssociationStore) - .exec(this, in, out, err, args, callback, resultReceiver); + + final CompanionDeviceShellCommand cmd = new CompanionDeviceShellCommand( + CompanionDeviceManagerService.this, + mAssociationStore, + mDevicePresenceMonitor); + cmd.exec(this, in, out, err, args, callback, resultReceiver); } @Override diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index fd130852a43a..6a19a42723c5 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -21,6 +21,8 @@ import android.os.ShellCommand; import android.util.Log; import android.util.Slog; +import com.android.server.companion.presence.CompanionDevicePresenceMonitor; + import java.io.PrintWriter; import java.util.List; @@ -29,20 +31,24 @@ class CompanionDeviceShellCommand extends ShellCommand { private final CompanionDeviceManagerService mService; private final AssociationStore mAssociationStore; + private final CompanionDevicePresenceMonitor mDevicePresenceMonitor; CompanionDeviceShellCommand(CompanionDeviceManagerService service, - AssociationStore associationStore) { + AssociationStore associationStore, + CompanionDevicePresenceMonitor devicePresenceMonitor) { mService = service; mAssociationStore = associationStore; + mDevicePresenceMonitor = devicePresenceMonitor; } @Override public int onCommand(String cmd) { final PrintWriter out = getOutPrintWriter(); + final int associationId; try { switch (cmd) { case "list": { - final int userId = getNextArgInt(); + final int userId = getNextIntArgRequired(); final List<AssociationInfo> associationsForUser = mAssociationStore.getAssociationsForUser(userId); for (AssociationInfo association : associationsForUser) { @@ -55,7 +61,7 @@ class CompanionDeviceShellCommand extends ShellCommand { break; case "associate": { - int userId = getNextArgInt(); + int userId = getNextIntArgRequired(); String packageName = getNextArgRequired(); String address = getNextArgRequired(); mService.legacyCreateAssociation(userId, address, packageName, null); @@ -63,7 +69,7 @@ class CompanionDeviceShellCommand extends ShellCommand { break; case "disassociate": { - final int userId = getNextArgInt(); + final int userId = getNextIntArgRequired(); final String packageName = getNextArgRequired(); final String address = getNextArgRequired(); final AssociationInfo association = @@ -80,6 +86,16 @@ class CompanionDeviceShellCommand extends ShellCommand { } break; + case "simulate-device-appeared": + associationId = getNextIntArgRequired(); + mDevicePresenceMonitor.simulateDeviceAppeared(associationId); + break; + + case "simulate-device-disappeared": + associationId = getNextIntArgRequired(); + mDevicePresenceMonitor.simulateDeviceDisappeared(associationId); + break; + default: return handleDefaultCommands(cmd); } @@ -91,10 +107,6 @@ class CompanionDeviceShellCommand extends ShellCommand { } } - private int getNextArgInt() { - return Integer.parseInt(getNextArgRequired()); - } - @Override public void onHelp() { PrintWriter pw = getOutPrintWriter(); @@ -108,7 +120,31 @@ class CompanionDeviceShellCommand extends ShellCommand { pw.println(" disassociate USER_ID PACKAGE MAC_ADDRESS"); pw.println(" Remove an existing Association."); pw.println(" clear-association-memory-cache"); - pw.println(" Clear the in-memory association cache and reload all association " - + "information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); + pw.println(" Clear the in-memory association cache and reload all association "); + pw.println(" information from persistent storage. USE FOR DEBUGGING PURPOSES ONLY."); + pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + + pw.println(" simulate-device-appeared ASSOCIATION_ID"); + pw.println(" Make CDM act as if the given companion device has appeared."); + pw.println(" I.e. bind the associated companion application's"); + pw.println(" CompanionDeviceService(s) and trigger onDeviceAppeared() callback."); + pw.println(" The CDM will consider the devices as present for 60 seconds and then"); + pw.println(" will act as if device disappeared, unless 'simulate-device-disappeared'"); + pw.println(" or 'simulate-device-appeared' is called again before 60 seconds run out" + + "."); + pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + + pw.println(" simulate-device-disappeared ASSOCIATION_ID"); + pw.println(" Make CDM act as if the given companion device has disappeared."); + pw.println(" I.e. unbind the associated companion application's"); + pw.println(" CompanionDeviceService(s) and trigger onDeviceDisappeared() callback."); + pw.println(" NOTE: This will only have effect if 'simulate-device-appeared' was"); + pw.println(" invoked for the same device (same ASSOCIATION_ID) no longer than"); + pw.println(" 60 seconds ago."); + pw.println(" USE FOR DEBUGGING AND/OR TESTING PURPOSES ONLY."); + } + + private int getNextIntArgRequired() { + return Integer.parseInt(getNextArgRequired()); } } diff --git a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java index 6371b25e6347..37e83697c787 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -16,11 +16,19 @@ package com.android.server.companion.presence; +import static android.os.Process.ROOT_UID; +import static android.os.Process.SHELL_UID; + import android.annotation.NonNull; import android.annotation.SuppressLint; +import android.annotation.TestApi; import android.bluetooth.BluetoothAdapter; import android.companion.AssociationInfo; import android.content.Context; +import android.os.Binder; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; import android.util.Log; import com.android.server.companion.AssociationStore; @@ -72,6 +80,11 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange private final @NonNull Set<Integer> mNearbyBleDevices = new HashSet<>(); private final @NonNull Set<Integer> mReportedSelfManagedDevices = new HashSet<>(); + // Tracking "simulated" presence. Used for debugging and testing only. + private final @NonNull Set<Integer> mSimulated = new HashSet<>(); + private final SimulatedDevicePresenceSchedulerHelper mSchedulerHelper = + new SimulatedDevicePresenceSchedulerHelper(); + public CompanionDevicePresenceMonitor(@NonNull AssociationStore associationStore, @NonNull Callback callback) { mAssociationStore = associationStore; @@ -106,7 +119,8 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange public boolean isDevicePresent(int associationId) { return mReportedSelfManagedDevices.contains(associationId) || mConnectedBtDevices.contains(associationId) - || mNearbyBleDevices.contains(associationId); + || mNearbyBleDevices.contains(associationId) + || mSimulated.contains(associationId); } /** @@ -155,6 +169,45 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange onDeviceGone(mNearbyBleDevices, associationId, /* sourceLoggingTag */ "ble"); } + /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ + @TestApi + public void simulateDeviceAppeared(int associationId) { + // IMPORTANT: this API should only be invoked via the + // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to + // make this call are SHELL and ROOT. + // No other caller (including SYSTEM!) should be allowed. + enforceCallerShellOrRoot(); + // Make sure the association exists. + enforceAssociationExists(associationId); + + onDevicePresent(mSimulated, associationId, /* sourceLoggingTag */ "simulated"); + + mSchedulerHelper.scheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); + } + + /** FOR DEBUGGING AND/OR TESTING PURPOSES ONLY. */ + @TestApi + public void simulateDeviceDisappeared(int associationId) { + // IMPORTANT: this API should only be invoked via the + // 'companiondevice simulate-device-appeared' Shell command, so the only uid-s allowed to + // make this call are SHELL and ROOT. + // No other caller (including SYSTEM!) should be allowed. + enforceCallerShellOrRoot(); + // Make sure the association exists. + enforceAssociationExists(associationId); + + mSchedulerHelper.unscheduleOnDeviceGoneCallForSimulatedDevicePresence(associationId); + + onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated"); + } + + private void enforceAssociationExists(int associationId) { + if (mAssociationStore.getAssociationById(associationId) == null) { + throw new IllegalArgumentException( + "Association with id " + associationId + " does not exist."); + } + } + private void onDevicePresent(@NonNull Set<Integer> presentDevicesForSource, int newDeviceAssociationId, @NonNull String sourceLoggingTag) { if (DEBUG) { @@ -206,6 +259,16 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange } /** + * Remove the current connected devices by associationId. + */ + public void removeDeviceFromMonitoring(int associationId) { + mConnectedBtDevices.remove(associationId); + mNearbyBleDevices.remove(associationId); + mReportedSelfManagedDevices.remove(associationId); + mSimulated.remove(associationId); + } + + /** * Implements * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)} */ @@ -217,12 +280,42 @@ public class CompanionDevicePresenceMonitor implements AssociationStore.OnChange Log.d(TAG, " > association=" + association); } - mConnectedBtDevices.remove(id); - mNearbyBleDevices.remove(id); - mReportedSelfManagedDevices.remove(id); + removeDeviceFromMonitoring(id); // Do NOT call mCallback.onDeviceDisappeared()! // CompanionDeviceManagerService will know that the association is removed, and will do // what's needed. } + + private static void enforceCallerShellOrRoot() { + final int callingUid = Binder.getCallingUid(); + if (callingUid == SHELL_UID || callingUid == ROOT_UID) return; + + throw new SecurityException("Caller is neither Shell nor Root"); + } + + private class SimulatedDevicePresenceSchedulerHelper extends Handler { + SimulatedDevicePresenceSchedulerHelper() { + super(Looper.getMainLooper()); + } + + void scheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) { + // First, unschedule if it was scheduled previously. + if (hasMessages(/* what */ associationId)) { + removeMessages(/* what */ associationId); + } + + sendEmptyMessageDelayed(/* what */ associationId, 60 * 1000 /* 60 seconds */); + } + + void unscheduleOnDeviceGoneCallForSimulatedDevicePresence(int associationId) { + removeMessages(/* what */ associationId); + } + + @Override + public void handleMessage(@NonNull Message msg) { + final int associationId = msg.what; + onDeviceGone(mSimulated, associationId, /* sourceLoggingTag */ "simulated"); + } + } } diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index 9d4b50be41fb..80182d26003d 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -22,6 +22,7 @@ import android.annotation.StringDef; import android.graphics.Point; import android.graphics.PointF; import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.InputDeviceIdentifier; import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; @@ -29,11 +30,13 @@ import android.hardware.input.VirtualMouseButtonEvent; import android.hardware.input.VirtualMouseRelativeEvent; import android.hardware.input.VirtualMouseScrollEvent; import android.hardware.input.VirtualTouchEvent; +import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.util.ArrayMap; import android.util.Slog; import android.view.Display; +import android.view.InputDevice; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -44,7 +47,11 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.Iterator; import java.util.Map; +import java.util.Objects; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Supplier; /** Controls virtual input devices, including device lifecycle and event dispatch. */ class InputController { @@ -72,20 +79,27 @@ class InputController { @GuardedBy("mLock") final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>(); + private final Handler mHandler; private final NativeWrapper mNativeWrapper; private final DisplayManagerInternal mDisplayManagerInternal; private final InputManagerInternal mInputManagerInternal; + private final DeviceCreationThreadVerifier mThreadVerifier; - InputController(@NonNull Object lock) { - this(lock, new NativeWrapper()); + InputController(@NonNull Object lock, @NonNull Handler handler) { + this(lock, new NativeWrapper(), handler, + // Verify that virtual devices are not created on the handler thread. + () -> !handler.getLooper().isCurrentThread()); } @VisibleForTesting - InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper) { + InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper, + @NonNull Handler handler, @NonNull DeviceCreationThreadVerifier threadVerifier) { mLock = lock; + mHandler = handler; mNativeWrapper = nativeWrapper; mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); + mThreadVerifier = threadVerifier; } void close() { @@ -108,23 +122,13 @@ class InputController { @NonNull IBinder deviceToken, int displayId) { final String phys = createPhys(PHYS_TYPE_KEYBOARD); - setUniqueIdAssociation(displayId, phys); - final int fd = mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys); - if (fd < 0) { - throw new RuntimeException( - "A native error occurred when creating keyboard: " + -fd); - } - final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); - synchronized (mLock) { - mInputDeviceDescriptors.put(deviceToken, - new InputDeviceDescriptor(fd, binderDeathRecipient, - InputDeviceDescriptor.TYPE_KEYBOARD, displayId, phys)); - } try { - deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); - } catch (RemoteException e) { - // TODO(b/215608394): remove and close InputDeviceDescriptor - throw new RuntimeException("Could not create virtual keyboard", e); + createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId, + productId, deviceToken, displayId, phys, + () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys)); + } catch (DeviceCreationException e) { + throw new RuntimeException( + "Failed to create virtual keyboard device '" + deviceName + "'.", e); } } @@ -134,25 +138,15 @@ class InputController { @NonNull IBinder deviceToken, int displayId) { final String phys = createPhys(PHYS_TYPE_MOUSE); - setUniqueIdAssociation(displayId, phys); - final int fd = mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys); - if (fd < 0) { - throw new RuntimeException( - "A native error occurred when creating mouse: " + -fd); - } - final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); - synchronized (mLock) { - mInputDeviceDescriptors.put(deviceToken, - new InputDeviceDescriptor(fd, binderDeathRecipient, - InputDeviceDescriptor.TYPE_MOUSE, displayId, phys)); - mInputManagerInternal.setVirtualMousePointerDisplayId(displayId); - } try { - deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); - } catch (RemoteException e) { - // TODO(b/215608394): remove and close InputDeviceDescriptor - throw new RuntimeException("Could not create virtual mouse", e); + createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId, + deviceToken, displayId, phys, + () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys)); + } catch (DeviceCreationException e) { + throw new RuntimeException( + "Failed to create virtual mouse device: '" + deviceName + "'.", e); } + mInputManagerInternal.setVirtualMousePointerDisplayId(displayId); } void createTouchscreen(@NonNull String deviceName, @@ -162,24 +156,14 @@ class InputController { int displayId, @NonNull Point screenSize) { final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN); - setUniqueIdAssociation(displayId, phys); - final int fd = mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys, - screenSize.y, screenSize.x); - if (fd < 0) { - throw new RuntimeException( - "A native error occurred when creating touchscreen: " + -fd); - } - final BinderDeathRecipient binderDeathRecipient = new BinderDeathRecipient(deviceToken); - synchronized (mLock) { - mInputDeviceDescriptors.put(deviceToken, - new InputDeviceDescriptor(fd, binderDeathRecipient, - InputDeviceDescriptor.TYPE_TOUCHSCREEN, displayId, phys)); - } try { - deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); - } catch (RemoteException e) { - // TODO(b/215608394): remove and close InputDeviceDescriptor - throw new RuntimeException("Could not create virtual touchscreen", e); + createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId, + productId, deviceToken, displayId, phys, + () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, + phys, screenSize.y, screenSize.x)); + } catch (DeviceCreationException e) { + throw new RuntimeException( + "Failed to create virtual touchscreen device '" + deviceName + "'.", e); } } @@ -510,4 +494,133 @@ class InputController { unregisterInputDevice(mDeviceToken); } } + + /** A helper class used to wait for an input device to be registered. */ + private class WaitForDevice implements AutoCloseable { + private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1); + private final InputManager.InputDeviceListener mListener; + + WaitForDevice(String deviceName, int vendorId, int productId) { + mListener = new InputManager.InputDeviceListener() { + @Override + public void onInputDeviceAdded(int deviceId) { + final InputDevice device = InputManager.getInstance().getInputDevice( + deviceId); + Objects.requireNonNull(device, "Newly added input device was null."); + if (!device.getName().equals(deviceName)) { + return; + } + final InputDeviceIdentifier id = device.getIdentifier(); + if (id.getVendorId() != vendorId || id.getProductId() != productId) { + return; + } + mDeviceAddedLatch.countDown(); + } + + @Override + public void onInputDeviceRemoved(int deviceId) { + + } + + @Override + public void onInputDeviceChanged(int deviceId) { + + } + }; + InputManager.getInstance().registerInputDeviceListener(mListener, mHandler); + } + + /** Note: This must not be called from {@link #mHandler}'s thread. */ + void waitForDeviceCreation() throws DeviceCreationException { + try { + if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) { + throw new DeviceCreationException( + "Timed out waiting for virtual device to be created."); + } + } catch (InterruptedException e) { + throw new DeviceCreationException( + "Interrupted while waiting for virtual device to be created.", e); + } + } + + @Override + public void close() { + InputManager.getInstance().unregisterInputDeviceListener(mListener); + } + } + + /** An internal exception that is thrown to indicate an error when opening a virtual device. */ + private static class DeviceCreationException extends Exception { + DeviceCreationException(String message) { + super(message); + } + DeviceCreationException(String message, Exception cause) { + super(message, cause); + } + } + + /** + * Creates a virtual input device synchronously, and waits for the notification that the device + * was added. + * + * Note: Input device creation is expected to happen on a binder thread, and the calling thread + * will be blocked until the input device creation is successful. This should not be called on + * the handler's thread. + * + * @throws DeviceCreationException Throws this exception if anything unexpected happens in the + * process of creating the device. This method will take care + * to restore the state of the system in the event of any + * unexpected behavior. + */ + private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName, + int vendorId, int productId, IBinder deviceToken, int displayId, String phys, + Supplier<Integer> deviceOpener) + throws DeviceCreationException { + if (!mThreadVerifier.isValidThread()) { + throw new IllegalStateException( + "Virtual device creation should happen on an auxiliary thread (e.g. binder " + + "thread) and not from the handler's thread."); + } + + final int fd; + final BinderDeathRecipient binderDeathRecipient; + + setUniqueIdAssociation(displayId, phys); + try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) { + fd = deviceOpener.get(); + if (fd < 0) { + throw new DeviceCreationException( + "A native error occurred when creating touchscreen: " + -fd); + } + // The fd is valid from here, so ensure that all failures close the fd after this point. + try { + waiter.waitForDeviceCreation(); + + binderDeathRecipient = new BinderDeathRecipient(deviceToken); + try { + deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); + } catch (RemoteException e) { + throw new DeviceCreationException( + "Client died before virtual device could be created.", e); + } + } catch (DeviceCreationException e) { + mNativeWrapper.closeUinput(fd); + throw e; + } + } catch (DeviceCreationException e) { + InputManager.getInstance().removeUniqueIdAssociation(phys); + throw e; + } + + synchronized (mLock) { + mInputDeviceDescriptors.put(deviceToken, + new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys)); + } + } + + @VisibleForTesting + interface DeviceCreationThreadVerifier { + /** Returns true if the calling thread is a valid thread for device creation. */ + boolean isValidThread(); + } } diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index de14ef61a075..9802b9783da2 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -166,7 +166,8 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mAppToken = token; mParams = params; if (inputController == null) { - mInputController = new InputController(mVirtualDeviceLock); + mInputController = new InputController( + mVirtualDeviceLock, context.getMainThreadHandler()); } else { mInputController = inputController; } diff --git a/services/core/java/com/android/server/StorageManagerService.java b/services/core/java/com/android/server/StorageManagerService.java index 8f37823e4f9a..bc40170d39b7 100644 --- a/services/core/java/com/android/server/StorageManagerService.java +++ b/services/core/java/com/android/server/StorageManagerService.java @@ -3055,19 +3055,7 @@ class StorageManagerService extends IStorageManager.Stub return true; } - if (packageName == null) { - return false; - } - - final int packageUid = mPmInternal.getPackageUid(packageName, - PackageManager.MATCH_DEBUG_TRIAGED_MISSING, UserHandle.getUserId(callerUid)); - - if (DEBUG_OBB) { - Slog.d(TAG, "packageName = " + packageName + ", packageUid = " + - packageUid + ", callerUid = " + callerUid); - } - - return callerUid == packageUid; + return mPmInternal.isSameApp(packageName, callerUid, UserHandle.getUserId(callerUid)); } @Override diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 2ceb00d1ac02..dea8cc015272 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -89,7 +89,6 @@ import static android.os.Process.killProcessQuiet; import static android.os.Process.myPid; import static android.os.Process.myUid; import static android.os.Process.readProcFile; -import static android.os.Process.removeAllProcessGroups; import static android.os.Process.sendSignal; import static android.os.Process.setThreadPriority; import static android.os.Process.setThreadScheduler; @@ -2440,8 +2439,6 @@ public class ActivityManagerService extends IActivityManager.Stub } private void start() { - removeAllProcessGroups(); - mBatteryStatsService.publish(); mAppOpsService.publish(); mProcessStats.publish(); diff --git a/services/core/java/com/android/server/am/AppBatteryTracker.java b/services/core/java/com/android/server/am/AppBatteryTracker.java index 6e28d8fbad59..5a234f5010dd 100644 --- a/services/core/java/com/android/server/am/AppBatteryTracker.java +++ b/services/core/java/com/android/server/am/AppBatteryTracker.java @@ -56,7 +56,6 @@ import android.os.BatteryConsumer.Dimensions; import android.os.BatteryStatsInternal; import android.os.BatteryUsageStats; import android.os.BatteryUsageStatsQuery; -import android.os.Build; import android.os.PowerExemptionManager; import android.os.PowerExemptionManager.ReasonCode; import android.os.SystemClock; @@ -97,7 +96,7 @@ final class AppBatteryTracker extends BaseAppStateTracker<AppBatteryPolicy> static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER = false; static final boolean DEBUG_BACKGROUND_BATTERY_TRACKER_VERBOSE = - DEBUG_BACKGROUND_BATTERY_TRACKER | Build.IS_DEBUGGABLE; + DEBUG_BACKGROUND_BATTERY_TRACKER | false; // As we don't support realtime per-UID battery usage stats yet, we're polling the stats // in a regular time basis. diff --git a/services/core/java/com/android/server/am/AppRestrictionController.java b/services/core/java/com/android/server/am/AppRestrictionController.java index f7abb117e3de..6f5d87bab15f 100644 --- a/services/core/java/com/android/server/am/AppRestrictionController.java +++ b/services/core/java/com/android/server/am/AppRestrictionController.java @@ -265,6 +265,20 @@ public final class AppRestrictionController { */ private int[] mDeviceIdleExceptIdleAllowlist = new int[0]; // No lock is needed. + /** + * The pre-configured system app-ids in the power-save allow list. + * + * @see #mDeviceIdleAllowlist. + */ + private final ArraySet<Integer> mSystemDeviceIdleAllowlist = new ArraySet<>(); + + /** + * The pre-configured system app-ids in the power-save allow list, except-idle. + * + * @see #mDeviceIdleExceptIdleAllowlist. + */ + private final ArraySet<Integer> mSystemDeviceIdleExceptIdleAllowlist = new ArraySet<>(); + private final Object mLock = new Object(); private final Object mSettingsLock = new Object(); private final Injector mInjector; @@ -1511,14 +1525,33 @@ public final class AppRestrictionController { } private void initBgRestrictionExemptioFromSysConfig() { - mBgRestrictionExemptioFromSysConfig = - SystemConfig.getInstance().getBgRestrictionExemption(); + final SystemConfig sysConfig = SystemConfig.getInstance(); + mBgRestrictionExemptioFromSysConfig = sysConfig.getBgRestrictionExemption(); if (DEBUG_BG_RESTRICTION_CONTROLLER) { final ArraySet<String> exemptedPkgs = mBgRestrictionExemptioFromSysConfig; for (int i = exemptedPkgs.size() - 1; i >= 0; i--) { Slog.i(TAG, "bg-restriction-exemption: " + exemptedPkgs.valueAt(i)); } } + loadAppIdsFromPackageList(sysConfig.getAllowInPowerSaveExceptIdle(), + mSystemDeviceIdleExceptIdleAllowlist); + loadAppIdsFromPackageList(sysConfig.getAllowInPowerSave(), mSystemDeviceIdleAllowlist); + } + + private void loadAppIdsFromPackageList(ArraySet<String> packages, ArraySet<Integer> apps) { + final PackageManager pm = mInjector.getPackageManager(); + for (int i = packages.size() - 1; i >= 0; i--) { + final String pkg = packages.valueAt(i); + try { + final ApplicationInfo ai = pm.getApplicationInfo(pkg, + PackageManager.MATCH_SYSTEM_ONLY); + if (ai == null) { + continue; + } + apps.add(UserHandle.getAppId(ai.uid)); + } catch (PackageManager.NameNotFoundException e) { + } + } } private boolean isExemptedFromSysConfig(String packageName) { @@ -2685,6 +2718,13 @@ public final class AppRestrictionController { || Arrays.binarySearch(mDeviceIdleExceptIdleAllowlist, appId) >= 0; } + boolean isOnSystemDeviceIdleAllowlist(int uid) { + final int appId = UserHandle.getAppId(uid); + + return mSystemDeviceIdleAllowlist.contains(appId) + || mSystemDeviceIdleExceptIdleAllowlist.contains(appId); + } + void setDeviceIdleAllowlist(int[] allAppids, int[] exceptIdleAppids) { mDeviceIdleAllowlist = allAppids; mDeviceIdleExceptIdleAllowlist = exceptIdleAppids; @@ -2703,6 +2743,9 @@ public final class AppRestrictionController { if (UserHandle.isCore(uid)) { return REASON_SYSTEM_UID; } + if (isOnSystemDeviceIdleAllowlist(uid)) { + return REASON_SYSTEM_ALLOW_LISTED; + } if (isOnDeviceIdleAllowlist(uid)) { return REASON_ALLOWLISTED_PACKAGE; } @@ -2748,7 +2791,7 @@ public final class AppRestrictionController { } else if (isExemptedFromSysConfig(pkg)) { return REASON_SYSTEM_ALLOW_LISTED; } else if (mConstantsObserver.mBgRestrictionExemptedPackages.contains(pkg)) { - return REASON_ALLOWLISTED_PACKAGE; + return REASON_SYSTEM_ALLOW_LISTED; } else if (pm.isPackageStateProtected(pkg, userId)) { return REASON_DPO_PROTECTED_APP; } diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index a172018ab291..e49497e8688c 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -479,19 +479,33 @@ public final class CachedAppOptimizer { @GuardedBy("mProcLock") void compactAppSome(ProcessRecord app, boolean force) { app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_SOME); - if (DEBUG_COMPACTION) { - Slog.d(TAG_AM, " compactAppSome requested for " + app.processName + " force: " + force); + compactApp(app, force, "some"); + } + + // This method returns true only if requirements are met. Note, that requirements are different + // from throttles applied at the time a compaction is trying to be executed in the sense that + // these are not subject to change dependent on time or memory as throttles usually do. + @GuardedBy("mProcLock") + boolean meetsCompactionRequirements(ProcessRecord proc) { + if (mAm.mInternal.isPendingTopUid(proc.uid)) { + // In case the OOM Adjust has not yet been propagated we see if this is + // pending on becoming top app in which case we should not compact. + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Skip compaction since UID is active for " + proc.processName); + } + return false; } - if (force || !app.mOptRecord.hasPendingCompact()) { - Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, - "compactAppSome " + app.processName != null ? app.processName : ""); - app.mOptRecord.setHasPendingCompact(true); - app.mOptRecord.setForceCompact(force); - mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( - COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState())); + + if (proc.mState.hasForegroundActivities()) { + if (DEBUG_COMPACTION) { + Slog.e(TAG_AM, + "Skip compaction as process " + proc.processName + + " has foreground activities"); + } + return false; } + + return true; } @GuardedBy("mProcLock") @@ -508,19 +522,7 @@ public final class CachedAppOptimizer { // Apply OOM adj score throttle for Full App Compaction. if (force || oomAdjEnteredCached) { app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_FULL); - if (!app.mOptRecord.hasPendingCompact()) { - Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, - "compactAppFull " + app.processName != null ? app.processName : ""); - app.mOptRecord.setHasPendingCompact(true); - app.mOptRecord.setForceCompact(force); - mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage( - COMPACT_PROCESS_MSG, app.mState.getSetAdj(), app.mState.getSetProcState())); - } else if (DEBUG_COMPACTION) { - Slog.d(TAG_AM, - " compactAppFull Skipped for " + app.processName - + " since it has a pending compact"); - } + compactApp(app, force, "Full"); } else { if (DEBUG_COMPACTION) { Slog.d(TAG_AM, "Skipping full compaction for " + app.processName @@ -533,15 +535,34 @@ public final class CachedAppOptimizer { @GuardedBy("mProcLock") void compactAppPersistent(ProcessRecord app) { app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_PERSISTENT); - if (!app.mOptRecord.hasPendingCompact()) { + compactApp(app, false, "Persistent"); + } + + @GuardedBy("mProcLock") + boolean compactApp(ProcessRecord app, boolean force, String compactRequestType) { + if (!app.mOptRecord.hasPendingCompact() && meetsCompactionRequirements(app)) { + final String processName = (app.processName != null ? app.processName : ""); + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "compactApp " + compactRequestType + " " + processName); + } Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, - "compactAppPersistent " + app.processName != null ? app.processName : ""); + "compactApp " + compactRequestType + " " + processName); app.mOptRecord.setHasPendingCompact(true); + app.mOptRecord.setForceCompact(force); mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( + mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage( COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState())); + return true; + } + + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, + " compactApp Skipped for " + app.processName + + " pendingCompact= " + app.mOptRecord.hasPendingCompact() + + " meetsCompactionRequirements=" + meetsCompactionRequirements(app) + + ". Requested compact: " + app.mOptRecord.getReqCompactAction()); } + return false; } @GuardedBy("mProcLock") @@ -553,15 +574,7 @@ public final class CachedAppOptimizer { @GuardedBy("mProcLock") void compactAppBfgs(ProcessRecord app) { app.mOptRecord.setReqCompactAction(COMPACT_PROCESS_BFGS); - if (!app.mOptRecord.hasPendingCompact()) { - Trace.instantForTrack(Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, - "compactAppBfgs " + app.processName != null ? app.processName : ""); - app.mOptRecord.setHasPendingCompact(true); - mPendingCompactionProcesses.add(app); - mCompactionHandler.sendMessage( - mCompactionHandler.obtainMessage( - COMPACT_PROCESS_MSG, app.mState.getCurAdj(), app.mState.getSetProcState())); - } + compactApp(app, false, " Bfgs"); } @GuardedBy("mProcLock") @@ -572,6 +585,9 @@ public final class CachedAppOptimizer { void compactAllSystem() { if (useCompaction()) { + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "compactAllSystem"); + } Trace.instantForTrack( Trace.TRACE_TAG_ACTIVITY_MANAGER, ATRACE_COMPACTION_TRACK, "compactAllSystem"); mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage( @@ -1175,13 +1191,13 @@ public final class CachedAppOptimizer { cancelCompaction(); } - // Perform a minor compaction when a perceptible app becomes the prev/home app - // Perform a major compaction when any app enters cached if (oldAdj <= ProcessList.PERCEPTIBLE_APP_ADJ && (newAdj == ProcessList.PREVIOUS_APP_ADJ || newAdj == ProcessList.HOME_APP_ADJ)) { + // Perform a minor compaction when a perceptible app becomes the prev/home app compactAppSome(app, false); } else if (newAdj >= ProcessList.CACHED_APP_MIN_ADJ && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) { + // Perform a major compaction when any app enters cached compactAppFull(app, false); } } @@ -1241,12 +1257,6 @@ public final class CachedAppOptimizer { private boolean shouldOomAdjThrottleCompaction(ProcessRecord proc, int action) { final String name = proc.processName; - if (mAm.mInternal.isPendingTopUid(proc.uid)) { - // In case the OOM Adjust has not yet been propagated we see if this is - // pending on becoming top app in which case we should not compact. - Slog.e(TAG_AM, "Skip compaction since UID is active for " + name); - return true; - } // don't compact if the process has returned to perceptible // and this is only a cached/home/prev compaction diff --git a/services/core/java/com/android/server/am/ConnectionRecord.java b/services/core/java/com/android/server/am/ConnectionRecord.java index b6757c8322d4..17fff919ba20 100644 --- a/services/core/java/com/android/server/am/ConnectionRecord.java +++ b/services/core/java/com/android/server/am/ConnectionRecord.java @@ -72,7 +72,7 @@ final class ConnectionRecord { Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE, Context.BIND_FOREGROUND_SERVICE, Context.BIND_TREAT_LIKE_ACTIVITY, - Context.BIND_VISIBLE, + Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, Context.BIND_SHOWING_UI, Context.BIND_NOT_VISIBLE, Context.BIND_NOT_PERCEPTIBLE, @@ -225,8 +225,8 @@ final class ConnectionRecord { if ((flags & Context.BIND_SCHEDULE_LIKE_TOP_APP) != 0) { sb.append("SLTA "); } - if ((flags&Context.BIND_VISIBLE) != 0) { - sb.append("VIS "); + if ((flags & Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) != 0) { + sb.append("VFGS "); } if ((flags&Context.BIND_SHOWING_UI) != 0) { sb.append("UI "); diff --git a/services/core/java/com/android/server/am/DropboxRateLimiter.java b/services/core/java/com/android/server/am/DropboxRateLimiter.java index 18fb6a480f29..08a6719a016a 100644 --- a/services/core/java/com/android/server/am/DropboxRateLimiter.java +++ b/services/core/java/com/android/server/am/DropboxRateLimiter.java @@ -24,9 +24,14 @@ import com.android.internal.annotations.GuardedBy; /** Rate limiter for adding errors into dropbox. */ public class DropboxRateLimiter { - private static final long RATE_LIMIT_BUFFER_EXPIRY = 15 * DateUtils.SECOND_IN_MILLIS; - private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.SECOND_IN_MILLIS; - private static final int RATE_LIMIT_ALLOWED_ENTRIES = 5; + // After RATE_LIMIT_ALLOWED_ENTRIES have been collected (for a single breakdown of + // process/eventType) further entries will be rejected until RATE_LIMIT_BUFFER_DURATION has + // elapsed, after which the current count for this breakdown will be reset. + private static final long RATE_LIMIT_BUFFER_DURATION = 10 * DateUtils.MINUTE_IN_MILLIS; + // The time duration after which the rate limit buffer will be cleared. + private static final long RATE_LIMIT_BUFFER_EXPIRY = 3 * RATE_LIMIT_BUFFER_DURATION; + // The number of entries to keep per breakdown of process/eventType. + private static final int RATE_LIMIT_ALLOWED_ENTRIES = 6; @GuardedBy("mErrorClusterRecords") private final ArrayMap<String, ErrorRecord> mErrorClusterRecords = new ArrayMap<>(); diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 4d10574149ff..e7fcc5989467 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -39,6 +39,7 @@ import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; import static android.app.ActivityManager.PROCESS_STATE_TOP; import static android.app.ActivityManager.PROCESS_STATE_TRANSIENT_BACKGROUND; +import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_LOCATION; import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE; @@ -2061,6 +2062,10 @@ public class OomAdjuster { newAdj = ProcessList.PERCEPTIBLE_APP_ADJ; } else if (clientAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { newAdj = clientAdj; + } else if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE) + && clientAdj <= ProcessList.VISIBLE_APP_ADJ + && adj > ProcessList.VISIBLE_APP_ADJ) { + newAdj = ProcessList.VISIBLE_APP_ADJ; } else { if (adj > ProcessList.VISIBLE_APP_ADJ) { // TODO: Is this too limiting for apps bound from TOP? @@ -2097,7 +2102,9 @@ public class OomAdjuster { // processes). These should not bring the current process // into the top state, since they are not on top. Instead // give them the best bound state after that. - if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) { + if (cr.hasFlag(BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE)) { + clientProcState = PROCESS_STATE_FOREGROUND_SERVICE; + } else if (cr.hasFlag(Context.BIND_FOREGROUND_SERVICE)) { clientProcState = PROCESS_STATE_BOUND_FOREGROUND_SERVICE; } else if (mService.mWakefulness.get() == PowerManagerInternal.WAKEFULNESS_AWAKE @@ -2556,20 +2563,21 @@ public class OomAdjuster { // reminder: here, setAdj is previous state, curAdj is upcoming state if (state.getCurAdj() != state.getSetAdj()) { mCachedAppOptimizer.onOomAdjustChanged(state.getSetAdj(), state.getCurAdj(), app); - } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE - && state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ - && !state.isRunningRemoteAnimation() - // Because these can fire independent of oom_adj/procstate changes, we need - // to throttle the actual dispatch of these requests in addition to the - // processing of the requests. As a result, there is throttling both here - // and in CachedAppOptimizer. - && mCachedAppOptimizer.shouldCompactPersistent(app, now)) { - mCachedAppOptimizer.compactAppPersistent(app); - } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE - && state.getCurProcState() - == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE - && mCachedAppOptimizer.shouldCompactBFGS(app, now)) { - mCachedAppOptimizer.compactAppBfgs(app); + } else if (mService.mWakefulness.get() != PowerManagerInternal.WAKEFULNESS_AWAKE) { + // See if we can compact persistent and bfgs services now that screen is off + if (state.getSetAdj() < ProcessList.FOREGROUND_APP_ADJ + && !state.isRunningRemoteAnimation() + // Because these can fire independent of oom_adj/procstate changes, we need + // to throttle the actual dispatch of these requests in addition to the + // processing of the requests. As a result, there is throttling both here + // and in CachedAppOptimizer. + && mCachedAppOptimizer.shouldCompactPersistent(app, now)) { + mCachedAppOptimizer.compactAppPersistent(app); + } else if (state.getCurProcState() + == ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE + && mCachedAppOptimizer.shouldCompactBFGS(app, now)) { + mCachedAppOptimizer.compactAppBfgs(app); + } } } diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index fc73a5955001..cceacd8c6fa3 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -2792,6 +2792,15 @@ public final class ProcessList { } int N = procs.size(); + for (int i = 0; i < N; ++i) { + final ProcessRecord proc = procs.get(i).first; + try { + Process.setProcessFrozen(proc.getPid(), proc.uid, true); + } catch (Exception e) { + Slog.w(TAG, "Unable to freeze " + proc.getPid() + " " + proc.processName); + } + } + for (int i=0; i<N; i++) { final Pair<ProcessRecord, Boolean> proc = procs.get(i); removeProcessLocked(proc.first, callerWillRestart, allowRestart || proc.second, diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java index 3e97b913a158..36afb3677438 100644 --- a/services/core/java/com/android/server/appop/AppOpsService.java +++ b/services/core/java/com/android/server/appop/AppOpsService.java @@ -4545,8 +4545,9 @@ public class AppOpsService extends IAppOpsService.Stub { * @return The restriction matching the package */ private RestrictionBypass getBypassforPackage(@NonNull AndroidPackage pkg) { - return new RestrictionBypass(pkg.isPrivileged(), mContext.checkPermission( - android.Manifest.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid()) + return new RestrictionBypass(pkg.getUid() == Process.SYSTEM_UID, pkg.isPrivileged(), + mContext.checkPermission(android.Manifest.permission + .EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS, -1, pkg.getUid()) == PackageManager.PERMISSION_GRANTED); } @@ -4853,6 +4854,9 @@ public class AppOpsService extends IAppOpsService.Stub { if (opBypass != null) { // If we are the system, bypass user restrictions for certain codes synchronized (this) { + if (opBypass.isSystemUid && appBypass != null && appBypass.isSystemUid) { + return false; + } if (opBypass.isPrivileged && appBypass != null && appBypass.isPrivileged) { return false; } diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java index 122a950e4fd3..d8aa9aa6591e 100644 --- a/services/core/java/com/android/server/audio/SpatializerHelper.java +++ b/services/core/java/com/android/server/audio/SpatializerHelper.java @@ -486,8 +486,7 @@ public class SpatializerHelper { for (SADeviceState deviceState : mSADevices) { if (deviceType == deviceState.mDeviceType - && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) - || !wireless) { + && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) { isInList = true; if (forceEnable) { deviceState.mEnabled = true; @@ -511,8 +510,7 @@ public class SpatializerHelper { for (SADeviceState deviceState : mSADevices) { if (deviceType == deviceState.mDeviceType - && (wireless && ada.getAddress().equals(deviceState.mDeviceAddress)) - || !wireless) { + && (!wireless || ada.getAddress().equals(deviceState.mDeviceAddress))) { deviceState.mEnabled = false; break; } diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java index b5c8cd16a355..bc550d311370 100644 --- a/services/core/java/com/android/server/biometrics/AuthService.java +++ b/services/core/java/com/android/server/biometrics/AuthService.java @@ -805,9 +805,10 @@ public class AuthService extends SystemService { if (isUdfps && udfpsProps.length == 3) { return new FingerprintSensorPropertiesInternal(sensorId, Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser, - componentInfo, sensorType, resetLockoutRequiresHardwareAuthToken, - List.of(new SensorLocationInternal("" /* display */, - udfpsProps[0], udfpsProps[1], udfpsProps[2]))); + componentInfo, sensorType, true /* halControlsIllumination */, + resetLockoutRequiresHardwareAuthToken, + List.of(new SensorLocationInternal("" /* display */, udfpsProps[0], + udfpsProps[1], udfpsProps[2]))); } else { return new FingerprintSensorPropertiesInternal(sensorId, Utils.authenticatorStrengthToPropertyStrength(strength), maxEnrollmentsPerUser, diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java index 998a8e1e9f90..a600f08efc24 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java @@ -176,6 +176,7 @@ public class FingerprintProvider implements IBinder.DeathRecipient, ServiceProvi prop.commonProps.maxEnrollmentsPerUser, componentInfo, prop.sensorType, + prop.halControlsIllumination, true /* resetLockoutRequiresHardwareAuthToken */, !workaroundLocations.isEmpty() ? workaroundLocations : Arrays.stream(prop.sensorLocations).map(location -> diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java index 485a674dda92..bea0f4ffd45d 100644 --- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java +++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java @@ -400,7 +400,7 @@ public class Fingerprint21UdfpsMock extends Fingerprint21 implements TrustManage .getInteger(R.integer.config_fingerprintMaxTemplatesPerUser); mSensorProperties = new FingerprintSensorPropertiesInternal(sensorProps.sensorId, sensorProps.sensorStrength, maxTemplatesAllowed, sensorProps.componentInfo, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, + FingerprintSensorProperties.TYPE_UDFPS_OPTICAL, false /* halControlsIllumination */, resetLockoutRequiresHardwareAuthToken, sensorProps.getAllLocations()); mMockHalResultController = controller; mUserHasTrust = new SparseBooleanArray(); diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index f3d41c4003c3..2cf11c657aa0 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -1440,16 +1440,6 @@ public final class DisplayManagerService extends SystemService { DisplayDevice device = mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken); if (device != null) { - final LogicalDisplay display = mLogicalDisplayMapper.getDisplayLocked(device); - if (display != null) { - final int displayId = display.getDisplayIdLocked(); - if (mDisplayWindowPolicyControllers.contains(displayId)) { - Pair<IVirtualDevice, DisplayWindowPolicyController> pair = - mDisplayWindowPolicyControllers.removeReturnOld(displayId); - getLocalService(VirtualDeviceManagerInternal.class) - .onVirtualDisplayRemoved(pair.first, displayId); - } - } // TODO: multi-display - handle virtual displays the same as other display adapters. mDisplayDeviceRepo.onDisplayDeviceEvent(device, DisplayAdapter.DISPLAY_DEVICE_EVENT_REMOVED); @@ -1609,6 +1599,17 @@ public final class DisplayManagerService extends SystemService { DisplayManagerGlobal.invalidateLocalDisplayInfoCaches(); sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_REMOVED); scheduleTraversalLocked(false); + + if (mDisplayWindowPolicyControllers.contains(displayId)) { + final IVirtualDevice virtualDevice = mDisplayWindowPolicyControllers.removeReturnOld( + displayId).first; + if (virtualDevice != null) { + mHandler.post(() -> { + getLocalService(VirtualDeviceManagerInternal.class) + .onVirtualDisplayRemoved(virtualDevice, displayId); + }); + } + } } private void handleLogicalDisplaySwappedLocked(@NonNull LogicalDisplay display) { diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java index 8ab0b931be11..e4e9d1d49a6e 100644 --- a/services/core/java/com/android/server/input/InputManagerService.java +++ b/services/core/java/com/android/server/input/InputManagerService.java @@ -164,6 +164,7 @@ public class InputManagerService extends IInputManager.Stub private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4; private static final int MSG_RELOAD_DEVICE_ALIASES = 5; private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 6; + private static final int MSG_POINTER_DISPLAY_ID_CHANGED = 7; private static final int DEFAULT_VIBRATION_MAGNITUDE = 192; @@ -276,11 +277,24 @@ public class InputManagerService extends IInputManager.Stub @GuardedBy("mAssociationLock") private final Map<String, String> mUniqueIdAssociations = new ArrayMap<>(); + // 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. private final Object mAdditionalDisplayInputPropertiesLock = new Object(); - // Forces the MouseCursorController to target a specific display id. + // Forces the PointerController to target a specific display id. @GuardedBy("mAdditionalDisplayInputPropertiesLock") private int mOverriddenPointerDisplayId = Display.INVALID_DISPLAY; + + // PointerController is the source of truth of the pointer display. This is the value of the + // latest pointer display id reported by PointerController. + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private int mAcknowledgedPointerDisplayId = Display.INVALID_DISPLAY; + // This is the latest display id that IMS has requested PointerController to use. If there are + // no devices that can control the pointer, PointerController may end up disregarding this + // value. + @GuardedBy("mAdditionalDisplayInputPropertiesLock") + private int mRequestedPointerDisplayId = Display.INVALID_DISPLAY; @GuardedBy("mAdditionalDisplayInputPropertiesLock") private final SparseArray<AdditionalDisplayInputProperties> mAdditionalDisplayInputProperties = new SparseArray<>(); @@ -289,7 +303,6 @@ public class InputManagerService extends IInputManager.Stub @GuardedBy("mAdditionalDisplayInputPropertiesLock") private PointerIcon mIcon; - // Holds all the registered gesture monitors that are implemented as spy windows. The spy // windows are mapped by their InputChannel tokens. @GuardedBy("mInputMonitors") @@ -383,6 +396,10 @@ public class InputManagerService extends IInputManager.Stub NativeInputManagerService getNativeService(InputManagerService service) { return new NativeInputManagerService.NativeImpl(service, mContext, mLooper.getQueue()); } + + void registerLocalService(InputManagerInternal localService) { + LocalServices.addService(InputManagerInternal.class, localService); + } } public InputManagerService(Context context) { @@ -391,11 +408,14 @@ public class InputManagerService extends IInputManager.Stub @VisibleForTesting InputManagerService(Injector injector) { + // The static association map is accessed by both java and native code, so it must be + // initialized before initializing the native service. + mStaticAssociations = loadStaticInputPortAssociations(); + mContext = injector.getContext(); mHandler = new InputManagerHandler(injector.getLooper()); mNative = injector.getNativeService(this); - mStaticAssociations = loadStaticInputPortAssociations(); mUseDevInputEventForAudioJack = mContext.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack); Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack=" @@ -406,7 +426,7 @@ public class InputManagerService extends IInputManager.Stub mDoubleTouchGestureEnableFile = TextUtils.isEmpty(doubleTouchGestureEnablePath) ? null : new File(doubleTouchGestureEnablePath); - LocalServices.addService(InputManagerInternal.class, new LocalService()); + injector.registerLocalService(new LocalService()); } public void setWindowManagerCallbacks(WindowManagerCallbacks callbacks) { @@ -556,6 +576,8 @@ public class InputManagerService extends IInputManager.Stub vArray[i] = viewports.get(i); } mNative.setDisplayViewports(vArray); + // Always attempt to update the pointer display when viewports change. + updatePointerDisplayId(); if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { final AdditionalDisplayInputProperties properties = @@ -1690,6 +1712,13 @@ public class InputManagerService extends IInputManager.Stub mPointerIconDisplayContext = null; } + synchronized (mAdditionalDisplayInputPropertiesLock) { + setPointerIconVisible(AdditionalDisplayInputProperties.DEFAULT_POINTER_ICON_VISIBLE, + displayId); + setPointerAcceleration(AdditionalDisplayInputProperties.DEFAULT_POINTER_ACCELERATION, + displayId); + } + mNative.displayRemoved(displayId); } @@ -1961,10 +1990,43 @@ public class InputManagerService extends IInputManager.Stub return result; } - private void setVirtualMousePointerDisplayId(int displayId) { + /** + * 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; + } + } + + private void handlePointerDisplayIdChanged(PointerDisplayIdChangedArgs args) { + synchronized (mAdditionalDisplayInputPropertiesLock) { + mAcknowledgedPointerDisplayId = args.mPointerDisplayId; + // Notify waiting threads that the display of the mouse pointer has changed. + mAdditionalDisplayInputPropertiesLock.notifyAll(); + } + mWindowManagerCallbacks.notifyPointerDisplayIdChanged( + 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; + synchronized (mAdditionalDisplayInputPropertiesLock) { mOverriddenPointerDisplayId = displayId; - if (displayId != Display.INVALID_DISPLAY) { + if (!removingOverride) { final AdditionalDisplayInputProperties properties = mAdditionalDisplayInputProperties.get(displayId); if (properties != null) { @@ -1972,9 +2034,30 @@ public class InputManagerService extends IInputManager.Stub updatePointerIconVisibleLocked(properties.pointerIconVisible); } } + if (!updatePointerDisplayId() && mAcknowledgedPointerDisplayId == displayId) { + // The requested pointer display is already set. + return true; + } + if (removingOverride && 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. + return true; + } + try { + // The pointer display changed, so wait until the change has propagated. + mAdditionalDisplayInputPropertiesLock.wait(5_000 /*mills*/); + } catch (InterruptedException ignored) { + } + // This request succeeds in two cases: + // - This request was to remove the override, in which case the new pointer display + // could be anything that WM has set. + // - We are setting a new override, in which case the request only succeeds if the + // 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; } - // TODO(b/215597605): trigger MousePositionTracker update - mNative.notifyPointerDisplayIdChanged(); } private int getVirtualMousePointerDisplayId() { @@ -3156,18 +3239,6 @@ public class InputManagerService extends IInputManager.Stub // Native callback. @SuppressWarnings("unused") - private int getPointerDisplayId() { - synchronized (mAdditionalDisplayInputPropertiesLock) { - // Prefer the override to all other displays. - if (mOverriddenPointerDisplayId != Display.INVALID_DISPLAY) { - return mOverriddenPointerDisplayId; - } - } - return mWindowManagerCallbacks.getPointerDisplayId(); - } - - // Native callback. - @SuppressWarnings("unused") private String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) { if (!mSystemReady) { return null; @@ -3206,6 +3277,26 @@ public class InputManagerService extends IInputManager.Stub return null; } + private static class PointerDisplayIdChangedArgs { + final int mPointerDisplayId; + final float mXPosition; + final float mYPosition; + PointerDisplayIdChangedArgs(int pointerDisplayId, float xPosition, float yPosition) { + mPointerDisplayId = pointerDisplayId; + mXPosition = xPosition; + mYPosition = yPosition; + } + } + + // Native callback. + @SuppressWarnings("unused") + @VisibleForTesting + void onPointerDisplayIdChanged(int pointerDisplayId, float xPosition, float yPosition) { + mHandler.obtainMessage(MSG_POINTER_DISPLAY_ID_CHANGED, + new PointerDisplayIdChangedArgs(pointerDisplayId, xPosition, + yPosition)).sendToTarget(); + } + /** * Callback interface implemented by the Window Manager. */ @@ -3329,6 +3420,14 @@ public class InputManagerService extends IInputManager.Stub */ @Nullable SurfaceControl createSurfaceForGestureMonitor(String name, int displayId); + + /** + * Notify WindowManagerService when the display of the mouse pointer changes. + * @param displayId The display on which the mouse pointer is shown. + * @param x The x coordinate of the mouse pointer. + * @param y The y coordinate of the mouse pointer. + */ + void notifyPointerDisplayIdChanged(int displayId, float x, float y); } /** @@ -3381,6 +3480,9 @@ public class InputManagerService extends IInputManager.Stub boolean inTabletMode = (boolean) args.arg1; deliverTabletModeChanged(whenNanos, inTabletMode); break; + case MSG_POINTER_DISPLAY_ID_CHANGED: + handlePointerDisplayIdChanged((PointerDisplayIdChangedArgs) msg.obj); + break; } } } @@ -3631,8 +3733,9 @@ public class InputManagerService extends IInputManager.Stub } @Override - public void setVirtualMousePointerDisplayId(int pointerDisplayId) { - InputManagerService.this.setVirtualMousePointerDisplayId(pointerDisplayId); + public boolean setVirtualMousePointerDisplayId(int pointerDisplayId) { + return InputManagerService.this + .setVirtualMousePointerDisplayIdBlocking(pointerDisplayId); } @Override diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java index 2169155343cd..81882d277a99 100644 --- a/services/core/java/com/android/server/input/NativeInputManagerService.java +++ b/services/core/java/com/android/server/input/NativeInputManagerService.java @@ -176,6 +176,9 @@ public interface NativeInputManagerService { void cancelCurrentTouch(); + /** Set the displayId on which the mouse cursor should be shown. */ + void setPointerDisplayId(int displayId); + /** The native implementation of InputManagerService methods. */ class NativeImpl implements NativeInputManagerService { /** Pointer to native input manager service object, used by native code. */ @@ -388,5 +391,8 @@ public interface NativeInputManagerService { @Override public native void cancelCurrentTouch(); + + @Override + public native void setPointerDisplayId(int displayId); } } diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java index db17c1056e39..8180e66166d9 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java @@ -64,7 +64,9 @@ final class HandwritingEventReceiverSurface { | InputConfig.INTERCEPTS_STYLUS | InputConfig.TRUSTED_OVERLAY; - // The touchable region of this input surface is not initially configured. + // Configure the surface to receive stylus events across the entire display. + mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */); + final SurfaceControl.Transaction t = new SurfaceControl.Transaction(); t.setInputWindowInfo(mInputSurface, mWindowHandle); t.setLayer(mInputSurface, HANDWRITING_SURFACE_LAYER); @@ -81,10 +83,6 @@ final class HandwritingEventReceiverSurface { mWindowHandle.ownerUid = imeUid; mWindowHandle.inputConfig &= ~InputConfig.SPY; - // Update the touchable region so that the IME can intercept stylus events - // across the entire display. - mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */); - new SurfaceControl.Transaction() .setInputWindowInfo(mInputSurface, mWindowHandle) .apply(); diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java index a70677222506..f89b6aedf1f5 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java @@ -91,7 +91,7 @@ final class HandwritingModeController { * InputEventReceiver that batches events according to the current thread's Choreographer. */ @UiThread - void initializeHandwritingSpy(int displayId, IBinder focusedWindowToken) { + void initializeHandwritingSpy(int displayId) { // When resetting, reuse resources if we are reinitializing on the same display. reset(displayId == mCurrentDisplayId); mCurrentDisplayId = displayId; @@ -115,12 +115,6 @@ final class HandwritingModeController { mHandwritingSurface = new HandwritingEventReceiverSurface( name, displayId, surface, channel); - // Configure the handwriting window to receive events over the focused window's bounds. - mWindowManagerInternal.replaceInputSurfaceTouchableRegionWithWindowCrop( - mHandwritingSurface.getSurface(), - mHandwritingSurface.getInputWindowHandle(), - focusedWindowToken); - // Use a dup of the input channel so that event processing can be paused by disposing the // event receiver without causing a fd hangup. mHandwritingEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver( @@ -149,7 +143,8 @@ final class HandwritingModeController { */ @UiThread @Nullable - HandwritingSession startHandwritingSession(int requestId, int imePid, int imeUid) { + HandwritingSession startHandwritingSession( + int requestId, int imePid, int imeUid, IBinder focusedWindowToken) { if (mHandwritingSurface == null) { Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized."); return null; @@ -158,12 +153,20 @@ final class HandwritingModeController { Slog.e(TAG, "Cannot start handwriting session: Invalid request id: " + requestId); return null; } - if (!mRecordingGesture) { + if (!mRecordingGesture || mHandwritingBuffer.isEmpty()) { Slog.e(TAG, "Cannot start handwriting session: No stylus gesture is being recorded."); return null; } Objects.requireNonNull(mHandwritingEventReceiver, "Handwriting session was already transferred to IME."); + final MotionEvent downEvent = mHandwritingBuffer.get(0); + assert (downEvent.getActionMasked() == MotionEvent.ACTION_DOWN); + if (!mWindowManagerInternal.isPointInsideWindow( + focusedWindowToken, mCurrentDisplayId, downEvent.getRawX(), downEvent.getRawY())) { + Slog.e(TAG, "Cannot start handwriting session: " + + "Stylus gesture did not start inside the focused window."); + return null; + } if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId); mInputManagerInternal.pilferPointers(mHandwritingSurface.getInputChannel().getToken()); @@ -226,13 +229,17 @@ final class HandwritingModeController { } if (!(ev instanceof MotionEvent)) { - Slog.e("Stylus", "Received non-motion event in stylus monitor."); + Slog.wtf(TAG, "Received non-motion event in stylus monitor."); return false; } final MotionEvent event = (MotionEvent) ev; if (!isStylusEvent(event)) { return false; } + if (event.getDisplayId() != mCurrentDisplayId) { + Slog.wtf(TAG, "Received stylus event associated with the incorrect display."); + return false; + } onStylusEvent(event); return true; diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index e62c5c13f04d..170331032086 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -30,6 +30,7 @@ import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethodSubtype; +import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodNavButtonFlags; @@ -148,10 +149,11 @@ final class IInputMethodInvoker { @AnyThread void startInput(IBinder startInputToken, IInputContext inputContext, EditorInfo attribute, - boolean restarting, @InputMethodNavButtonFlags int navButtonFlags) { + boolean restarting, @InputMethodNavButtonFlags int navButtonFlags, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { try { mTarget.startInput(startInputToken, inputContext, attribute, restarting, - navButtonFlags); + navButtonFlags, imeDispatcher); } catch (RemoteException e) { logRemoteException(e); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 6af00b3fbeea..ea2b1571c0ae 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -150,6 +150,7 @@ import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTr import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; +import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; @@ -616,6 +617,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub IInputContext mCurInputContext; /** + * The {@link ImeOnBackInvokedDispatcher} last provided by the current client to + * receive {@link android.window.OnBackInvokedCallback}s forwarded from IME. + */ + ImeOnBackInvokedDispatcher mCurImeDispatcher; + + /** * The {@link IRemoteAccessibilityInputConnection} last provided by the current client. */ @Nullable IRemoteAccessibilityInputConnection mCurRemoteAccessibilityInputConnection; @@ -2623,7 +2630,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final SessionState session = mCurClient.curSession; setEnabledSessionLocked(session); session.method.startInput(startInputToken, mCurInputContext, mCurAttribute, restarting, - navButtonFlags); + navButtonFlags, mCurImeDispatcher); if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null, @@ -2733,7 +2740,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, - int unverifiedTargetSdkVersion) { + int unverifiedTargetSdkVersion, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { // If no method is currently selected, do nothing. final String selectedMethodId = getSelectedMethodIdLocked(); if (selectedMethodId == null) { @@ -2777,6 +2785,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurClient = cs; mCurInputContext = inputContext; mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; + mCurImeDispatcher = imeDispatcher; mCurVirtualDisplayToScreenMatrix = getVirtualDisplayToScreenMatrixLocked(cs.selfReportedDisplayId, mDisplayIdToShowIme); @@ -3780,10 +3789,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext, IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, - int unverifiedTargetSdkVersion) { + int unverifiedTargetSdkVersion, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { return startInputOrWindowGainedFocusInternal(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, attribute, inputContext, - remoteAccessibilityInputConnection, unverifiedTargetSdkVersion); + remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, + imeDispatcher); } @NonNull @@ -3792,7 +3803,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo attribute, @Nullable IInputContext inputContext, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, - int unverifiedTargetSdkVersion) { + int unverifiedTargetSdkVersion, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (windowToken == null) { Slog.e(TAG, "windowToken cannot be null."); return InputBindResult.NULL; @@ -3829,7 +3841,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client, windowToken, startInputFlags, softInputMode, windowFlags, attribute, inputContext, remoteAccessibilityInputConnection, - unverifiedTargetSdkVersion, userId); + unverifiedTargetSdkVersion, userId, imeDispatcher); } finally { Binder.restoreCallingIdentity(ident); } @@ -3857,7 +3869,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute, IInputContext inputContext, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, - int unverifiedTargetSdkVersion, @UserIdInt int userId) { + int unverifiedTargetSdkVersion, @UserIdInt int userId, + @NonNull ImeOnBackInvokedDispatcher imeDispatcher) { if (DEBUG) { Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason=" + InputMethodDebug.startInputReasonToString(startInputReason) @@ -3868,7 +3881,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub + InputMethodDebug.startInputFlagsToString(startInputFlags) + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode) + " windowFlags=#" + Integer.toHexString(windowFlags) - + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion); + + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion + + " imeDispatcher=" + imeDispatcher); } final ClientState cs = mClients.get(client.asBinder()); @@ -3952,7 +3966,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (attribute != null) { return startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, attribute, startInputFlags, - startInputReason, unverifiedTargetSdkVersion); + startInputReason, unverifiedTargetSdkVersion, imeDispatcher); } return new InputBindResult( InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, @@ -3993,7 +4007,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (isTextEditor && attribute != null && shouldRestoreImeVisibility(windowToken, softInputMode)) { res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, - attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion); + attribute, startInputFlags, startInputReason, unverifiedTargetSdkVersion, + imeDispatcher); showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); return res; @@ -4033,7 +4048,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, attribute, startInputFlags, - startInputReason, unverifiedTargetSdkVersion); + startInputReason, unverifiedTargetSdkVersion, + imeDispatcher); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -4065,7 +4081,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, attribute, startInputFlags, - startInputReason, unverifiedTargetSdkVersion); + startInputReason, unverifiedTargetSdkVersion, + imeDispatcher); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -4085,7 +4102,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (attribute != null) { res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, attribute, startInputFlags, - startInputReason, unverifiedTargetSdkVersion); + startInputReason, unverifiedTargetSdkVersion, + imeDispatcher); didStart = true; } showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, @@ -4115,7 +4133,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, attribute, startInputFlags, - startInputReason, unverifiedTargetSdkVersion); + startInputReason, unverifiedTargetSdkVersion, + imeDispatcher); } else { res = InputBindResult.NULL_EDITOR_INFO; } @@ -5099,9 +5118,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_RESET_HANDWRITING: { synchronized (ImfLock.class) { if (mBindingController.supportsStylusHandwriting() - && getCurMethodLocked() != null && mCurFocusedWindow != null) { - mHwController.initializeHandwritingSpy( - mCurTokenDisplayId, mCurFocusedWindow); + && getCurMethodLocked() != null) { + mHwController.initializeHandwritingSpy(mCurTokenDisplayId); } else { mHwController.reset(); } @@ -5111,14 +5129,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_START_HANDWRITING: synchronized (ImfLock.class) { IInputMethodInvoker curMethod = getCurMethodLocked(); - if (curMethod == null) { + if (curMethod == null || mCurFocusedWindow == null) { return true; } final HandwritingModeController.HandwritingSession session = mHwController.startHandwritingSession( msg.arg1 /*requestId*/, msg.arg2 /*pid*/, - mBindingController.getCurMethodUid()); + mBindingController.getCurMethodUid(), + mCurFocusedWindow); if (session == null) { Slog.e(TAG, "Failed to start handwriting session for requestId: " + msg.arg1); diff --git a/services/core/java/com/android/server/media/MediaSessionRecord.java b/services/core/java/com/android/server/media/MediaSessionRecord.java index 92703ec86d06..604e8f3949f4 100644 --- a/services/core/java/com/android/server/media/MediaSessionRecord.java +++ b/services/core/java/com/android/server/media/MediaSessionRecord.java @@ -60,8 +60,8 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; import java.util.NoSuchElementException; +import java.util.concurrent.CopyOnWriteArrayList; /** * This is the system implementation of a Session. Apps will interact with the @@ -1159,6 +1159,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void sendCommand(String packageName, int pid, int uid, String command, Bundle args, ResultReceiver cb) { try { + final String reason = TAG + ":" + command; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onCommand(packageName, pid, uid, command, args, cb); } catch (RemoteException e) { Log.e(TAG, "Remote failure in sendCommand.", e); @@ -1168,6 +1171,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void sendCustomAction(String packageName, int pid, int uid, String action, Bundle args) { try { + final String reason = TAG + ":custom-" + action; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onCustomAction(packageName, pid, uid, action, args); } catch (RemoteException e) { Log.e(TAG, "Remote failure in sendCustomAction.", e); @@ -1176,6 +1182,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void prepare(String packageName, int pid, int uid) { try { + final String reason = TAG + ":prepare"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPrepare(packageName, pid, uid); } catch (RemoteException e) { Log.e(TAG, "Remote failure in prepare.", e); @@ -1185,6 +1194,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void prepareFromMediaId(String packageName, int pid, int uid, String mediaId, Bundle extras) { try { + final String reason = TAG + ":prepareFromMediaId"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPrepareFromMediaId(packageName, pid, uid, mediaId, extras); } catch (RemoteException e) { Log.e(TAG, "Remote failure in prepareFromMediaId.", e); @@ -1194,6 +1206,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void prepareFromSearch(String packageName, int pid, int uid, String query, Bundle extras) { try { + final String reason = TAG + ":prepareFromSearch"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPrepareFromSearch(packageName, pid, uid, query, extras); } catch (RemoteException e) { Log.e(TAG, "Remote failure in prepareFromSearch.", e); @@ -1202,6 +1217,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void prepareFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) { try { + final String reason = TAG + ":prepareFromUri"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPrepareFromUri(packageName, pid, uid, uri, extras); } catch (RemoteException e) { Log.e(TAG, "Remote failure in prepareFromUri.", e); @@ -1210,6 +1228,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void play(String packageName, int pid, int uid) { try { + final String reason = TAG + ":play"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPlay(packageName, pid, uid); } catch (RemoteException e) { Log.e(TAG, "Remote failure in play.", e); @@ -1219,6 +1240,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void playFromMediaId(String packageName, int pid, int uid, String mediaId, Bundle extras) { try { + final String reason = TAG + ":playFromMediaId"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPlayFromMediaId(packageName, pid, uid, mediaId, extras); } catch (RemoteException e) { Log.e(TAG, "Remote failure in playFromMediaId.", e); @@ -1228,6 +1252,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void playFromSearch(String packageName, int pid, int uid, String query, Bundle extras) { try { + final String reason = TAG + ":playFromSearch"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPlayFromSearch(packageName, pid, uid, query, extras); } catch (RemoteException e) { Log.e(TAG, "Remote failure in playFromSearch.", e); @@ -1236,6 +1263,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void playFromUri(String packageName, int pid, int uid, Uri uri, Bundle extras) { try { + final String reason = TAG + ":playFromUri"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPlayFromUri(packageName, pid, uid, uri, extras); } catch (RemoteException e) { Log.e(TAG, "Remote failure in playFromUri.", e); @@ -1244,6 +1274,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void skipToTrack(String packageName, int pid, int uid, long id) { try { + final String reason = TAG + ":skipToTrack"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onSkipToTrack(packageName, pid, uid, id); } catch (RemoteException e) { Log.e(TAG, "Remote failure in skipToTrack", e); @@ -1252,6 +1285,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void pause(String packageName, int pid, int uid) { try { + final String reason = TAG + ":pause"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPause(packageName, pid, uid); } catch (RemoteException e) { Log.e(TAG, "Remote failure in pause.", e); @@ -1260,6 +1296,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void stop(String packageName, int pid, int uid) { try { + final String reason = TAG + ":stop"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onStop(packageName, pid, uid); } catch (RemoteException e) { Log.e(TAG, "Remote failure in stop.", e); @@ -1268,6 +1307,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void next(String packageName, int pid, int uid) { try { + final String reason = TAG + ":next"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onNext(packageName, pid, uid); } catch (RemoteException e) { Log.e(TAG, "Remote failure in next.", e); @@ -1276,6 +1318,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void previous(String packageName, int pid, int uid) { try { + final String reason = TAG + ":previous"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onPrevious(packageName, pid, uid); } catch (RemoteException e) { Log.e(TAG, "Remote failure in previous.", e); @@ -1284,6 +1329,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void fastForward(String packageName, int pid, int uid) { try { + final String reason = TAG + ":fastForward"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onFastForward(packageName, pid, uid); } catch (RemoteException e) { Log.e(TAG, "Remote failure in fastForward.", e); @@ -1292,6 +1340,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void rewind(String packageName, int pid, int uid) { try { + final String reason = TAG + ":rewind"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onRewind(packageName, pid, uid); } catch (RemoteException e) { Log.e(TAG, "Remote failure in rewind.", e); @@ -1300,6 +1351,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void seekTo(String packageName, int pid, int uid, long pos) { try { + final String reason = TAG + ":seekTo"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onSeekTo(packageName, pid, uid, pos); } catch (RemoteException e) { Log.e(TAG, "Remote failure in seekTo.", e); @@ -1308,6 +1362,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void rate(String packageName, int pid, int uid, Rating rating) { try { + final String reason = TAG + ":rate"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onRate(packageName, pid, uid, rating); } catch (RemoteException e) { Log.e(TAG, "Remote failure in rate.", e); @@ -1316,6 +1373,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void setPlaybackSpeed(String packageName, int pid, int uid, float speed) { try { + final String reason = TAG + ":setPlaybackSpeed"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onSetPlaybackSpeed(packageName, pid, uid, speed); } catch (RemoteException e) { Log.e(TAG, "Remote failure in setPlaybackSpeed.", e); @@ -1325,6 +1385,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void adjustVolume(String packageName, int pid, int uid, boolean asSystemService, int direction) { try { + final String reason = TAG + ":adjustVolume"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); if (asSystemService) { mCb.onAdjustVolume(mContext.getPackageName(), Process.myPid(), Process.SYSTEM_UID, direction); @@ -1338,6 +1401,9 @@ public class MediaSessionRecord implements IBinder.DeathRecipient, MediaSessionR public void setVolumeTo(String packageName, int pid, int uid, int value) { try { + final String reason = TAG + ":setVolumeTo"; + mService.tempAllowlistTargetPkgIfPossible(getUid(), getPackageName(), + pid, uid, packageName, reason); mCb.onSetVolumeTo(packageName, pid, uid, value); } catch (RemoteException e) { Log.e(TAG, "Remote failure in setVolumeTo.", e); diff --git a/services/core/java/com/android/server/notification/NotificationDelegate.java b/services/core/java/com/android/server/notification/NotificationDelegate.java index 02f9ceb2d11d..89902f7f8321 100644 --- a/services/core/java/com/android/server/notification/NotificationDelegate.java +++ b/services/core/java/com/android/server/notification/NotificationDelegate.java @@ -51,14 +51,16 @@ public interface NotificationDelegate { void onNotificationSettingsViewed(String key); /** * Called when the state of {@link Notification#FLAG_BUBBLE} is changed. + * + * @param key the notification key + * @param isBubble whether the notification should have {@link Notification#FLAG_BUBBLE} applied + * @param flags the flags to apply to the notification's {@link Notification.BubbleMetadata} */ void onNotificationBubbleChanged(String key, boolean isBubble, int flags); /** - * Called when the state of {@link Notification.BubbleMetadata#FLAG_SUPPRESS_NOTIFICATION} - * or {@link Notification.BubbleMetadata#FLAG_SUPPRESS_BUBBLE} changes. + * Called when the flags on {@link Notification.BubbleMetadata} are changed. */ - void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, - boolean isBubbleSuppressed); + void onBubbleMetadataFlagChanged(String key, int flags); /** * Grant permission to read the specified URI to the package associated with the diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index c548e7edc3cf..8a627367c1dc 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -42,4 +42,7 @@ public interface NotificationManagerInternal { /** Does the specified package/uid have permission to post notifications? */ boolean areNotificationsEnabledForPackage(String pkg, int uid); + + /** Send a notification to the user prompting them to review their notification permissions. */ + void sendReviewPermissionsNotification(); } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 6078bfc95488..83c576e9259d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -18,6 +18,7 @@ package com.android.server.notification; import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; import static android.app.AppOpsManager.MODE_ALLOWED; +import static android.app.Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; import static android.app.Notification.FLAG_AUTOGROUP_SUMMARY; import static android.app.Notification.FLAG_BUBBLE; import static android.app.Notification.FLAG_FOREGROUND_SERVICE; @@ -274,6 +275,7 @@ import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.notification.SystemNotificationChannels; import com.android.internal.os.BackgroundThread; import com.android.internal.os.SomeArgs; @@ -442,6 +444,18 @@ public class NotificationManagerService extends SystemService { private static final int NOTIFICATION_INSTANCE_ID_MAX = (1 << 13); + // States for the review permissions notification + static final int REVIEW_NOTIF_STATE_UNKNOWN = -1; + static final int REVIEW_NOTIF_STATE_SHOULD_SHOW = 0; + static final int REVIEW_NOTIF_STATE_USER_INTERACTED = 1; + static final int REVIEW_NOTIF_STATE_DISMISSED = 2; + static final int REVIEW_NOTIF_STATE_RESHOWN = 3; + + // Action strings for review permissions notification + static final String REVIEW_NOTIF_ACTION_REMIND = "REVIEW_NOTIF_ACTION_REMIND"; + static final String REVIEW_NOTIF_ACTION_DISMISS = "REVIEW_NOTIF_ACTION_DISMISS"; + static final String REVIEW_NOTIF_ACTION_CANCELED = "REVIEW_NOTIF_ACTION_CANCELED"; + /** * Apps that post custom toasts in the background will have those blocked. Apps can * still post toasts created with @@ -652,6 +666,9 @@ public class NotificationManagerService extends SystemService { private InstanceIdSequence mNotificationInstanceIdSequence; private Set<String> mMsgPkgsAllowedAsConvos = new HashSet(); + // Broadcast intent receiver for notification permissions review-related intents + private ReviewNotificationPermissionsReceiver mReviewNotificationPermissionsReceiver; + static class Archive { final SparseArray<Boolean> mEnabled; final int mBufferSize; @@ -1413,8 +1430,7 @@ public class NotificationManagerService extends SystemService { } @Override - public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, - boolean isBubbleSuppressed) { + public void onBubbleMetadataFlagChanged(String key, int flags) { synchronized (mNotificationLock) { NotificationRecord r = mNotificationsByKey.get(key); if (r != null) { @@ -1424,17 +1440,12 @@ public class NotificationManagerService extends SystemService { return; } - boolean flagChanged = false; - if (data.isNotificationSuppressed() != isNotifSuppressed) { - flagChanged = true; - data.setSuppressNotification(isNotifSuppressed); - } - if (data.isBubbleSuppressed() != isBubbleSuppressed) { - flagChanged = true; - data.setSuppressBubble(isBubbleSuppressed); - } - if (flagChanged) { + if (flags != data.getFlags()) { + data.setFlags(flags); + // Shouldn't alert again just because of a flag change. r.getNotification().flags |= FLAG_ONLY_ALERT_ONCE; + // Force isAppForeground true here, because for sysui's purposes we + // want to be able to adjust the flag behaviour. mHandler.post( new EnqueueNotificationRunnable(r.getUser().getIdentifier(), r, true /* isAppForeground */, SystemClock.elapsedRealtime())); @@ -2416,6 +2427,11 @@ public class NotificationManagerService extends SystemService { IntentFilter localeChangedFilter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED); getContext().registerReceiver(mLocaleChangeReceiver, localeChangedFilter); + + mReviewNotificationPermissionsReceiver = new ReviewNotificationPermissionsReceiver(); + getContext().registerReceiver(mReviewNotificationPermissionsReceiver, + ReviewNotificationPermissionsReceiver.getFilter(), + Context.RECEIVER_NOT_EXPORTED); } /** @@ -2709,6 +2725,7 @@ public class NotificationManagerService extends SystemService { mHistoryManager.onBootPhaseAppsCanStart(); registerDeviceConfigChange(); migrateDefaultNAS(); + maybeShowInitialReviewPermissionsNotification(); } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); } @@ -6336,6 +6353,21 @@ public class NotificationManagerService extends SystemService { public boolean areNotificationsEnabledForPackage(String pkg, int uid) { return areNotificationsEnabledForPackageInt(pkg, uid); } + + @Override + public void sendReviewPermissionsNotification() { + // This method is meant to be called from the JobService upon running the job for this + // notification having been rescheduled; so without checking any other state, it will + // send the notification. + checkCallerIsSystem(); + NotificationManager nm = getContext().getSystemService(NotificationManager.class); + nm.notify(TAG, + SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS, + createReviewPermissionsNotification()); + Settings.Global.putInt(getContext().getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN); + } }; int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) { @@ -7145,10 +7177,12 @@ public class NotificationManagerService extends SystemService { && r.getNotification().isBubbleNotification()) || (mReason == REASON_CLICK && r.canBubble() && r.isFlagBubbleRemoved())) { - boolean isBubbleSuppressed = r.getNotification().getBubbleMetadata() != null - && r.getNotification().getBubbleMetadata().isBubbleSuppressed(); - mNotificationDelegate.onBubbleNotificationSuppressionChanged( - r.getKey(), true /* notifSuppressed */, isBubbleSuppressed); + int flags = 0; + if (r.getNotification().getBubbleMetadata() != null) { + flags = r.getNotification().getBubbleMetadata().getFlags(); + } + flags |= FLAG_SUPPRESS_NOTIFICATION; + mNotificationDelegate.onBubbleMetadataFlagChanged(r.getKey(), flags); return; } if ((r.getNotification().flags & mMustHaveFlags) != mMustHaveFlags) { @@ -11608,6 +11642,76 @@ public class NotificationManagerService extends SystemService { out.endTag(null, LOCKSCREEN_ALLOW_SECURE_NOTIFICATIONS_TAG); } + // Creates a notification that informs the user about changes due to the migration to + // use permissions for notifications. + protected Notification createReviewPermissionsNotification() { + int title = R.string.review_notification_settings_title; + int content = R.string.review_notification_settings_text; + + // Tapping on the notification leads to the settings screen for managing app notifications, + // using the intent reserved for system services to indicate it comes from this notification + Intent tapIntent = new Intent(Settings.ACTION_ALL_APPS_NOTIFICATION_SETTINGS_FOR_REVIEW); + Intent remindIntent = new Intent(REVIEW_NOTIF_ACTION_REMIND); + Intent dismissIntent = new Intent(REVIEW_NOTIF_ACTION_DISMISS); + Intent swipeIntent = new Intent(REVIEW_NOTIF_ACTION_CANCELED); + + // Both "remind me" and "dismiss" actions will be actions received by the BroadcastReceiver + final Notification.Action remindMe = new Notification.Action.Builder(null, + getContext().getResources().getString( + R.string.review_notification_settings_remind_me_action), + PendingIntent.getBroadcast( + getContext(), 0, remindIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) + .build(); + final Notification.Action dismiss = new Notification.Action.Builder(null, + getContext().getResources().getString( + R.string.review_notification_settings_dismiss), + PendingIntent.getBroadcast( + getContext(), 0, dismissIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) + .build(); + + return new Notification.Builder(getContext(), SystemNotificationChannels.SYSTEM_CHANGES) + .setSmallIcon(R.drawable.stat_sys_adb) + .setContentTitle(getContext().getResources().getString(title)) + .setContentText(getContext().getResources().getString(content)) + .setContentIntent(PendingIntent.getActivity(getContext(), 0, tapIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) + .setStyle(new Notification.BigTextStyle()) + .setFlag(Notification.FLAG_NO_CLEAR, true) + .setAutoCancel(true) + .addAction(remindMe) + .addAction(dismiss) + .setDeleteIntent(PendingIntent.getBroadcast(getContext(), 0, swipeIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) + .build(); + } + + protected void maybeShowInitialReviewPermissionsNotification() { + int currentState = Settings.Global.getInt(getContext().getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + REVIEW_NOTIF_STATE_UNKNOWN); + + // now check the last known state of the notification -- this determination of whether the + // user is in the correct target audience occurs elsewhere, and will have written the + // REVIEW_NOTIF_STATE_SHOULD_SHOW to indicate it should be shown in the future. + // + // alternatively, if the user has rescheduled the notification (so it has been shown + // again) but not yet interacted with the new notification, then show it again on boot, + // as this state indicates that the user had the notification open before rebooting. + // + // sending the notification here does not record a new state for the notification; + // that will be written by parts of the system further down the line if at any point + // the user interacts with the notification. + if (currentState == REVIEW_NOTIF_STATE_SHOULD_SHOW + || currentState == REVIEW_NOTIF_STATE_RESHOWN) { + NotificationManager nm = getContext().getSystemService(NotificationManager.class); + nm.notify(TAG, + SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS, + createReviewPermissionsNotification()); + } + } + /** * Shows a warning on logcat. Shows the toast only once per package. This is to avoid being too * aggressive and annoying the user. diff --git a/services/core/java/com/android/server/notification/PermissionHelper.java b/services/core/java/com/android/server/notification/PermissionHelper.java index a09aa7cea0a4..b4230c11bcab 100644 --- a/services/core/java/com/android/server/notification/PermissionHelper.java +++ b/services/core/java/com/android/server/notification/PermissionHelper.java @@ -55,10 +55,8 @@ public final class PermissionHelper { private final PermissionManagerServiceInternal mPmi; private final IPackageManager mPackageManager; private final IPermissionManager mPermManager; - // TODO (b/194833441): Remove this boolean (but keep the isMigrationEnabled() method) - // when the migration is enabled + // TODO (b/194833441): Remove when the migration is enabled private final boolean mMigrationEnabled; - private final boolean mIsTv; private final boolean mForceUserSetOnUpgrade; public PermissionHelper(PermissionManagerServiceInternal pmi, IPackageManager packageManager, @@ -69,17 +67,10 @@ public final class PermissionHelper { mPermManager = permManager; mMigrationEnabled = migrationEnabled; mForceUserSetOnUpgrade = forceUserSetOnUpgrade; - boolean isTv; - try { - isTv = mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0); - } catch (RemoteException e) { - isTv = false; - } - mIsTv = isTv; } public boolean isMigrationEnabled() { - return mMigrationEnabled && !mIsTv; + return mMigrationEnabled; } /** diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 0525b1e33267..ef3c770f125b 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -96,6 +96,10 @@ public class PreferencesHelper implements RankingConfig { private final int XML_VERSION; /** What version to check to do the upgrade for bubbles. */ private static final int XML_VERSION_BUBBLES_UPGRADE = 1; + /** The first xml version with notification permissions enabled. */ + private static final int XML_VERSION_NOTIF_PERMISSION = 3; + /** The first xml version that notifies users to review their notification permissions */ + private static final int XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION = 4; @VisibleForTesting static final int UNKNOWN_UID = UserHandle.USER_NULL; private static final String NON_BLOCKABLE_CHANNEL_DELIM = ":"; @@ -206,7 +210,7 @@ public class PreferencesHelper implements RankingConfig { mStatsEventBuilderFactory = statsEventBuilderFactory; if (mPermissionHelper.isMigrationEnabled()) { - XML_VERSION = 3; + XML_VERSION = 4; } else { XML_VERSION = 2; } @@ -226,8 +230,16 @@ public class PreferencesHelper implements RankingConfig { final int xmlVersion = parser.getAttributeInt(null, ATT_VERSION, -1); boolean upgradeForBubbles = xmlVersion == XML_VERSION_BUBBLES_UPGRADE; - boolean migrateToPermission = - (xmlVersion < XML_VERSION) && mPermissionHelper.isMigrationEnabled(); + boolean migrateToPermission = (xmlVersion < XML_VERSION_NOTIF_PERMISSION) + && mPermissionHelper.isMigrationEnabled(); + if (xmlVersion < XML_VERSION_REVIEW_PERMISSIONS_NOTIFICATION) { + // make a note that we should show the notification at some point. + // it shouldn't be possible for the user to already have seen it, as the XML version + // would be newer then. + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); + } ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>(); synchronized (mPackagePreferences) { while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java new file mode 100644 index 000000000000..fde45f71a844 --- /dev/null +++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsJobService.java @@ -0,0 +1,79 @@ +/* + * 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.server.notification; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.app.job.JobService; +import android.content.ComponentName; +import android.content.Context; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalServices; + +/** + * JobService implementation for scheduling the notification informing users about + * notification permissions updates and taking them to review their existing permissions. + * @hide + */ +public class ReviewNotificationPermissionsJobService extends JobService { + public static final String TAG = "ReviewNotificationPermissionsJobService"; + + @VisibleForTesting + protected static final int JOB_ID = 225373531; + + /** + * Schedule a new job that will show a notification the specified amount of time in the future. + */ + public static void scheduleJob(Context context, long rescheduleTimeMillis) { + JobScheduler jobScheduler = context.getSystemService(JobScheduler.class); + // if the job already exists for some reason, cancel & reschedule + if (jobScheduler.getPendingJob(JOB_ID) != null) { + jobScheduler.cancel(JOB_ID); + } + ComponentName component = new ComponentName( + context, ReviewNotificationPermissionsJobService.class); + JobInfo newJob = new JobInfo.Builder(JOB_ID, component) + .setPersisted(true) // make sure it'll still get rescheduled after reboot + .setMinimumLatency(rescheduleTimeMillis) // run after specified amount of time + .build(); + jobScheduler.schedule(newJob); + } + + @Override + public boolean onStartJob(JobParameters params) { + // While jobs typically should be run on different threads, this + // job only posts a notification, which is not a long-running operation + // as notification posting is asynchronous. + NotificationManagerInternal nmi = + LocalServices.getService(NotificationManagerInternal.class); + nmi.sendReviewPermissionsNotification(); + + // once the notification is posted, the job is done, so no need to + // keep it alive afterwards + return false; + } + + @Override + public boolean onStopJob(JobParameters params) { + // If we're interrupted for some reason, try again (though this may not + // ever happen due to onStartJob not leaving a job running after being + // called) + return true; + } +} diff --git a/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java new file mode 100644 index 000000000000..b99aeac44025 --- /dev/null +++ b/services/core/java/com/android/server/notification/ReviewNotificationPermissionsReceiver.java @@ -0,0 +1,122 @@ +/* + * 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.server.notification; + +import android.app.NotificationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.provider.Settings; +import android.util.Log; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.messages.nano.SystemMessageProto; + +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; + +/** + * Broadcast receiver for intents that come from the "review notification permissions" notification, + * shown to users who upgrade to T from an earlier OS to inform them of notification setup changes + * and invite them to review their notification permissions. + */ +public class ReviewNotificationPermissionsReceiver extends BroadcastReceiver { + public static final String TAG = "ReviewNotifPermissions"; + static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + // 7 days in millis, as the amount of time to wait before re-sending the notification + private static final long JOB_RESCHEDULE_TIME = 1000 /* millis */ * 60 /* seconds */ + * 60 /* minutes */ * 24 /* hours */ * 7 /* days */; + + static IntentFilter getFilter() { + IntentFilter filter = new IntentFilter(); + filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND); + filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS); + filter.addAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED); + return filter; + } + + // Cancels the "review notification permissions" notification. + @VisibleForTesting + protected void cancelNotification(Context context) { + NotificationManager nm = context.getSystemService(NotificationManager.class); + if (nm != null) { + nm.cancel(NotificationManagerService.TAG, + SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS); + } else { + Slog.w(TAG, "could not cancel notification: NotificationManager not found"); + } + } + + @VisibleForTesting + protected void rescheduleNotification(Context context) { + ReviewNotificationPermissionsJobService.scheduleJob(context, JOB_RESCHEDULE_TIME); + // log if needed + if (DEBUG) { + Slog.d(TAG, "Scheduled review permissions notification for on or after: " + + LocalDateTime.now(ZoneId.systemDefault()) + .plus(JOB_RESCHEDULE_TIME, ChronoUnit.MILLIS)); + } + } + + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND)) { + // Reschedule the notification for 7 days in the future + rescheduleNotification(context); + + // note that the user has interacted; no longer needed to show the initial + // notification + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED); + cancelNotification(context); + } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS)) { + // user dismissed; write to settings so we don't show ever again + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED); + cancelNotification(context); + } else if (action.equals(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED)) { + // we may get here from the user swiping away the notification, + // or from the notification being canceled in any other way. + // only in the case that the user hasn't interacted with it in + // any other way yet, reschedule + int notifState = Settings.Global.getInt(context.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + /* default */ NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN); + if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW) { + // user hasn't interacted in the past, so reschedule once and then note that the + // user *has* interacted now so we don't re-reschedule if they swipe again + rescheduleNotification(context); + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED); + } else if (notifState == NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN) { + // swiping away on a rescheduled notification; mark as interacted and + // don't reschedule again. + Settings.Global.putInt(context.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED); + } + } + } +} diff --git a/services/core/java/com/android/server/pm/ApkChecksums.java b/services/core/java/com/android/server/pm/ApkChecksums.java index 2824585bf5aa..39119948fa3e 100644 --- a/services/core/java/com/android/server/pm/ApkChecksums.java +++ b/services/core/java/com/android/server/pm/ApkChecksums.java @@ -34,13 +34,14 @@ import android.content.Context; import android.content.pm.ApkChecksum; import android.content.pm.Checksum; import android.content.pm.IOnChecksumsReadyListener; -import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.Signature; import android.content.pm.SigningDetails.SignatureSchemeVersion; import android.content.pm.parsing.ApkLiteParseUtils; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; +import android.os.Environment; +import android.os.FileUtils; import android.os.Handler; import android.os.RemoteException; import android.os.SystemClock; @@ -63,7 +64,6 @@ import android.util.apk.VerityBuilder; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.security.VerityUtils; -import com.android.server.LocalServices; import com.android.server.pm.parsing.pkg.AndroidPackage; import java.io.ByteArrayOutputStream; @@ -637,9 +637,18 @@ public class ApkChecksums { return null; } + private static boolean containsFile(File dir, String filePath) { + if (dir == null) { + return false; + } + return FileUtils.contains(dir.getAbsolutePath(), filePath); + } + private static ApkChecksum extractHashFromFS(String split, String filePath) { // verity first - { + // Skip /product folder. + // TODO(b/231354111): remove this hack once we are allowed to change SELinux rules. + if (!containsFile(Environment.getProductDirectory(), filePath)) { byte[] hash = VerityUtils.getFsverityRootHash(filePath); if (hash != null) { return new ApkChecksum(split, TYPE_WHOLE_MERKLE_ROOT_4K_SHA256, hash); diff --git a/services/core/java/com/android/server/pm/AppIdSettingMap.java b/services/core/java/com/android/server/pm/AppIdSettingMap.java index b41a0b8878e0..20c67f87d3b9 100644 --- a/services/core/java/com/android/server/pm/AppIdSettingMap.java +++ b/services/core/java/com/android/server/pm/AppIdSettingMap.java @@ -19,6 +19,7 @@ package com.android.server.pm; import android.os.Process; import android.util.Log; +import com.android.server.utils.SnapshotCache; import com.android.server.utils.WatchedArrayList; import com.android.server.utils.WatchedSparseArray; import com.android.server.utils.Watcher; @@ -34,10 +35,28 @@ final class AppIdSettingMap { * index to the corresponding SettingBase object is (appId - FIRST_APPLICATION_ID). If an app ID * doesn't exist (i.e., app is not installed), we fill the corresponding entry with null. */ - private WatchedArrayList<SettingBase> mNonSystemSettings = new WatchedArrayList<>(); - private WatchedSparseArray<SettingBase> mSystemSettings = new WatchedSparseArray<>(); + private final WatchedArrayList<SettingBase> mNonSystemSettings; + private final SnapshotCache<WatchedArrayList<SettingBase>> mNonSystemSettingsSnapshot; + private final WatchedSparseArray<SettingBase> mSystemSettings; + private final SnapshotCache<WatchedSparseArray<SettingBase>> mSystemSettingsSnapshot; private int mFirstAvailableAppId = Process.FIRST_APPLICATION_UID; + AppIdSettingMap() { + mNonSystemSettings = new WatchedArrayList<>(); + mNonSystemSettingsSnapshot = new SnapshotCache.Auto<>( + mNonSystemSettings, mNonSystemSettings, "AppIdSettingMap.mNonSystemSettings"); + mSystemSettings = new WatchedSparseArray<>(); + mSystemSettingsSnapshot = new SnapshotCache.Auto<>( + mSystemSettings, mSystemSettings, "AppIdSettingMap.mSystemSettings"); + } + + AppIdSettingMap(AppIdSettingMap orig) { + mNonSystemSettings = orig.mNonSystemSettingsSnapshot.snapshot(); + mNonSystemSettingsSnapshot = new SnapshotCache.Sealed<>(); + mSystemSettings = orig.mSystemSettingsSnapshot.snapshot(); + mSystemSettingsSnapshot = new SnapshotCache.Sealed<>(); + } + /** Returns true if the requested AppID was valid and not already registered. */ public boolean registerExistingAppId(int appId, SettingBase setting, Object name) { if (appId >= Process.FIRST_APPLICATION_UID) { @@ -134,10 +153,7 @@ final class AppIdSettingMap { } public AppIdSettingMap snapshot() { - AppIdSettingMap l = new AppIdSettingMap(); - mNonSystemSettings.snapshot(l.mNonSystemSettings, mNonSystemSettings); - mSystemSettings.snapshot(l.mSystemSettings, mSystemSettings); - return l; + return new AppIdSettingMap(this); } public void registerObserver(Watcher observer) { diff --git a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java index 89f8be27096a..daac7c04098a 100644 --- a/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java +++ b/services/core/java/com/android/server/pm/CrossProfileAppsServiceImpl.java @@ -236,13 +236,6 @@ public class CrossProfileAppsServiceImpl extends ICrossProfileApps.Stub { verifyActivityCanHandleIntent(launchIntent, callingUid, userId); - // Always show the cross profile animation - if (options == null) { - options = ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle(); - } else { - options.putAll(ActivityOptions.makeOpenCrossProfileAppsAnimation().toBundle()); - } - mInjector.getActivityTaskManagerInternal() .startActivityAsUser( caller, diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 320b06f6dc3e..32f0f109821d 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -119,7 +119,7 @@ public class Installer extends SystemService { IInstalld.FLAG_CLEAR_APP_DATA_KEEP_ART_PROFILES; private final boolean mIsolated; - + private volatile boolean mDeferSetFirstBoot; private volatile IInstalld mInstalld; private volatile Object mWarnIfHeld; @@ -171,6 +171,7 @@ public class Installer extends SystemService { mInstalld = IInstalld.Stub.asInterface(binder); try { invalidateMounts(); + executeDeferredActions(); } catch (InstallerException ignored) { } } else { @@ -180,6 +181,15 @@ public class Installer extends SystemService { } /** + * Perform any deferred actions on mInstalld while the connection could not be made. + */ + private void executeDeferredActions() throws InstallerException { + if (mDeferSetFirstBoot) { + setFirstBoot(); + } + } + + /** * Do several pre-flight checks before making a remote call. * * @return if the remote call should continue. @@ -291,8 +301,15 @@ public class Installer extends SystemService { return; } try { - mInstalld.setFirstBoot(); - } catch (RemoteException e) { + // mInstalld might be null if the connection could not be established. + if (mInstalld != null) { + mInstalld.setFirstBoot(); + } else { + // if it is null while trying to set the first boot, set a flag to try and set the + // first boot when the connection is eventually established + mDeferSetFirstBoot = true; + } + } catch (Exception e) { throw InstallerException.from(e); } } diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index ba4d09f28d05..6400502f1a89 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -4579,7 +4579,7 @@ public final class Settings implements Watchable, Snappable { pw.print(" (override=true)"); } pw.println(); - if (ps.getPkg().getQueriesPackages().isEmpty()) { + if (!ps.getPkg().getQueriesPackages().isEmpty()) { pw.append(prefix).append(" queriesPackages=") .println(ps.getPkg().getQueriesPackages()); } diff --git a/services/core/java/com/android/server/pm/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index fef6ce1f67b3..fa0c6c393cdc 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -166,18 +166,19 @@ class ShortcutPackage extends ShortcutPackageItem { * An in-memory copy of shortcuts for this package that was loaded from xml, keyed on IDs. */ @GuardedBy("mLock") - final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>(); + private final ArrayMap<String, ShortcutInfo> mShortcuts = new ArrayMap<>(); /** * A temporary copy of shortcuts that are to be cleared once persisted into AppSearch, keyed on * IDs. */ @GuardedBy("mLock") - private ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0); + private final ArrayMap<String, ShortcutInfo> mTransientShortcuts = new ArrayMap<>(0); /** * All the share targets from the package */ + @GuardedBy("mLock") private final ArrayList<ShareTargetInfo> mShareTargets = new ArrayList<>(0); /** @@ -231,7 +232,9 @@ class ShortcutPackage extends ShortcutPackageItem { } public int getShortcutCount() { - return mShortcuts.size(); + synchronized (mLock) { + return mShortcuts.size(); + } } @Override @@ -272,7 +275,9 @@ class ShortcutPackage extends ShortcutPackageItem { @Nullable public ShortcutInfo findShortcutById(@Nullable final String id) { if (id == null) return null; - return mShortcuts.get(id); + synchronized (mLock) { + return mShortcuts.get(id); + } } public boolean isShortcutExistsAndInvisibleToPublisher(String id) { @@ -347,11 +352,14 @@ class ShortcutPackage extends ShortcutPackageItem { * Delete a shortcut by ID. This will *always* remove it even if it's immutable or invisible. */ private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) { - final ShortcutInfo shortcut = mShortcuts.remove(id); - if (shortcut != null) { - mShortcutUser.mService.removeIconLocked(shortcut); - shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED - | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL); + final ShortcutInfo shortcut; + synchronized (mLock) { + shortcut = mShortcuts.remove(id); + if (shortcut != null) { + removeIcon(shortcut); + shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED + | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL); + } } return shortcut; } @@ -366,7 +374,7 @@ class ShortcutPackage extends ShortcutPackageItem { forceDeleteShortcutInner(newShortcut.getId()); // Extract Icon and update the icon res ID and the bitmap path. - s.saveIconAndFixUpShortcutLocked(newShortcut); + s.saveIconAndFixUpShortcutLocked(this, newShortcut); s.fixUpShortcutResourceNamesAndValues(newShortcut); saveShortcut(newShortcut); } @@ -524,14 +532,16 @@ class ShortcutPackage extends ShortcutPackageItem { public List<ShortcutInfo> deleteAllDynamicShortcuts() { final long now = mShortcutUser.mService.injectCurrentTimeMillis(); boolean changed = false; - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - ShortcutInfo si = mShortcuts.valueAt(i); - if (si.isDynamic() && si.isVisibleToPublisher()) { - changed = true; - - si.setTimestamp(now); - si.clearFlags(ShortcutInfo.FLAG_DYNAMIC); - si.setRank(0); // It may still be pinned, so clear the rank. + synchronized (mLock) { + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + ShortcutInfo si = mShortcuts.valueAt(i); + if (si.isDynamic() && si.isVisibleToPublisher()) { + changed = true; + + si.setTimestamp(now); + si.clearFlags(ShortcutInfo.FLAG_DYNAMIC); + si.setRank(0); // It may still be pinned, so clear the rank. + } } } removeAllShortcutsAsync(); @@ -874,59 +884,63 @@ class ShortcutPackage extends ShortcutPackageItem { */ public List<ShortcutManager.ShareShortcutInfo> getMatchingShareTargets( @NonNull IntentFilter filter) { - final List<ShareTargetInfo> matchedTargets = new ArrayList<>(); - for (int i = 0; i < mShareTargets.size(); i++) { - final ShareTargetInfo target = mShareTargets.get(i); - for (ShareTargetInfo.TargetData data : target.mTargetData) { - if (filter.hasDataType(data.mMimeType)) { - // Matched at least with one data type - matchedTargets.add(target); - break; + synchronized (mLock) { + final List<ShareTargetInfo> matchedTargets = new ArrayList<>(); + for (int i = 0; i < mShareTargets.size(); i++) { + final ShareTargetInfo target = mShareTargets.get(i); + for (ShareTargetInfo.TargetData data : target.mTargetData) { + if (filter.hasDataType(data.mMimeType)) { + // Matched at least with one data type + matchedTargets.add(target); + break; + } } } - } - if (matchedTargets.isEmpty()) { - return new ArrayList<>(); - } + if (matchedTargets.isEmpty()) { + return new ArrayList<>(); + } - // Get the list of all dynamic shortcuts in this package. - final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); - // Pass callingLauncher to ensure pinned flag marked by system ui, e.g. ShareSheet, are - // included in the result - findAll(shortcuts, ShortcutInfo::isNonManifestVisible, - ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION, - mShortcutUser.mService.mContext.getPackageName(), - 0, /*getPinnedByAnyLauncher=*/ false); + // Get the list of all dynamic shortcuts in this package. + final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + // Pass callingLauncher to ensure pinned flag marked by system ui, e.g. ShareSheet, are + // included in the result + findAll(shortcuts, ShortcutInfo::isNonManifestVisible, + ShortcutInfo.CLONE_REMOVE_FOR_APP_PREDICTION, + mShortcutUser.mService.mContext.getPackageName(), + 0, /*getPinnedByAnyLauncher=*/ false); - final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>(); - for (int i = 0; i < shortcuts.size(); i++) { - final Set<String> categories = shortcuts.get(i).getCategories(); - if (categories == null || categories.isEmpty()) { - continue; - } - for (int j = 0; j < matchedTargets.size(); j++) { - // Shortcut must have all of share target categories - boolean hasAllCategories = true; - final ShareTargetInfo target = matchedTargets.get(j); - for (int q = 0; q < target.mCategories.length; q++) { - if (!categories.contains(target.mCategories[q])) { - hasAllCategories = false; + final List<ShortcutManager.ShareShortcutInfo> result = new ArrayList<>(); + for (int i = 0; i < shortcuts.size(); i++) { + final Set<String> categories = shortcuts.get(i).getCategories(); + if (categories == null || categories.isEmpty()) { + continue; + } + for (int j = 0; j < matchedTargets.size(); j++) { + // Shortcut must have all of share target categories + boolean hasAllCategories = true; + final ShareTargetInfo target = matchedTargets.get(j); + for (int q = 0; q < target.mCategories.length; q++) { + if (!categories.contains(target.mCategories[q])) { + hasAllCategories = false; + break; + } + } + if (hasAllCategories) { + result.add(new ShortcutManager.ShareShortcutInfo(shortcuts.get(i), + new ComponentName(getPackageName(), target.mTargetClass))); break; } } - if (hasAllCategories) { - result.add(new ShortcutManager.ShareShortcutInfo(shortcuts.get(i), - new ComponentName(getPackageName(), target.mTargetClass))); - break; - } } + return result; } - return result; } public boolean hasShareTargets() { - return !mShareTargets.isEmpty(); + synchronized (mLock) { + return !mShareTargets.isEmpty(); + } } /** @@ -935,44 +949,47 @@ class ShortcutPackage extends ShortcutPackageItem { * the app's Xml resource. */ int getSharingShortcutCount() { - if (mShareTargets.isEmpty()) { - return 0; - } + synchronized (mLock) { + if (mShareTargets.isEmpty()) { + return 0; + } - // Get the list of all dynamic shortcuts in this package - final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); - findAll(shortcuts, ShortcutInfo::isNonManifestVisible, - ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); + // Get the list of all dynamic shortcuts in this package + final ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + findAll(shortcuts, ShortcutInfo::isNonManifestVisible, + ShortcutInfo.CLONE_REMOVE_FOR_LAUNCHER); - int sharingShortcutCount = 0; - for (int i = 0; i < shortcuts.size(); i++) { - final Set<String> categories = shortcuts.get(i).getCategories(); - if (categories == null || categories.isEmpty()) { - continue; - } - for (int j = 0; j < mShareTargets.size(); j++) { - // A SharingShortcut must have all of share target categories - boolean hasAllCategories = true; - final ShareTargetInfo target = mShareTargets.get(j); - for (int q = 0; q < target.mCategories.length; q++) { - if (!categories.contains(target.mCategories[q])) { - hasAllCategories = false; + int sharingShortcutCount = 0; + for (int i = 0; i < shortcuts.size(); i++) { + final Set<String> categories = shortcuts.get(i).getCategories(); + if (categories == null || categories.isEmpty()) { + continue; + } + for (int j = 0; j < mShareTargets.size(); j++) { + // A SharingShortcut must have all of share target categories + boolean hasAllCategories = true; + final ShareTargetInfo target = mShareTargets.get(j); + for (int q = 0; q < target.mCategories.length; q++) { + if (!categories.contains(target.mCategories[q])) { + hasAllCategories = false; + break; + } + } + if (hasAllCategories) { + sharingShortcutCount++; break; } } - if (hasAllCategories) { - sharingShortcutCount++; - break; - } } + return sharingShortcutCount; } - return sharingShortcutCount; } /** * Return the filenames (excluding path names) of icon bitmap files from this package. */ - public ArraySet<String> getUsedBitmapFiles() { + @GuardedBy("mLock") + private ArraySet<String> getUsedBitmapFilesLocked() { final ArraySet<String> usedFiles = new ArraySet<>(1); forEachShortcut(si -> { if (si.getBitmapPath() != null) { @@ -982,6 +999,26 @@ class ShortcutPackage extends ShortcutPackageItem { return usedFiles; } + public void cleanupDanglingBitmapFiles(@NonNull File path) { + synchronized (mLock) { + mShortcutBitmapSaver.waitForAllSavesLocked(); + final ArraySet<String> usedFiles = getUsedBitmapFilesLocked(); + + for (File child : path.listFiles()) { + if (!child.isFile()) { + continue; + } + final String name = child.getName(); + if (!usedFiles.contains(name)) { + if (ShortcutService.DEBUG) { + Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath()); + } + child.delete(); + } + } + } + } + private static String getFileName(@NonNull String path) { final int sep = path.lastIndexOf(File.separatorChar); if (sep == -1) { @@ -1069,19 +1106,25 @@ class ShortcutPackage extends ShortcutPackageItem { // Now prepare to publish manifest shortcuts. List<ShortcutInfo> newManifestShortcutList = null; - try { - newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService, - getPackageName(), getPackageUserId(), mShareTargets); - } catch (IOException|XmlPullParserException e) { - Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e); + final int shareTargetSize; + synchronized (mLock) { + try { + shareTargetSize = mShareTargets.size(); + newManifestShortcutList = ShortcutParser.parseShortcuts(mShortcutUser.mService, + getPackageName(), getPackageUserId(), mShareTargets); + } catch (IOException | XmlPullParserException e) { + Slog.e(TAG, "Failed to load shortcuts from AndroidManifest.xml.", e); + } } final int manifestShortcutSize = newManifestShortcutList == null ? 0 : newManifestShortcutList.size(); if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) { Slog.d(TAG, - String.format("Package %s has %d manifest shortcut(s), and %d share target(s)", - getPackageName(), manifestShortcutSize, mShareTargets.size())); + String.format( + "Package %s has %d manifest shortcut(s), and %d share target(s)", + getPackageName(), manifestShortcutSize, shareTargetSize)); } + if (isNewApp && (manifestShortcutSize == 0)) { // If it's a new app, and it doesn't have manifest shortcuts, then nothing to do. @@ -1608,6 +1651,11 @@ class ShortcutPackage extends ShortcutPackageItem { pw.print(" ("); pw.print(Formatter.formatFileSize(mShortcutUser.mService.mContext, totalBitmapSize[0])); pw.println(")"); + + pw.println(); + synchronized (mLock) { + mShortcutBitmapSaver.dumpLocked(pw, " "); + } } public void dumpShortcuts(@NonNull PrintWriter pw, int matchFlags) { @@ -1675,37 +1723,38 @@ class ShortcutPackage extends ShortcutPackageItem { @Override public void saveToXml(@NonNull TypedXmlSerializer out, boolean forBackup) throws IOException, XmlPullParserException { - final int size = mShortcuts.size(); - final int shareTargetSize = mShareTargets.size(); + synchronized (mLock) { + final int size = mShortcuts.size(); + final int shareTargetSize = mShareTargets.size(); - if (hasNoShortcut() && shareTargetSize == 0 && mApiCallCount == 0) { - return; // nothing to write. - } + if (hasNoShortcut() && shareTargetSize == 0 && mApiCallCount == 0) { + return; // nothing to write. + } - out.startTag(null, TAG_ROOT); + out.startTag(null, TAG_ROOT); - ShortcutService.writeAttr(out, ATTR_NAME, getPackageName()); - ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); - ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); - if (!forBackup) { - synchronized (mLock) { - ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, (mIsAppSearchSchemaUpToDate) + ShortcutService.writeAttr(out, ATTR_NAME, getPackageName()); + ShortcutService.writeAttr(out, ATTR_CALL_COUNT, mApiCallCount); + ShortcutService.writeAttr(out, ATTR_LAST_RESET, mLastResetTime); + if (!forBackup) { + ShortcutService.writeAttr(out, ATTR_SCHEMA_VERSON, mIsAppSearchSchemaUpToDate ? AppSearchShortcutInfo.SCHEMA_VERSION : 0); } - } - getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup); + getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup); - for (int j = 0; j < size; j++) { - saveShortcut(out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed()); - } + for (int j = 0; j < size; j++) { + saveShortcut( + out, mShortcuts.valueAt(j), forBackup, getPackageInfo().isBackupAllowed()); + } - if (!forBackup) { - for (int j = 0; j < shareTargetSize; j++) { - mShareTargets.get(j).saveToXml(out); + if (!forBackup) { + for (int j = 0; j < shareTargetSize; j++) { + mShareTargets.get(j).saveToXml(out); + } } - } - out.endTag(null, TAG_ROOT); + out.endTag(null, TAG_ROOT); + } } private void saveShortcut(TypedXmlSerializer out, ShortcutInfo si, boolean forBackup, @@ -1729,7 +1778,7 @@ class ShortcutPackage extends ShortcutPackageItem { // Note: at this point no shortcuts should have bitmaps pending save, but if they do, // just remove the bitmap. if (si.isIconPendingSave()) { - s.removeIconLocked(si); + removeIcon(si); } out.startTag(null, TAG_SHORTCUT); ShortcutService.writeAttr(out, ATTR_ID, si.getId()); @@ -1891,38 +1940,38 @@ class ShortcutPackage extends ShortcutPackageItem { synchronized (ret.mLock) { ret.mIsAppSearchSchemaUpToDate = ShortcutService.parseIntAttribute( parser, ATTR_SCHEMA_VERSON, 0) == AppSearchShortcutInfo.SCHEMA_VERSION; - } - ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); - ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); + ret.mApiCallCount = ShortcutService.parseIntAttribute(parser, ATTR_CALL_COUNT); + ret.mLastResetTime = ShortcutService.parseLongAttribute(parser, ATTR_LAST_RESET); - final int outerDepth = parser.getDepth(); - int type; - while ((type = parser.next()) != XmlPullParser.END_DOCUMENT - && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { - if (type != XmlPullParser.START_TAG) { - continue; - } - final int depth = parser.getDepth(); - final String tag = parser.getName(); - if (depth == outerDepth + 1) { - switch (tag) { - case ShortcutPackageInfo.TAG_ROOT: - ret.getPackageInfo().loadFromXml(parser, fromBackup); - - continue; - case TAG_SHORTCUT: - final ShortcutInfo si = parseShortcut(parser, packageName, - shortcutUser.getUserId(), fromBackup); - // Don't use addShortcut(), we don't need to save the icon. - ret.mShortcuts.put(si.getId(), si); - continue; - case TAG_SHARE_TARGET: - ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser)); - continue; + final int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type != XmlPullParser.START_TAG) { + continue; + } + final int depth = parser.getDepth(); + final String tag = parser.getName(); + if (depth == outerDepth + 1) { + switch (tag) { + case ShortcutPackageInfo.TAG_ROOT: + ret.getPackageInfo().loadFromXml(parser, fromBackup); + + continue; + case TAG_SHORTCUT: + final ShortcutInfo si = parseShortcut(parser, packageName, + shortcutUser.getUserId(), fromBackup); + // Don't use addShortcut(), we don't need to save the icon. + ret.mShortcuts.put(si.getId(), si); + continue; + case TAG_SHARE_TARGET: + ret.mShareTargets.add(ShareTargetInfo.loadFromXml(parser)); + continue; + } } + ShortcutService.warnForInvalidTag(depth, tag); } - ShortcutService.warnForInvalidTag(depth, tag); } return ret; } @@ -2126,7 +2175,9 @@ class ShortcutPackage extends ShortcutPackageItem { @VisibleForTesting List<ShareTargetInfo> getAllShareTargetsForTest() { - return new ArrayList<>(mShareTargets); + synchronized (mLock) { + return new ArrayList<>(mShareTargets); + } } @Override @@ -2265,15 +2316,19 @@ class ShortcutPackage extends ShortcutPackageItem { private void saveShortcut(@NonNull final Collection<ShortcutInfo> shortcuts) { Objects.requireNonNull(shortcuts); - for (ShortcutInfo si : shortcuts) { - mShortcuts.put(si.getId(), si); + synchronized (mLock) { + for (ShortcutInfo si : shortcuts) { + mShortcuts.put(si.getId(), si); + } } } @Nullable List<ShortcutInfo> findAll(@NonNull final Collection<String> ids) { - return ids.stream().map(mShortcuts::get) - .filter(Objects::nonNull).collect(Collectors.toList()); + synchronized (mLock) { + return ids.stream().map(mShortcuts::get) + .filter(Objects::nonNull).collect(Collectors.toList()); + } } private void forEachShortcut(@NonNull final Consumer<ShortcutInfo> cb) { @@ -2292,10 +2347,12 @@ class ShortcutPackage extends ShortcutPackageItem { private void forEachShortcutStopWhen( @NonNull final Function<ShortcutInfo, Boolean> cb) { - for (int i = mShortcuts.size() - 1; i >= 0; i--) { - final ShortcutInfo si = mShortcuts.valueAt(i); - if (cb.apply(si)) { - return; + synchronized (mLock) { + for (int i = mShortcuts.size() - 1; i >= 0; i--) { + final ShortcutInfo si = mShortcuts.valueAt(i); + if (cb.apply(si)) { + return; + } } } } @@ -2435,6 +2492,7 @@ class ShortcutPackage extends ShortcutPackageItem { }))); } + @GuardedBy("mLock") @Override void scheduleSaveToAppSearchLocked() { final Map<String, ShortcutInfo> copy = new ArrayMap<>(mShortcuts); diff --git a/services/core/java/com/android/server/pm/ShortcutPackageItem.java b/services/core/java/com/android/server/pm/ShortcutPackageItem.java index 6e0436f208e3..7800183692ca 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackageItem.java +++ b/services/core/java/com/android/server/pm/ShortcutPackageItem.java @@ -16,8 +16,10 @@ package com.android.server.pm; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.pm.PackageInfo; import android.content.pm.ShortcutInfo; +import android.graphics.Bitmap; import android.util.AtomicFile; import android.util.Slog; import android.util.TypedXmlSerializer; @@ -50,6 +52,9 @@ abstract class ShortcutPackageItem { protected ShortcutUser mShortcutUser; + @GuardedBy("mLock") + protected ShortcutBitmapSaver mShortcutBitmapSaver; + protected final Object mLock = new Object(); protected ShortcutPackageItem(@NonNull ShortcutUser shortcutUser, @@ -59,6 +64,7 @@ abstract class ShortcutPackageItem { mPackageUserId = packageUserId; mPackageName = Preconditions.checkStringNotEmpty(packageName); mPackageInfo = Objects.requireNonNull(packageInfo); + mShortcutBitmapSaver = new ShortcutBitmapSaver(shortcutUser.mService); } /** @@ -206,7 +212,7 @@ abstract class ShortcutPackageItem { void saveShortcutPackageItem() { // Wait for bitmap saves to conclude before proceeding to saving shortcuts. - mShortcutUser.mService.waitForBitmapSaves(); + waitForBitmapSaves(); // Save each ShortcutPackageItem in a separate Xml file. final File path = getShortcutPackageItemFile(); if (ShortcutService.DEBUG || ShortcutService.DEBUG_REBOOT) { @@ -221,6 +227,35 @@ abstract class ShortcutPackageItem { } } + public boolean waitForBitmapSaves() { + synchronized (mLock) { + return mShortcutBitmapSaver.waitForAllSavesLocked(); + } + } + + public void saveBitmap(ShortcutInfo shortcut, + int maxDimension, Bitmap.CompressFormat format, int quality) { + synchronized (mLock) { + mShortcutBitmapSaver.saveBitmapLocked(shortcut, maxDimension, format, quality); + } + } + + /** + * Wait for all pending saves to finish, and then return the given shortcut's bitmap path. + */ + @Nullable + public String getBitmapPathMayWait(ShortcutInfo shortcut) { + synchronized (mLock) { + return mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcut); + } + } + + public void removeIcon(ShortcutInfo shortcut) { + synchronized (mLock) { + mShortcutBitmapSaver.removeIcon(shortcut); + } + } + void removeShortcutPackageItem() { synchronized (mLock) { getShortcutPackageItemFile().delete(); diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java index 780f976d2a40..f2bcf5e461a7 100644 --- a/services/core/java/com/android/server/pm/ShortcutService.java +++ b/services/core/java/com/android/server/pm/ShortcutService.java @@ -363,7 +363,6 @@ public class ShortcutService extends IShortcutService.Stub { private final RoleManager mRoleManager; private final ShortcutRequestPinProcessor mShortcutRequestPinProcessor; - private final ShortcutBitmapSaver mShortcutBitmapSaver; private final ShortcutDumpFiles mShortcutDumpFiles; @GuardedBy("mLock") @@ -490,7 +489,6 @@ public class ShortcutService extends IShortcutService.Stub { mRoleManager = Objects.requireNonNull(mContext.getSystemService(RoleManager.class)); mShortcutRequestPinProcessor = new ShortcutRequestPinProcessor(this, mLock); - mShortcutBitmapSaver = new ShortcutBitmapSaver(this); mShortcutDumpFiles = new ShortcutDumpFiles(this); mIsAppSearchEnabled = DeviceConfig.getBoolean(NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SHORTCUT_APPSEARCH_INTEGRATION, true) @@ -1063,8 +1061,6 @@ public class ShortcutService extends IShortcutService.Stub { Slog.d(TAG, "Saving to " + path); } - mShortcutBitmapSaver.waitForAllSavesLocked(); - path.getParentFile().mkdirs(); final AtomicFile file = new AtomicFile(path); FileOutputStream os = null; @@ -1388,15 +1384,12 @@ public class ShortcutService extends IShortcutService.Stub { // === Caller validation === - void removeIconLocked(ShortcutInfo shortcut) { - mShortcutBitmapSaver.removeIcon(shortcut); - } - public void cleanupBitmapsForPackage(@UserIdInt int userId, String packageName) { final File packagePath = new File(getUserBitmapFilePath(userId), packageName); if (!packagePath.isDirectory()) { return; } + // ShortcutPackage is already removed at this point, we can safely remove the folder. if (!(FileUtils.deleteContents(packagePath) && packagePath.delete())) { Slog.w(TAG, "Unable to remove directory " + packagePath); } @@ -1437,38 +1430,12 @@ public class ShortcutService extends IShortcutService.Stub { } cleanupBitmapsForPackage(userId, packageName); } else { - cleanupDanglingBitmapFilesLocked(userId, user, packageName, child); + user.getPackageShortcuts(packageName).cleanupDanglingBitmapFiles(child); } } logDurationStat(Stats.CLEANUP_DANGLING_BITMAPS, start); } - /** - * Remove dangling bitmap files for a package. - * - * Note this method must be called with the lock held after calling - * {@link ShortcutBitmapSaver#waitForAllSavesLocked()} to make sure there's no pending bitmap - * saves are going on. - */ - private void cleanupDanglingBitmapFilesLocked(@UserIdInt int userId, @NonNull ShortcutUser user, - @NonNull String packageName, @NonNull File path) { - final ArraySet<String> usedFiles = - user.getPackageShortcuts(packageName).getUsedBitmapFiles(); - - for (File child : path.listFiles()) { - if (!child.isFile()) { - continue; - } - final String name = child.getName(); - if (!usedFiles.contains(name)) { - if (DEBUG) { - Slog.d(TAG, "Removing dangling bitmap file: " + child.getAbsolutePath()); - } - child.delete(); - } - } - } - @VisibleForTesting static class FileOutputStreamWithPath extends FileOutputStream { private final File mFile; @@ -1513,7 +1480,7 @@ public class ShortcutService extends IShortcutService.Stub { } } - void saveIconAndFixUpShortcutLocked(ShortcutInfo shortcut) { + void saveIconAndFixUpShortcutLocked(ShortcutPackage p, ShortcutInfo shortcut) { if (shortcut.hasIconFile() || shortcut.hasIconResource() || shortcut.hasIconUri()) { return; } @@ -1521,7 +1488,7 @@ public class ShortcutService extends IShortcutService.Stub { final long token = injectClearCallingIdentity(); try { // Clear icon info on the shortcut. - removeIconLocked(shortcut); + p.removeIcon(shortcut); final Icon icon = shortcut.getIcon(); if (icon == null) { @@ -1560,8 +1527,7 @@ public class ShortcutService extends IShortcutService.Stub { // just in case. throw ShortcutInfo.getInvalidIconException(); } - mShortcutBitmapSaver.saveBitmapLocked(shortcut, - maxIconDimension, mIconPersistFormat, mIconPersistQuality); + p.saveBitmap(shortcut, maxIconDimension, mIconPersistFormat, mIconPersistQuality); } finally { // Once saved, we won't use the original icon information, so null it out. shortcut.clearIcon(); @@ -2110,7 +2076,7 @@ public class ShortcutService extends IShortcutService.Stub { final boolean replacingIcon = (source.getIcon() != null); if (replacingIcon) { - removeIconLocked(target); + ps.removeIcon(target); } // Note copyNonNullFieldsFrom() does the "updatable with?" check too. @@ -2118,7 +2084,7 @@ public class ShortcutService extends IShortcutService.Stub { target.setTimestamp(injectCurrentTimeMillis()); if (replacingIcon) { - saveIconAndFixUpShortcutLocked(target); + saveIconAndFixUpShortcutLocked(ps, target); } // When we're updating any resource related fields, re-extract the res @@ -3463,7 +3429,7 @@ public class ShortcutService extends IShortcutService.Stub { if (shortcutInfo == null) { return null; } - return getShortcutIconParcelFileDescriptor(shortcutInfo); + return getShortcutIconParcelFileDescriptor(p, shortcutInfo); } } @@ -3476,6 +3442,7 @@ public class ShortcutService extends IShortcutService.Stub { Objects.requireNonNull(shortcutId, "shortcutId"); // Checks shortcuts in memory first + final ShortcutPackage p; synchronized (mLock) { throwIfUserLockedL(userId); throwIfUserLockedL(launcherUserId); @@ -3483,8 +3450,7 @@ public class ShortcutService extends IShortcutService.Stub { getLauncherShortcutsLocked(callingPackage, userId, launcherUserId) .attemptToRestoreIfNeededAndSave(); - final ShortcutPackage p = getUserShortcutsLocked(userId) - .getPackageShortcutsIfExists(packageName); + p = getUserShortcutsLocked(userId).getPackageShortcutsIfExists(packageName); if (p == null) { cb.complete(null); return; @@ -3492,24 +3458,23 @@ public class ShortcutService extends IShortcutService.Stub { final ShortcutInfo shortcutInfo = p.findShortcutById(shortcutId); if (shortcutInfo != null) { - cb.complete(getShortcutIconParcelFileDescriptor(shortcutInfo)); + cb.complete(getShortcutIconParcelFileDescriptor(p, shortcutInfo)); return; } } // Otherwise check persisted shortcuts - getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> { - cb.complete(getShortcutIconParcelFileDescriptor(si)); - }); + getShortcutInfoAsync(launcherUserId, packageName, shortcutId, userId, si -> + cb.complete(getShortcutIconParcelFileDescriptor(p, si))); } @Nullable private ParcelFileDescriptor getShortcutIconParcelFileDescriptor( - @Nullable final ShortcutInfo shortcutInfo) { - if (shortcutInfo == null || !shortcutInfo.hasIconFile()) { + @Nullable final ShortcutPackage p, @Nullable final ShortcutInfo shortcutInfo) { + if (p == null || shortcutInfo == null || !shortcutInfo.hasIconFile()) { return null; } - final String path = mShortcutBitmapSaver.getBitmapPathMayWaitLocked(shortcutInfo); + final String path = p.getBitmapPathMayWait(shortcutInfo); if (path == null) { Slog.w(TAG, "null bitmap detected in getShortcutIconFd()"); return null; @@ -4772,9 +4737,6 @@ public class ShortcutService extends IShortcutService.Stub { } pw.println(); - mShortcutBitmapSaver.dumpLocked(pw, " "); - - pw.println(); } for (int i = 0; i < mUsers.size(); i++) { @@ -5347,9 +5309,11 @@ public class ShortcutService extends IShortcutService.Stub { } } - void waitForBitmapSaves() { + @VisibleForTesting + void waitForBitmapSavesForTest() { synchronized (mLock) { - mShortcutBitmapSaver.waitForAllSavesLocked(); + forEachLoadedUserLocked(u -> + u.forAllPackageItems(ShortcutPackageItem::waitForBitmapSaves)); } } diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java index 75e18b547c55..b9fd2fdb9630 100644 --- a/services/core/java/com/android/server/pm/ShortcutUser.java +++ b/services/core/java/com/android/server/pm/ShortcutUser.java @@ -401,6 +401,7 @@ class ShortcutUser { private void saveShortcutPackageItem(TypedXmlSerializer out, ShortcutPackageItem spi, boolean forBackup) throws IOException, XmlPullParserException { + spi.waitForBitmapSaves(); if (forBackup) { if (spi.getPackageUserId() != spi.getOwnerUserId()) { return; // Don't save cross-user information. diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 0dabff8370ba..bcdf4291ed41 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1430,6 +1430,8 @@ public class UserManagerService extends IUserManager.Stub { /** * Returns a UserInfo object with the name filled in, for Owner and Guest, or the original * if the name is already set. + * + * Note: Currently, the resulting name can be null if a user was truly created with a null name. */ private UserInfo userWithName(UserInfo orig) { if (orig != null && orig.name == null) { @@ -1638,7 +1640,7 @@ public class UserManagerService extends IUserManager.Stub { } @Override - public String getUserName() { + public @NonNull String getUserName() { final int callingUid = Binder.getCallingUid(); if (!hasQueryOrCreateUsersPermission() && !hasPermissionGranted( @@ -1649,7 +1651,10 @@ public class UserManagerService extends IUserManager.Stub { final int userId = UserHandle.getUserId(callingUid); synchronized (mUsersLock) { UserInfo userInfo = userWithName(getUserInfoLU(userId)); - return userInfo == null ? "" : userInfo.name; + if (userInfo != null && userInfo.name != null) { + return userInfo.name; + } + return ""; } } @@ -4165,7 +4170,7 @@ public class UserManagerService extends IUserManager.Stub { * @return the converted user, or {@code null} if no pre-created user could be converted. */ private @Nullable UserInfo convertPreCreatedUserIfPossible(String userType, - @UserInfoFlag int flags, String name, @Nullable Object token) { + @UserInfoFlag int flags, @Nullable String name, @Nullable Object token) { final UserData preCreatedUserData; synchronized (mUsersLock) { preCreatedUserData = getPreCreatedUserLU(userType); 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 2277d8a8ecee..7be83b03243a 100644 --- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java +++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java @@ -2816,14 +2816,12 @@ public class PermissionManagerServiceImpl implements PermissionManagerServiceInt } // Remove review flag as it is not necessary anymore - // TODO(b/227186603) re-enable check for notification permission once - // droidfood state has been cleared - //if (!NOTIFICATION_PERMISSIONS.contains(perm)) { + if (!NOTIFICATION_PERMISSIONS.contains(perm)) { if ((flags & FLAG_PERMISSION_REVIEW_REQUIRED) != 0) { flags &= ~FLAG_PERMISSION_REVIEW_REQUIRED; wasChanged = true; } - //} + } if ((flags & FLAG_PERMISSION_REVOKED_COMPAT) != 0 && !isPermissionSplitFromNonRuntime(permName, diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index b14214189833..d8e7fbe8b296 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -27,7 +27,6 @@ import static android.content.pm.PackageManager.FEATURE_LEANBACK; import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE; import static android.content.pm.PackageManager.FEATURE_WATCH; import static android.content.pm.PackageManager.PERMISSION_GRANTED; -import static android.os.BatteryManager.BATTERY_PLUGGED_WIRELESS; import static android.os.Build.VERSION_CODES.M; import static android.os.Build.VERSION_CODES.O; import static android.provider.Settings.Secure.VOLUME_HUSH_OFF; @@ -130,7 +129,6 @@ import android.media.AudioManagerInternal; import android.media.AudioSystem; import android.media.IAudioService; import android.media.session.MediaSessionLegacyHelper; -import android.os.BatteryManagerInternal; import android.os.Binder; import android.os.Bundle; import android.os.DeviceIdleManager; @@ -396,7 +394,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { PowerManagerInternal mPowerManagerInternal; IStatusBarService mStatusBarService; StatusBarManagerInternal mStatusBarManagerInternal; - BatteryManagerInternal mBatteryManagerInternal; AudioManagerInternal mAudioManagerInternal; DisplayManager mDisplayManager; DisplayManagerInternal mDisplayManagerInternal; @@ -791,8 +788,7 @@ public class PhoneWindowManager implements WindowManagerPolicy { @Override public void onWakeUp() { synchronized (mLock) { - if (shouldEnableWakeGestureLp() - && getBatteryManagerInternal().getPlugType() != BATTERY_PLUGGED_WIRELESS) { + if (shouldEnableWakeGestureLp()) { performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY, false, "Wake Up"); wakeUp(SystemClock.uptimeMillis(), mAllowTheaterModeWakeFromWakeGesture, @@ -849,15 +845,6 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } - BatteryManagerInternal getBatteryManagerInternal() { - synchronized (mServiceAcquireLock) { - if (mBatteryManagerInternal == null) { - mBatteryManagerInternal = - LocalServices.getService(BatteryManagerInternal.class); - } - return mBatteryManagerInternal; - } - } // returns true if the key was handled and should not be passed to the user private boolean backKeyPress() { diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java index 08344170b02d..3c779f387673 100644 --- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java +++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java @@ -727,7 +727,8 @@ public final class SensorPrivacyService extends SystemService { return false; } - if (mKeyguardManager != null && mKeyguardManager.isDeviceLocked(userId)) { + if (requiresAuthentication() && mKeyguardManager != null + && mKeyguardManager.isDeviceLocked(userId)) { Log.i(TAG, "Can't change mic/cam toggle while device is locked"); return false; } @@ -993,6 +994,13 @@ public final class SensorPrivacyService extends SystemService { } @Override + public boolean requiresAuthentication() { + enforceObserveSensorPrivacyPermission(); + return mContext.getResources() + .getBoolean(R.bool.config_sensorPrivacyRequiresAuthentication); + } + + @Override public void showSensorUseDialog(int sensor) { if (Binder.getCallingUid() != Process.SYSTEM_UID) { throw new SecurityException("Can only be called by the system uid"); diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index b6855726c122..d48f26332017 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -1650,13 +1650,11 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D } @Override - public void onBubbleNotificationSuppressionChanged(String key, boolean isNotifSuppressed, - boolean isBubbleSuppressed) { + public void onBubbleMetadataFlagChanged(String key, int flags) { enforceStatusBarService(); final long identity = Binder.clearCallingIdentity(); try { - mNotificationDelegate.onBubbleNotificationSuppressionChanged(key, isNotifSuppressed, - isBubbleSuppressed); + mNotificationDelegate.onBubbleMetadataFlagChanged(key, flags); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/trust/TrustAgentWrapper.java b/services/core/java/com/android/server/trust/TrustAgentWrapper.java index d3748140a5a5..4b8c7c176fda 100644 --- a/services/core/java/com/android/server/trust/TrustAgentWrapper.java +++ b/services/core/java/com/android/server/trust/TrustAgentWrapper.java @@ -122,16 +122,9 @@ public class TrustAgentWrapper { if (!TrustManagerService.ENABLE_ACTIVE_UNLOCK_FLAG) { return; } - if (!mWaitingForTrustableDowngrade) { - return; - } // are these the broadcasts we want to listen to - if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction()) - || Intent.ACTION_USER_PRESENT.equals(intent.getAction())) { - mTrusted = false; - mTrustable = true; - mWaitingForTrustableDowngrade = false; - mTrustManagerService.updateTrust(mUserId, 0); + if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { + downgradeToTrustable(); } } }; @@ -480,8 +473,7 @@ public class TrustAgentWrapper { final String pathUri = mAlarmIntent.toUri(Intent.URI_INTENT_SCHEME); alarmFilter.addDataPath(pathUri, PatternMatcher.PATTERN_LITERAL); - IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_USER_PRESENT); - trustableFilter.addAction(Intent.ACTION_SCREEN_OFF); + IntentFilter trustableFilter = new IntentFilter(Intent.ACTION_SCREEN_OFF); // Schedules a restart for when connecting times out. If the connection succeeds, // the restart is canceled in mCallback's onConnected. @@ -668,6 +660,19 @@ public class TrustAgentWrapper { mTrustable = false; } + /** + * Downgrades the trustagent to trustable as a result of a keyguard or screen related event, and + * then updates the trust state of the phone to reflect the change. + */ + public void downgradeToTrustable() { + if (mWaitingForTrustableDowngrade) { + mWaitingForTrustableDowngrade = false; + mTrusted = false; + mTrustable = true; + mTrustManagerService.updateTrust(mUserId, 0); + } + } + public boolean isManagingTrust() { return mManagingTrust && !mTrustDisabledByDpm; } diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index 8f4ddea1c30c..80ce70de2138 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -1184,6 +1184,22 @@ public class TrustManagerService extends SystemService { return false; } + /** + * We downgrade to trustable whenever keyguard changes its showing value. + * - becomes showing: something has caused the device to show keyguard which happens due to + * user intent to lock the device either through direct action or a timeout + * - becomes not showing: keyguard was dismissed and we no longer need to keep the device + * unlocked + * */ + private void dispatchTrustableDowngrade() { + for (int i = 0; i < mActiveAgents.size(); i++) { + AgentInfo info = mActiveAgents.valueAt(i); + if (info.userId == mCurrentUser) { + info.agent.downgradeToTrustable(); + } + } + } + private List<String> getTrustGrantedMessages(int userId) { if (!mStrongAuthTracker.isTrustAllowedForUser(userId)) { return new ArrayList<>(); @@ -1752,6 +1768,7 @@ public class TrustManagerService extends SystemService { refreshDeviceLockedForUser(UserHandle.USER_ALL); break; case MSG_KEYGUARD_SHOWING_CHANGED: + dispatchTrustableDowngrade(); refreshDeviceLockedForUser(mCurrentUser); break; case MSG_START_USER: diff --git a/services/core/java/com/android/server/utils/WatchedArrayList.java b/services/core/java/com/android/server/utils/WatchedArrayList.java index 6059f9675e34..75e39ebb32d8 100644 --- a/services/core/java/com/android/server/utils/WatchedArrayList.java +++ b/services/core/java/com/android/server/utils/WatchedArrayList.java @@ -416,7 +416,7 @@ public class WatchedArrayList<E> extends WatchableImpl dst.mStorage.ensureCapacity(end); for (int i = 0; i < end; i++) { final E val = Snapshots.maybeSnapshot(src.get(i)); - dst.add(i, val); + dst.add(val); } dst.seal(); } diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java index 63619e543a6d..f1dbad61a76d 100644 --- a/services/core/java/com/android/server/wm/AccessibilityController.java +++ b/services/core/java/com/android/server/wm/AccessibilityController.java @@ -446,6 +446,43 @@ final class AccessibilityController { // Not relevant for the window observer. } + public Pair<Matrix, MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec( + IBinder token) { + synchronized (mService.mGlobalLock) { + final Matrix transformationMatrix = new Matrix(); + final MagnificationSpec magnificationSpec = new MagnificationSpec(); + + final WindowState windowState = mService.mWindowMap.get(token); + if (windowState != null) { + windowState.getTransformationMatrix(new float[9], transformationMatrix); + + if (hasCallbacks()) { + final MagnificationSpec otherMagnificationSpec = + getMagnificationSpecForWindow(windowState); + if (otherMagnificationSpec != null && !otherMagnificationSpec.isNop()) { + magnificationSpec.setTo(otherMagnificationSpec); + } + } + } + + return new Pair<>(transformationMatrix, magnificationSpec); + } + } + + MagnificationSpec getMagnificationSpecForWindow(WindowState windowState) { + if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) { + mAccessibilityTracing.logTrace(TAG + ".getMagnificationSpecForWindow", + FLAGS_MAGNIFICATION_CALLBACK, + "windowState={" + windowState + "}"); + } + final int displayId = windowState.getDisplayId(); + final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId); + if (displayMagnifier != null) { + return displayMagnifier.getMagnificationSpecForWindow(windowState); + } + return null; + } + boolean hasCallbacks() { if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK | FLAGS_WINDOWS_FOR_ACCESSIBILITY_CALLBACK)) { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index b97ee7ef76b3..b2f27ebaf513 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -2732,8 +2732,8 @@ class ActivityStarter { intentActivity.getTaskFragment().clearLastPausedActivity(); Task intentTask = intentActivity.getTask(); - // Only update the target-root-task when it is not indicated. if (mTargetRootTask == null) { + // Update launch target task when it is not indicated. if (mSourceRecord != null && mSourceRecord.mLaunchRootTask != null) { // Inherit the target-root-task from source to ensure trampoline activities will be // launched into the same root task. @@ -2742,6 +2742,17 @@ class ActivityStarter { mTargetRootTask = getOrCreateRootTask(mStartActivity, mLaunchFlags, intentTask, mOptions); } + } else { + // If a launch target indicated, and the matching task is already in the adjacent task + // of the launch target. Adjust to use the adjacent task as its launch target. So the + // existing task will be launched into the closer one and won't be reparent redundantly. + // TODO(b/231541706): Migrate the logic to wm-shell after having proper APIs to help + // resolve target task without actually starting the activity. + final Task adjacentTargetTask = mTargetRootTask.getAdjacentTaskFragment() != null + ? mTargetRootTask.getAdjacentTaskFragment().asTask() : null; + if (adjacentTargetTask != null && intentActivity.isDescendantOf(adjacentTargetTask)) { + mTargetRootTask = adjacentTargetTask; + } } // If the target task is not in the front, then we need to bring it to the front... @@ -2771,7 +2782,7 @@ class ActivityStarter { intentActivity.setTaskToAffiliateWith(mSourceRecord.getTask()); } - if (mTargetRootTask == intentActivity.getRootTask()) { + if (intentActivity.isDescendantOf(mTargetRootTask)) { // TODO(b/151572268): Figure out a better way to move tasks in above 2-levels // tasks hierarchies. if (mTargetRootTask != intentTask @@ -2818,19 +2829,6 @@ class ActivityStarter { mTargetRootTask = intentActivity.getRootTask(); mSupervisor.handleNonResizableTaskIfNeeded(intentTask, WINDOWING_MODE_UNDEFINED, mRootWindowContainer.getDefaultTaskDisplayArea(), mTargetRootTask); - - // We need to check if there is a launch root task in TDA for this target root task. - // If it exist, we need to reparent target root task from TDA to launch root task. - final TaskDisplayArea tda = mTargetRootTask.getDisplayArea(); - final Task launchRootTask = tda.getLaunchRootTask(mTargetRootTask.getWindowingMode(), - mTargetRootTask.getActivityType(), null /** options */, mSourceRootTask, - mLaunchFlags); - // If target root task is created by organizer, let organizer handle reparent itself. - if (!mTargetRootTask.mCreatedByOrganizer && launchRootTask != null - && launchRootTask != mTargetRootTask) { - mTargetRootTask.reparent(launchRootTask, POSITION_TOP); - mTargetRootTask = launchRootTask; - } } private void resumeTargetRootTaskIfNeeded() { diff --git a/services/core/java/com/android/server/wm/BLASTSyncEngine.java b/services/core/java/com/android/server/wm/BLASTSyncEngine.java index ee7d9a9b3f2d..46ce43335f01 100644 --- a/services/core/java/com/android/server/wm/BLASTSyncEngine.java +++ b/services/core/java/com/android/server/wm/BLASTSyncEngine.java @@ -153,10 +153,10 @@ class BLASTSyncEngine { for (WindowContainer wc : mRootMembers) { wc.waitForSyncTransactionCommit(wcAwaitingCommit); } - final Runnable callback = new Runnable() { + class CommitCallback implements Runnable { // Can run a second time if the action completes after the timeout. boolean ran = false; - public void run() { + public void onCommitted() { synchronized (mWm.mGlobalLock) { if (ran) { return; @@ -171,8 +171,23 @@ class BLASTSyncEngine { wcAwaitingCommit.clear(); } } + + // Called in timeout + @Override + public void run() { + // Sometimes we get a trace, sometimes we get a bugreport without + // a trace. Since these kind of ANRs can trigger such an issue, + // try and ensure we will have some visibility in both cases. + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionCommitTimeout"); + Slog.e(TAG, "WM sent Transaction to organized, but never received" + + " commit callback. Application ANR likely to follow."); + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + onCommitted(); + + } }; - merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::run); + CommitCallback callback = new CommitCallback(); + merged.addTransactionCommittedListener((r) -> { r.run(); }, callback::onCommitted); mWm.mH.postDelayed(callback, BLAST_TIMEOUT_DURATION); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "onTransactionReady"); diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index b37f980ce9a0..247117e707c8 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -124,8 +124,7 @@ class BackNavigationController { LocalServices.getService(WindowManagerInternal.class); IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken(); - window = wmService.windowForClientLocked(null, focusedWindowToken, - false /* throwOnError */); + window = wmService.getFocusedWindowLocked(); if (window == null) { EmbeddedWindowController.EmbeddedWindow embeddedWindow = @@ -147,12 +146,6 @@ class BackNavigationController { } if (window == null) { - window = wmService.getFocusedWindowLocked(); - ProtoLog.d(WM_DEBUG_BACK_PREVIEW, - "Focused window found using wmService.getFocusedWindowLocked()"); - } - - if (window == null) { // We don't have any focused window, fallback ont the top currentTask of the focused // display. ProtoLog.w(WM_DEBUG_BACK_PREVIEW, @@ -194,7 +187,6 @@ class BackNavigationController { if (backType == BackNavigationInfo.TYPE_CALLBACK || currentActivity == null || currentTask == null - || currentTask.getDisplayContent().getImeContainer().isVisible() || currentActivity.isActivityTypeHome()) { return infoBuilder .setType(backType) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2975a95426bb..d0baa238f732 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -2922,7 +2922,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } DisplayCutout loadDisplayCutout(int displayWidth, int displayHeight) { - if (mDisplayPolicy == null) { + if (mDisplayPolicy == null || mInitialDisplayCutout == null) { return null; } return DisplayCutout.fromResourcesRectApproximation( @@ -2931,7 +2931,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } RoundedCorners loadRoundedCorners(int displayWidth, int displayHeight) { - if (mDisplayPolicy == null) { + if (mDisplayPolicy == null || mInitialRoundedCorners == null) { return null; } return RoundedCorners.fromResources( diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index 67dd89ee295c..33cdd2e98113 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -270,6 +270,22 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal } } + @Override + public void notifyPointerDisplayIdChanged(int displayId, float x, float y) { + synchronized (mService.mGlobalLock) { + mService.setMousePointerDisplayId(displayId); + if (displayId == Display.INVALID_DISPLAY) return; + + final DisplayContent dc = mService.mRoot.getDisplayContent(displayId); + if (dc == null) { + Slog.wtf(TAG, "The mouse pointer was moved to display " + displayId + + " that does not have a valid DisplayContent."); + return; + } + mService.restorePointerIconLocked(dc, x, y); + } + } + /** Waits until the built-in input devices have been configured. */ public boolean waitForInputDevicesReady(long timeoutMillis) { synchronized (mInputDevicesReadyMonitor) { diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java index 65062e576d55..ea72e12783c3 100644 --- a/services/core/java/com/android/server/wm/InputMonitor.java +++ b/services/core/java/com/android/server/wm/InputMonitor.java @@ -573,7 +573,7 @@ final class InputMonitor { recentsAnimationController.getTargetAppDisplayArea(); if (targetDA != null) { mRecentsAnimationInputConsumer.reparent(mInputTransaction, targetDA); - mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 1); + mRecentsAnimationInputConsumer.show(mInputTransaction, MAX_VALUE - 2); mAddRecentsAnimationInputConsumerHandle = false; } } @@ -584,10 +584,14 @@ final class InputMonitor { final Task rootTask = w.getTask().getRootTask(); mPipInputConsumer.mWindowHandle.replaceTouchableRegionWithCrop( rootTask.getSurfaceControl()); + final DisplayArea targetDA = rootTask.getDisplayArea(); // We set the layer to z=MAX-1 so that it's always on top. - mPipInputConsumer.reparent(mInputTransaction, rootTask); - mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1); - mAddPipInputConsumerHandle = false; + if (targetDA != null) { + mPipInputConsumer.layout(mInputTransaction, rootTask.getBounds()); + mPipInputConsumer.reparent(mInputTransaction, targetDA); + mPipInputConsumer.show(mInputTransaction, MAX_VALUE - 1); + mAddPipInputConsumerHandle = false; + } } } diff --git a/services/core/java/com/android/server/wm/RecentsAnimationController.java b/services/core/java/com/android/server/wm/RecentsAnimationController.java index efe617d86d4e..2bae59a93048 100644 --- a/services/core/java/com/android/server/wm/RecentsAnimationController.java +++ b/services/core/java/com/android/server/wm/RecentsAnimationController.java @@ -19,7 +19,6 @@ package com.android.server.wm; import static android.app.ActivityTaskManager.INVALID_TASK_ID; import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; -import static android.content.res.Configuration.ORIENTATION_LANDSCAPE; import static android.view.RemoteAnimationTarget.MODE_CLOSING; import static android.view.RemoteAnimationTarget.MODE_OPENING; import static android.view.WindowManager.INPUT_CONSUMER_RECENTS_ANIMATION; @@ -602,22 +601,12 @@ public class RecentsAnimationController implements DeathRecipient { || mDisplayContent.getAsyncRotationController() != null) { return; } - boolean shouldTranslateNavBar = false; - final boolean isDisplayLandscape = - mDisplayContent.getConfiguration().orientation == ORIENTATION_LANDSCAPE; for (int i = mPendingAnimations.size() - 1; i >= 0; i--) { final TaskAnimationAdapter adapter = mPendingAnimations.get(i); final Task task = adapter.mTask; - final TaskFragment adjacentTask = task.getRootTask().getAdjacentTaskFragment(); - final boolean inSplitScreen = task.inSplitScreen(); - if (task.isActivityTypeHomeOrRecents() - // Skip if the task is in split screen and in landscape. - || (inSplitScreen && isDisplayLandscape) - // Skip if the task is the top task in split screen. - || (inSplitScreen && task.getBounds().top < adjacentTask.getBounds().top)) { + if (task.isActivityTypeHomeOrRecents()) { continue; } - shouldTranslateNavBar = inSplitScreen; mNavBarAttachedApp = task.getTopVisibleActivity(); break; } @@ -630,9 +619,7 @@ public class RecentsAnimationController implements DeathRecipient { navWindow.mToken.cancelAnimation(); final SurfaceControl.Transaction t = navWindow.mToken.getPendingTransaction(); final SurfaceControl navSurfaceControl = navWindow.mToken.getSurfaceControl(); - if (shouldTranslateNavBar) { - navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top); - } + navWindow.setSurfaceTranslationY(-mNavBarAttachedApp.getBounds().top); t.reparent(navSurfaceControl, mNavBarAttachedApp.getSurfaceControl()); t.show(navSurfaceControl); diff --git a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java index 4068a97a881a..c7f8a1e2068a 100644 --- a/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java +++ b/services/core/java/com/android/server/wm/SurfaceAnimationRunner.java @@ -158,7 +158,7 @@ class SurfaceAnimationRunner { void continueStartingAnimations() { synchronized (mLock) { mAnimationStartDeferred = false; - if (!mPendingAnimations.isEmpty()) { + if (!mPendingAnimations.isEmpty() && mPreProcessingAnimations.isEmpty()) { mChoreographer.postFrameCallback(this::startAnimations); } } @@ -204,7 +204,7 @@ class SurfaceAnimationRunner { mPreProcessingAnimations.remove(animationLeash); mPendingAnimations.put(animationLeash, runningAnim); - if (!mAnimationStartDeferred) { + if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) { mChoreographer.postFrameCallback(this::startAnimations); } } @@ -214,7 +214,7 @@ class SurfaceAnimationRunner { if (!requiresEdgeExtension) { mPendingAnimations.put(animationLeash, runningAnim); - if (!mAnimationStartDeferred) { + if (!mAnimationStartDeferred && mPreProcessingAnimations.isEmpty()) { mChoreographer.postFrameCallback(this::startAnimations); } @@ -330,6 +330,14 @@ class SurfaceAnimationRunner { private void startAnimations(long frameTimeNanos) { synchronized (mLock) { + if (!mPreProcessingAnimations.isEmpty()) { + // We only want to start running animations once all mPreProcessingAnimations have + // been processed to ensure preprocessed animations start in sync. + // NOTE: This means we might delay running animations that require preprocessing if + // new animations that also require preprocessing are requested before the previous + // ones have finished (see b/227449117). + return; + } startPendingAnimationsLocked(); } mPowerManagerInternal.setPowerBoost(Boost.INTERACTION, 0); @@ -553,4 +561,4 @@ class SurfaceAnimationRunner { return mAnimationHandler; } } -} +}
\ No newline at end of file diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 718ce2870f10..60c280cb61f9 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -28,7 +28,6 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; -import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; @@ -1720,13 +1719,6 @@ class Task extends TaskFragment { && (topTask == null || topTask.supportsSplitScreenWindowingModeInner(tda)); } - /** Returns {@code true} if this task is currently in split-screen. */ - boolean inSplitScreen() { - return getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW - && getCreatedByOrganizerTask() != null - && getCreatedByOrganizerTask().getAdjacentTaskFragment() != null; - } - private boolean supportsSplitScreenWindowingModeInner(@Nullable TaskDisplayArea tda) { return super.supportsSplitScreenWindowingMode() && mAtmService.mSupportsSplitScreenMultiWindow diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 1176182ede50..ce406e4ecb20 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1163,20 +1163,21 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { } } - // For a better split UX, If a task is launching from a created-by-organizer task, it should - // be launched into the same created-by-organizer task as well. Unless, the candidate task - // is already positioned in the split. - Task preferredRootInSplit = sourceTask != null && sourceTask.inSplitScreen() - ? sourceTask.getCreatedByOrganizerTask() : null; - if (preferredRootInSplit != null) { - if (candidateTask != null) { - final Task candidateRoot = candidateTask.getCreatedByOrganizerTask(); - if (candidateRoot != null && candidateRoot != preferredRootInSplit - && preferredRootInSplit == candidateRoot.getAdjacentTaskFragment()) { - preferredRootInSplit = candidateRoot; + // If a task is launching from a created-by-organizer task, it should be launched into the + // same created-by-organizer task as well. Unless, the candidate task is already positioned + // in the another adjacent task. + if (sourceTask != null) { + Task launchTarget = sourceTask.getCreatedByOrganizerTask(); + if (launchTarget != null && launchTarget.getAdjacentTaskFragment() != null) { + if (candidateTask != null) { + final Task candidateRoot = candidateTask.getCreatedByOrganizerTask(); + if (candidateRoot != null && candidateRoot != launchTarget + && launchTarget == candidateRoot.getAdjacentTaskFragment()) { + launchTarget = candidateRoot; + } } + return launchTarget; } - return preferredRootInSplit; } return null; diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index c7bc51356c76..2f00bc821678 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -3741,8 +3741,16 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer< } // Otherwise this is the "root" of a synced subtree, so continue on to preparation. } + // This container's situation has changed so we need to restart its sync. - mSyncState = SYNC_STATE_NONE; + // We cannot reset the sync without a chance of a deadlock since it will request a new + // buffer from the app process. This could cause issues if the app has run out of buffers + // since the previous buffer was already synced and is still held in a transaction. + // Resetting syncState violates the policies outlined in BlastSyncEngine.md so for now + // disable this when shell transitions is disabled. + if (mTransitionController.isShellTransitionsEnabled()) { + mSyncState = SYNC_STATE_NONE; + } prepareSync(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index f8bc26a7d91d..c0d7d1362ac3 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -23,17 +23,18 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.ClipData; import android.content.Context; +import android.graphics.Matrix; import android.graphics.Rect; import android.graphics.Region; import android.hardware.display.DisplayManagerInternal; import android.os.Bundle; import android.os.IBinder; +import android.util.Pair; import android.view.Display; import android.view.IInputFilter; import android.view.IRemoteAnimationFinishedCallback; import android.view.IWindow; import android.view.InputChannel; -import android.view.InputWindowHandle; import android.view.MagnificationSpec; import android.view.RemoteAnimationTarget; import android.view.SurfaceControl; @@ -459,6 +460,17 @@ public abstract class WindowManagerInternal { public abstract void getWindowFrame(IBinder token, Rect outBounds); /** + * Get the transformation matrix and MagnificationSpec given its token. + * + * @param token The token. + * @return The pair of the transformation matrix and magnification spec. + */ + // TODO (b/231663133): Long term solution for tracking window when the + // FLAG_RETRIEVE_INTERACTIVE_WINDOWS is unset. + public abstract Pair<Matrix, MagnificationSpec> + getWindowTransformationMatrixAndMagnificationSpec(IBinder token); + + /** * Opens the global actions dialog. */ public abstract void showGlobalActions(); @@ -849,24 +861,12 @@ public abstract class WindowManagerInternal { public abstract SurfaceControl getHandwritingSurfaceForDisplay(int displayId); /** - * Replaces the touchable region of the provided input surface with the crop of the window with - * the provided token. This method will associate the inputSurface with a copy of - * the given inputWindowHandle, where the copy is configured using - * {@link InputWindowHandle#replaceTouchableRegionWithCrop(SurfaceControl)} with the surface - * of the provided windowToken. - * - * This is a no-op if windowToken is not valid or the window is not found. - * - * This does not change any other properties of the inputSurface. - * - * This method exists to avoid leaking the window's SurfaceControl outside WindowManagerService. + * Returns {@code true} if the given point is within the window bounds of the given window. * - * @param inputSurface The surface for which the touchable region should be set. - * @param inputWindowHandle The {@link InputWindowHandle} for the input surface. - * @param windowToken The window whose bounds should be used as the touchable region for the - * inputSurface. + * @param windowToken the window whose bounds should be used for the hit test. + * @param displayX the x coordinate of the test point in the display's coordinate space. + * @param displayY the y coordinate of the test point in the display's coordinate space. */ - public abstract void replaceInputSurfaceTouchableRegionWithWindowCrop( - @NonNull SurfaceControl inputSurface, @NonNull InputWindowHandle inputWindowHandle, - @NonNull IBinder windowToken); + public abstract boolean isPointInsideWindow( + @NonNull IBinder windowToken, int displayId, float displayX, float displayY); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 7b77fd0683cd..8f1f7ece897b 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -172,6 +172,7 @@ import android.content.res.Resources; import android.content.res.TypedArray; import android.database.ContentObserver; import android.graphics.Bitmap; +import android.graphics.Matrix; import android.graphics.Point; import android.graphics.PointF; import android.graphics.Rect; @@ -223,6 +224,7 @@ import android.util.ArraySet; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.MergedConfiguration; +import android.util.Pair; import android.util.Slog; import android.util.SparseBooleanArray; import android.util.TimeUtils; @@ -7198,18 +7200,42 @@ public class WindowManagerService extends IWindowManager.Stub private float mLatestMouseX; private float mLatestMouseY; - void updatePosition(float x, float y) { + /** + * The display that the pointer (mouse cursor) is currently shown on. This is updated + * directly by InputManagerService when the pointer display changes. + */ + private int mPointerDisplayId = INVALID_DISPLAY; + + /** + * Update the mouse cursor position as a result of a mouse movement. + * @return true if the position was successfully updated, false otherwise. + */ + boolean updatePosition(int displayId, float x, float y) { synchronized (this) { mLatestEventWasMouse = true; + + if (displayId != mPointerDisplayId) { + // The display of the position update does not match the display on which the + // mouse pointer is shown, so do not update the position. + return false; + } mLatestMouseX = x; mLatestMouseY = y; + return true; + } + } + + void setPointerDisplayId(int displayId) { + synchronized (this) { + mPointerDisplayId = displayId; } } @Override public void onPointerEvent(MotionEvent motionEvent) { if (motionEvent.isFromSource(InputDevice.SOURCE_MOUSE)) { - updatePosition(motionEvent.getRawX(), motionEvent.getRawY()); + updatePosition(motionEvent.getDisplayId(), motionEvent.getRawX(), + motionEvent.getRawY()); } else { synchronized (this) { mLatestEventWasMouse = false; @@ -7219,6 +7245,7 @@ public class WindowManagerService extends IWindowManager.Stub }; void updatePointerIcon(IWindow client) { + int pointerDisplayId; float mouseX, mouseY; synchronized(mMousePositionTracker) { @@ -7227,6 +7254,7 @@ public class WindowManagerService extends IWindowManager.Stub } mouseX = mMousePositionTracker.mLatestMouseX; mouseY = mMousePositionTracker.mLatestMouseY; + pointerDisplayId = mMousePositionTracker.mPointerDisplayId; } synchronized (mGlobalLock) { @@ -7243,6 +7271,10 @@ public class WindowManagerService extends IWindowManager.Stub if (displayContent == null) { return; } + if (pointerDisplayId != displayContent.getDisplayId()) { + // Do not let the pointer icon be updated by a window on a different display. + return; + } WindowState windowUnderPointer = displayContent.getTouchableWinAtPointLocked(mouseX, mouseY); if (windowUnderPointer != callingWin) { @@ -7260,7 +7292,11 @@ public class WindowManagerService extends IWindowManager.Stub void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) { // Mouse position tracker has not been getting updates while dragging, update it now. - mMousePositionTracker.updatePosition(latestX, latestY); + if (!mMousePositionTracker.updatePosition( + displayContent.getDisplayId(), latestX, latestY)) { + // The mouse position could not be updated, so ignore this request. + return; + } WindowState windowUnderPointer = displayContent.getTouchableWinAtPointLocked(latestX, latestY); @@ -7284,6 +7320,10 @@ public class WindowManagerService extends IWindowManager.Stub } } + void setMousePointerDisplayId(int displayId) { + mMousePositionTracker.setPointerDisplayId(displayId); + } + /** * Update a tap exclude region in the window identified by the provided id. Touches down on this * region will not: @@ -7721,6 +7761,13 @@ public class WindowManagerService extends IWindowManager.Stub } @Override + public Pair<Matrix, MagnificationSpec> getWindowTransformationMatrixAndMagnificationSpec( + IBinder token) { + return mAccessibilityController + .getWindowTransformationMatrixAndMagnificationSpec(token); + } + + @Override public void waitForAllWindowsDrawn(Runnable callback, long timeout, int displayId) { final WindowContainer container = displayId == INVALID_DISPLAY ? mRoot : mRoot.getDisplayContent(displayId); @@ -8193,23 +8240,15 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void replaceInputSurfaceTouchableRegionWithWindowCrop( - @NonNull SurfaceControl inputSurface, - @NonNull InputWindowHandle inputWindowHandle, - @NonNull IBinder windowToken) { + public boolean isPointInsideWindow(@NonNull IBinder windowToken, int displayId, + float displayX, float displayY) { synchronized (mGlobalLock) { final WindowState w = mWindowMap.get(windowToken); - if (w == null) { - return; + if (w == null || w.getDisplayId() != displayId) { + return false; } - // Make a copy of the InputWindowHandle to avoid leaking the window's - // SurfaceControl. - final InputWindowHandle localHandle = new InputWindowHandle(inputWindowHandle); - localHandle.replaceTouchableRegionWithCrop(w.getSurfaceControl()); - final SurfaceControl.Transaction t = mTransactionFactory.get(); - t.setInputWindowInfo(inputSurface, localHandle); - t.apply(); - t.close(); + + return w.getBounds().contains((int) displayX, (int) displayY); } } } @@ -8829,16 +8868,18 @@ public class WindowManagerService extends IWindowManager.Stub WindowState newFocusTarget = displayContent == null ? null : displayContent.findFocusedWindow(); if (newFocusTarget == null) { - ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus remove request for " - + "win=%s dropped since no candidate was found", + t.setFocusedWindow(null, null, displayId).apply(); + ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s" + + " dropped focus so setting focus to null since no candidate" + + " was found", embeddedWindow); return; } - t.requestFocusTransfer(newFocusTarget.mInputChannelToken, newFocusTarget.getName(), - inputToken, embeddedWindow.toString(), + t.setFocusedWindow(newFocusTarget.mInputChannelToken, newFocusTarget.getName(), displayId).apply(); + EventLog.writeEvent(LOGTAG_INPUT_FOCUS, - "Transfer focus request " + newFocusTarget, + "Focus request " + newFocusTarget, "reason=grantEmbeddedWindowFocus(false)"); } ProtoLog.v(WM_DEBUG_FOCUS, "grantEmbeddedWindowFocus win=%s grantFocus=%s", diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index cd19f64ba12e..3b282aada7ae 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -3639,13 +3639,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP final int requested = mLastRequestedExclusionHeight[side]; final int granted = mLastGrantedExclusionHeight[side]; - final boolean inSplitScreen = getTask() != null && getTask().inSplitScreen(); - FrameworkStatsLog.write(FrameworkStatsLog.EXCLUSION_RECT_STATE_CHANGED, mAttrs.packageName, requested, requested - granted /* rejected */, side + 1 /* Sides are 1-indexed in atoms.proto */, (getConfiguration().orientation == ORIENTATION_LANDSCAPE), - inSplitScreen, (int) duration); + false /* (deprecated param) inSplitscreen */, (int) duration); } private void initExclusionRestrictions() { diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java index d2e56faa0914..ed9b2f072bff 100644 --- a/services/core/java/com/android/server/wm/WindowToken.java +++ b/services/core/java/com/android/server/wm/WindowToken.java @@ -378,7 +378,7 @@ class WindowToken extends WindowContainer<WindowState> { SurfaceControl.Builder makeSurface() { final SurfaceControl.Builder builder = super.makeSurface(); if (mRoundedCornerOverlay) { - builder.setParent(null); + builder.setParent(getDisplayContent().getSurfaceControl()); } return builder; } diff --git a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp index 49a4021b1a84..93152f2ea1b7 100644 --- a/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp +++ b/services/core/jni/com_android_server_am_CachedAppOptimizer.cpp @@ -66,13 +66,13 @@ using android::base::unique_fd; // Defines the maximum amount of VMAs we can send per process_madvise syscall. // Currently this is set to UIO_MAXIOV which is the maximum segments allowed by // iovec implementation used by process_madvise syscall -#define MAX_VMAS_PER_BATCH UIO_MAXIOV +#define MAX_VMAS_PER_COMPACTION UIO_MAXIOV // Maximum bytes that we can send per process_madvise syscall once this limit // is reached we split the remaining VMAs into another syscall. The MAX_RW_COUNT // limit is imposed by iovec implementation. However, if you want to use a smaller -// limit, it has to be a page aligned value. -#define MAX_BYTES_PER_BATCH MAX_RW_COUNT +// limit, it has to be a page aligned value, otherwise, compaction would fail. +#define MAX_BYTES_PER_COMPACTION MAX_RW_COUNT // Selected a high enough number to avoid clashing with linux errno codes #define ERROR_COMPACTION_CANCELLED -1000 @@ -83,181 +83,6 @@ namespace android { // before starting next VMA batch static std::atomic<bool> cancelRunningCompaction; -// A VmaBatch represents a set of VMAs that can be processed -// as VMAs are processed by client code it is expected that the -// VMAs get consumed which means they are discarded as they are -// processed so that the first element always is the next element -// to be sent -struct VmaBatch { - struct iovec* vmas; - // total amount of VMAs to reach the end of iovec - int totalVmas; - // total amount of bytes that are remaining within iovec - uint64_t totalBytes; -}; - -// Advances the iterator by the specified amount of bytes. -// This is used to remove already processed or no longer -// needed parts of the batch. -// Returns total bytes consumed -int consumeBytes(VmaBatch& batch, uint64_t bytesToConsume) { - int index = 0; - if (CC_UNLIKELY(bytesToConsume) < 0) { - LOG(ERROR) << "Cannot consume negative bytes for VMA batch !"; - return 0; - } - - if (bytesToConsume > batch.totalBytes) { - // Avoid consuming more bytes than available - bytesToConsume = batch.totalBytes; - } - - uint64_t bytesConsumed = 0; - while (bytesConsumed < bytesToConsume) { - if (CC_UNLIKELY(index >= batch.totalVmas)) { - // reach the end of the batch - return bytesConsumed; - } - if (CC_UNLIKELY(bytesConsumed + batch.vmas[index].iov_len > bytesToConsume)) { - // this is the whole VMA that will be consumed - break; - } - bytesConsumed += batch.vmas[index].iov_len; - batch.totalBytes -= batch.vmas[index].iov_len; - --batch.totalVmas; - ++index; - } - - // Move pointer to consume all the whole VMAs - batch.vmas = batch.vmas + index; - - // Consume the rest of the bytes partially at last VMA in batch - uint64_t bytesLeftToConsume = bytesToConsume - bytesConsumed; - bytesConsumed += bytesLeftToConsume; - if (batch.totalVmas > 0) { - batch.vmas[0].iov_base = (void*)((uint64_t)batch.vmas[0].iov_base + bytesLeftToConsume); - } - - return bytesConsumed; -} - -// given a source of vmas this class will act as a factory -// of VmaBatch objects and it will allow generating batches -// until there are no more left in the source vector. -// Note: the class does not actually modify the given -// vmas vector, instead it iterates on it until the end. -class VmaBatchCreator { - const std::vector<Vma>* sourceVmas; - // This is the destination array where batched VMAs will be stored - // it gets encapsulated into a VmaBatch which is the object - // meant to be used by client code. - struct iovec* destVmas; - - // Parameters to keep track of the iterator on the source vmas - int currentIndex_; - uint64_t currentOffset_; - -public: - VmaBatchCreator(const std::vector<Vma>* vmasToBatch, struct iovec* destVmasVec) - : sourceVmas(vmasToBatch), destVmas(destVmasVec), currentIndex_(0), currentOffset_(0) {} - - int currentIndex() { return currentIndex_; } - uint64_t currentOffset() { return currentOffset_; } - - // Generates a batch and moves the iterator on the source vmas - // past the last VMA in the batch. - // Returns true on success, false on failure - bool createNextBatch(VmaBatch& batch) { - if (currentIndex_ >= MAX_VMAS_PER_BATCH && currentIndex_ >= sourceVmas->size()) { - return false; - } - - const std::vector<Vma>& vmas = *sourceVmas; - batch.vmas = destVmas; - uint64_t totalBytesInBatch = 0; - int indexInBatch = 0; - - // Add VMAs to the batch up until we consumed all the VMAs or - // reached any imposed limit of VMAs per batch. - while (indexInBatch < MAX_VMAS_PER_BATCH && currentIndex_ < vmas.size()) { - uint64_t vmaStart = vmas[currentIndex_].start + currentOffset_; - uint64_t vmaSize = vmas[currentIndex_].end - vmaStart; - if (CC_UNLIKELY(vmaSize == 0)) { - // No more bytes to batch for this VMA, move to next one - // this only happens if a batch partially consumed bytes - // and offset landed at exactly the end of a vma - continue; - } - batch.vmas[indexInBatch].iov_base = (void*)vmaStart; - uint64_t bytesAvailableInBatch = MAX_BYTES_PER_BATCH - totalBytesInBatch; - - if (vmaSize >= bytesAvailableInBatch) { - // VMA would exceed the max available bytes in batch - // clamp with available bytes and finish batch. - vmaSize = bytesAvailableInBatch; - currentOffset_ += bytesAvailableInBatch; - } - - batch.vmas[indexInBatch].iov_len = vmaSize; - totalBytesInBatch += vmaSize; - - ++indexInBatch; - if (totalBytesInBatch >= MAX_BYTES_PER_BATCH) { - // Reached max bytes quota so this marks - // the end of the batch - break; - } - - // Fully finished current VMA, move to next one - currentOffset_ = 0; - ++currentIndex_; - } - // Vmas where fully filled and we are past the last filled index. - batch.totalVmas = indexInBatch; - batch.totalBytes = totalBytesInBatch; - return true; - } -}; - -// Madvise a set of VMAs given in a batch for a specific process -// The total number of bytes successfully madvised will be set on -// outBytesProcessed. -// Returns 0 on success and standard linux -errno code returned by -// process_madvise on failure -int madviseVmasFromBatch(unique_fd& pidfd, VmaBatch& batch, int madviseType, - uint64_t* outBytesProcessed) { - if (batch.totalVmas == 0) { - // No VMAs in Batch, skip. - *outBytesProcessed = 0; - return 0; - } - - ATRACE_BEGIN(StringPrintf("Madvise %d: %d VMAs", madviseType, batch.totalVmas).c_str()); - uint64_t bytesProcessedInSend = - process_madvise(pidfd, batch.vmas, batch.totalVmas, madviseType, 0); - ATRACE_END(); - - if (CC_UNLIKELY(bytesProcessedInSend == -1)) { - bytesProcessedInSend = 0; - if (errno != EINVAL) { - // Forward irrecoverable errors and bail out compaction - *outBytesProcessed = 0; - return -errno; - } - } - - if (bytesProcessedInSend < batch.totalBytes) { - // Did not process all the bytes requested - // skip last page which likely failed - bytesProcessedInSend += PAGE_SIZE; - } - - bytesProcessedInSend = consumeBytes(batch, bytesProcessedInSend); - - *outBytesProcessed = bytesProcessedInSend; - return 0; -} - // Legacy method for compacting processes, any new code should // use compactProcess instead. static inline void compactProcessProcfs(int pid, const std::string& compactionType) { @@ -271,6 +96,8 @@ static inline void compactProcessProcfs(int pid, const std::string& compactionTy // If any VMA fails compaction due to -EINVAL it will be skipped and continue. // However, if it fails for any other reason, it will bail out and forward the error static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseType) { + static struct iovec vmasToKernel[MAX_VMAS_PER_COMPACTION]; + if (vmas.empty()) { return 0; } @@ -281,16 +108,13 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT return -errno; } - struct iovec destVmas[MAX_VMAS_PER_BATCH]; - - VmaBatch batch; - VmaBatchCreator batcher(&vmas, destVmas); - int64_t totalBytesProcessed = 0; - while (batcher.createNextBatch(batch)) { - uint64_t bytesProcessedInSend; - do { + int64_t vmaOffset = 0; + for (int iVma = 0; iVma < vmas.size();) { + uint64_t bytesSentToCompact = 0; + int iVec = 0; + while (iVec < MAX_VMAS_PER_COMPACTION && iVma < vmas.size()) { if (CC_UNLIKELY(cancelRunningCompaction.load())) { // There could be a significant delay between when a compaction // is requested and when it is handled during this time our @@ -300,13 +124,50 @@ static int64_t compactMemory(const std::vector<Vma>& vmas, int pid, int madviseT StringPrintf("Cancelled compaction for %d", pid).c_str()); return ERROR_COMPACTION_CANCELLED; } - int error = madviseVmasFromBatch(pidfd, batch, madviseType, &bytesProcessedInSend); - if (error < 0) { - // Returns standard linux errno code - return error; + + uint64_t vmaStart = vmas[iVma].start + vmaOffset; + uint64_t vmaSize = vmas[iVma].end - vmaStart; + if (vmaSize == 0) { + goto next_vma; + } + vmasToKernel[iVec].iov_base = (void*)vmaStart; + if (vmaSize > MAX_BYTES_PER_COMPACTION - bytesSentToCompact) { + // Exceeded the max bytes that could be sent, so clamp + // the end to avoid exceeding limit and issue compaction + vmaSize = MAX_BYTES_PER_COMPACTION - bytesSentToCompact; } - totalBytesProcessed += bytesProcessedInSend; - } while (batch.totalBytes > 0); + + vmasToKernel[iVec].iov_len = vmaSize; + bytesSentToCompact += vmaSize; + ++iVec; + if (bytesSentToCompact >= MAX_BYTES_PER_COMPACTION) { + // Ran out of bytes within iovec, dispatch compaction. + vmaOffset += vmaSize; + break; + } + + next_vma: + // Finished current VMA, and have more bytes remaining + vmaOffset = 0; + ++iVma; + } + + ATRACE_BEGIN(StringPrintf("Compact %d VMAs", iVec).c_str()); + auto bytesProcessed = process_madvise(pidfd, vmasToKernel, iVec, madviseType, 0); + ATRACE_END(); + + if (CC_UNLIKELY(bytesProcessed == -1)) { + if (errno == EINVAL) { + // This error is somewhat common due to an unevictable VMA if this is + // the case silently skip the bad VMA and continue compacting the rest. + continue; + } else { + // Forward irrecoverable errors and bail out compaction + return -errno; + } + } + + totalBytesProcessed += bytesProcessed; } return totalBytesProcessed; diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index 3c5ebe7c62ee..32adac7f282b 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -107,6 +107,7 @@ static struct { jmethodID interceptKeyBeforeDispatching; jmethodID dispatchUnhandledKey; jmethodID checkInjectEventsPermission; + jmethodID onPointerDisplayIdChanged; jmethodID onPointerDownOutsideFocus; jmethodID getVirtualKeyQuietTimeMillis; jmethodID getExcludedDeviceNames; @@ -120,7 +121,6 @@ static struct { jmethodID getLongPressTimeout; jmethodID getPointerLayer; jmethodID getPointerIcon; - jmethodID getPointerDisplayId; jmethodID getKeyboardLayoutOverlay; jmethodID getDeviceAlias; jmethodID getTouchCalibrationForInputDevice; @@ -277,6 +277,7 @@ public: void setFocusedDisplay(int32_t displayId); void setInputDispatchMode(bool enabled, bool frozen); void setSystemUiLightsOut(bool lightsOut); + void setPointerDisplayId(int32_t displayId); void setPointerSpeed(int32_t speed); void setPointerAcceleration(float acceleration); void setInputDeviceEnabled(uint32_t deviceId, bool enabled); @@ -288,7 +289,6 @@ public: void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled); void setCustomPointerIcon(const SpriteIcon& icon); void setMotionClassifierEnabled(bool enabled); - void notifyPointerDisplayIdChanged(); /* --- InputReaderPolicyInterface implementation --- */ @@ -346,6 +346,7 @@ public: std::map<int32_t, PointerAnimation>* outAnimationResources, int32_t displayId); virtual int32_t getDefaultPointerIconId(); virtual int32_t getCustomPointerIconId(); + virtual void onPointerDisplayIdChanged(int32_t displayId, float xPos, float yPos); private: sp<InputManagerInterface> mInputManager; @@ -394,7 +395,6 @@ private: void updateInactivityTimeoutLocked(); void handleInterceptActions(jint wmActions, nsecs_t when, uint32_t& policyFlags); void ensureSpriteControllerLocked(); - int32_t getPointerDisplayId(); sp<SurfaceControl> getParentSurfaceForPointers(int displayId); static bool checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName); @@ -498,13 +498,9 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO } } - // Get the preferred pointer controller displayId. - int32_t pointerDisplayId = getPointerDisplayId(); - { // acquire lock AutoMutex _l(mLock); mLocked.viewports = viewports; - mLocked.pointerDisplayId = pointerDisplayId; std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller != nullptr) { controller->onDisplayViewportsUpdated(mLocked.viewports); @@ -666,15 +662,12 @@ std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerCon return controller; } -int32_t NativeInputManager::getPointerDisplayId() { +void NativeInputManager::onPointerDisplayIdChanged(int32_t pointerDisplayId, float xPos, + float yPos) { JNIEnv* env = jniEnv(); - jint pointerDisplayId = env->CallIntMethod(mServiceObj, - gServiceClassInfo.getPointerDisplayId); - if (checkAndClearExceptionFromCallback(env, "getPointerDisplayId")) { - pointerDisplayId = ADISPLAY_ID_DEFAULT; - } - - return pointerDisplayId; + env->CallVoidMethod(mServiceObj, gServiceClassInfo.onPointerDisplayIdChanged, pointerDisplayId, + xPos, yPos); + checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged"); } sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) { @@ -1032,6 +1025,22 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { : InactivityTimeout::NORMAL); } +void NativeInputManager::setPointerDisplayId(int32_t displayId) { + { // acquire lock + AutoMutex _l(mLock); + + if (mLocked.pointerDisplayId == displayId) { + return; + } + + ALOGI("Setting pointer display id to %d.", displayId); + mLocked.pointerDisplayId = displayId; + } // release lock + + mInputManager->getReader().requestRefreshConfiguration( + InputReaderConfiguration::CHANGE_DISPLAY_INFO); +} + void NativeInputManager::setPointerSpeed(int32_t speed) { { // acquire lock AutoMutex _l(mLock); @@ -1494,18 +1503,6 @@ void NativeInputManager::setMotionClassifierEnabled(bool enabled) { mInputManager->getClassifier().setMotionClassifierEnabled(enabled); } -void NativeInputManager::notifyPointerDisplayIdChanged() { - int32_t pointerDisplayId = getPointerDisplayId(); - - { // acquire lock - AutoMutex _l(mLock); - mLocked.pointerDisplayId = pointerDisplayId; - } // release lock - - mInputManager->getReader().requestRefreshConfiguration( - InputReaderConfiguration::CHANGE_DISPLAY_INFO); -} - // ---------------------------------------------------------------------------- static NativeInputManager* getNativeInputManager(JNIEnv* env, jobject clazz) { @@ -2199,11 +2196,6 @@ static void nativeNotifyPortAssociationsChanged(JNIEnv* env, jobject nativeImplO InputReaderConfiguration::CHANGE_DISPLAY_INFO); } -static void nativeNotifyPointerDisplayIdChanged(JNIEnv* env, jobject nativeImplObj) { - NativeInputManager* im = getNativeInputManager(env, nativeImplObj); - im->notifyPointerDisplayIdChanged(); -} - static void nativeSetDisplayEligibilityForPointerCapture(JNIEnv* env, jobject nativeImplObj, jint displayId, jboolean isEligible) { NativeInputManager* im = getNativeInputManager(env, nativeImplObj); @@ -2321,6 +2313,11 @@ static void nativeCancelCurrentTouch(JNIEnv* env, jobject nativeImplObj) { im->getInputManager()->getDispatcher().cancelCurrentTouch(); } +static void nativeSetPointerDisplayId(JNIEnv* env, jobject nativeImplObj, jint displayId) { + NativeInputManager* im = getNativeInputManager(env, nativeImplObj); + im->setPointerDisplayId(displayId); +} + // ---------------------------------------------------------------------------- static const JNINativeMethod gInputManagerMethods[] = { @@ -2393,7 +2390,6 @@ static const JNINativeMethod gInputManagerMethods[] = { {"canDispatchToDisplay", "(II)Z", (void*)nativeCanDispatchToDisplay}, {"notifyPortAssociationsChanged", "()V", (void*)nativeNotifyPortAssociationsChanged}, {"changeUniqueIdAssociation", "()V", (void*)nativeChangeUniqueIdAssociation}, - {"notifyPointerDisplayIdChanged", "()V", (void*)nativeNotifyPointerDisplayIdChanged}, {"setDisplayEligibilityForPointerCapture", "(IZ)V", (void*)nativeSetDisplayEligibilityForPointerCapture}, {"setMotionClassifierEnabled", "(Z)V", (void*)nativeSetMotionClassifierEnabled}, @@ -2403,6 +2399,7 @@ static const JNINativeMethod gInputManagerMethods[] = { {"disableSensor", "(II)V", (void*)nativeDisableSensor}, {"flushSensor", "(II)Z", (void*)nativeFlushSensor}, {"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch}, + {"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId}, }; #define FIND_CLASS(var, className) \ @@ -2498,6 +2495,9 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.checkInjectEventsPermission, clazz, "checkInjectEventsPermission", "(II)Z"); + GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged", + "(IFF)V"); + GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz, "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V"); @@ -2537,9 +2537,6 @@ int register_android_server_InputManager(JNIEnv* env) { GET_METHOD_ID(gServiceClassInfo.getPointerIcon, clazz, "getPointerIcon", "(I)Landroid/view/PointerIcon;"); - GET_METHOD_ID(gServiceClassInfo.getPointerDisplayId, clazz, - "getPointerDisplayId", "()I"); - GET_METHOD_ID(gServiceClassInfo.getKeyboardLayoutOverlay, clazz, "getKeyboardLayoutOverlay", "(Landroid/hardware/input/InputDeviceIdentifier;)[Ljava/lang/String;"); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 8a7134e24cf8..7f466f38e96a 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -8513,6 +8513,13 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } } + private boolean isDeviceOwnerUserId(int userId) { + synchronized (getLockObject()) { + return mOwners.getDeviceOwnerComponent() != null + && mOwners.getDeviceOwnerUserId() == userId; + } + } + private boolean isDeviceOwnerPackage(String packageName, int userId) { synchronized (getLockObject()) { return mOwners.hasDeviceOwner() @@ -12117,10 +12124,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { return; } final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isProfileOwner(caller) + Preconditions.checkCallAuthorization((isProfileOwner(caller) + && isManagedProfile(caller.getUserId())) || isDefaultDeviceOwner(caller), - "Caller is not profile owner or device owner;" - + " only profile owner or device owner may control the preferential" + "Caller is not managed profile owner or device owner;" + + " only managed profile owner or device owner may control the preferential" + " network service"); synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked( @@ -12147,11 +12155,12 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { } final CallerIdentity caller = getCallerIdentity(); - Preconditions.checkCallAuthorization(isProfileOwner(caller) + Preconditions.checkCallAuthorization((isProfileOwner(caller) + && isManagedProfile(caller.getUserId())) || isDefaultDeviceOwner(caller), - "Caller is not profile owner or device owner;" - + " only profile owner or device owner may retrieve the preferential" - + " network service configurations"); + "Caller is not managed profile owner or device owner;" + + " only managed profile owner or device owner may retrieve the " + + "preferential network service configurations"); synchronized (getLockObject()) { final ActiveAdmin requiredAdmin = getDeviceOrProfileOwnerAdminLocked( caller.getUserId()); @@ -18266,7 +18275,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager { private void updateNetworkPreferenceForUser(int userId, List<PreferentialNetworkServiceConfig> preferentialNetworkServiceConfigs) { - if (!isManagedProfile(userId)) { + if (!isManagedProfile(userId) && !isDeviceOwnerUserId(userId)) { return; } List<ProfileNetworkPreference> preferences = new ArrayList<>(); diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 8aa9f60e922d..994a76700dc0 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -238,7 +238,7 @@ public class MidiService extends IMidiManager.Stub { } } - // called from Device.close() + // called from Device.closeLocked() public void removeDeviceConnection(DeviceConnection connection) { mDeviceConnections.remove(connection.getToken()); if (mListeners.size() == 0 && mDeviceConnections.size() == 0) { @@ -294,12 +294,6 @@ public class MidiService extends IMidiManager.Stub { } for (DeviceConnection connection : mDeviceConnections.values()) { - if (connection.getDevice().getDeviceInfo().getType() - == MidiDeviceInfo.TYPE_USB) { - synchronized (mUsbMidiLock) { - removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo()); - } - } connection.getDevice().removeDeviceConnection(connection); } } @@ -541,6 +535,13 @@ public class MidiService extends IMidiManager.Stub { synchronized (mDeviceConnections) { mDeviceConnections.remove(connection); + if (connection.getDevice().getDeviceInfo().getType() + == MidiDeviceInfo.TYPE_USB) { + synchronized (mUsbMidiLock) { + removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo()); + } + } + if (mDeviceConnections.size() == 0 && mServiceConnection != null) { mContext.unbindService(mServiceConnection); mServiceConnection = null; @@ -559,6 +560,12 @@ public class MidiService extends IMidiManager.Stub { public void closeLocked() { synchronized (mDeviceConnections) { for (DeviceConnection connection : mDeviceConnections) { + if (connection.getDevice().getDeviceInfo().getType() + == MidiDeviceInfo.TYPE_USB) { + synchronized (mUsbMidiLock) { + removeUsbMidiDeviceLocked(connection.getDevice().getDeviceInfo()); + } + } connection.getClient().removeDeviceConnection(connection); } mDeviceConnections.clear(); @@ -1401,6 +1408,8 @@ public class MidiService extends IMidiManager.Stub { String deviceName = extractUsbDeviceName(name); String tagName = extractUsbDeviceTag(name); + Log.i(TAG, "Checking " + deviceName + " " + tagName); + // Only one MIDI 2.0 device can be used at once. // Multiple MIDI 1.0 devices can be used at once. if (mUsbMidiUniversalDeviceInUse.contains(deviceName) @@ -1420,6 +1429,8 @@ public class MidiService extends IMidiManager.Stub { String deviceName = extractUsbDeviceName(name); String tagName = extractUsbDeviceTag(name); + Log.i(TAG, "Adding " + deviceName + " " + tagName); + if ((tagName).equals(MIDI_UNIVERSAL_STRING)) { mUsbMidiUniversalDeviceInUse.add(deviceName); } else if ((tagName).equals(MIDI_LEGACY_STRING)) { @@ -1437,6 +1448,8 @@ public class MidiService extends IMidiManager.Stub { String deviceName = extractUsbDeviceName(name); String tagName = extractUsbDeviceTag(name); + Log.i(TAG, "Removing " + deviceName + " " + tagName); + if ((tagName).equals(MIDI_UNIVERSAL_STRING)) { mUsbMidiUniversalDeviceInUse.remove(deviceName); } else if ((tagName).equals(MIDI_LEGACY_STRING)) { diff --git a/services/proguard.flags b/services/proguard.flags index 425da6c11177..bad02b47031c 100644 --- a/services/proguard.flags +++ b/services/proguard.flags @@ -33,6 +33,11 @@ public <init>(...); } +# Accessed from com.android.compos APEX +-keep,allowoptimization,allowaccessmodification class com.android.internal.art.ArtStatsLog { + public static void write(...); +} + # Binder interfaces -keep,allowoptimization,allowaccessmodification class * extends android.os.IInterface -keep,allowoptimization,allowaccessmodification class * extends android.os.IHwInterface diff --git a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java index 2f68306e9ba1..8d59dce29bd9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/MockingOomAdjusterTests.java @@ -30,6 +30,7 @@ import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; import static android.app.ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT; +import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT; import static android.app.ActivityManager.PROCESS_STATE_PERSISTENT_UI; import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; import static android.app.ActivityManager.PROCESS_STATE_SERVICE; @@ -1591,6 +1592,69 @@ public class MockingOomAdjusterTests { @SuppressWarnings("GuardedBy") @Test + public void testUpdateOomAdj_DoOne_TreatLikeVisFGS() { + final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, + MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); + final ProcessRecord app2 = spy(makeDefaultProcessRecord(MOCKAPP2_PID, MOCKAPP2_UID, + MOCKAPP2_PROCESSNAME, MOCKAPP2_PACKAGENAME, false)); + final ProcessRecord client1 = spy(makeDefaultProcessRecord(MOCKAPP3_PID, MOCKAPP3_UID, + MOCKAPP3_PROCESSNAME, MOCKAPP3_PACKAGENAME, false)); + final ProcessRecord client2 = spy(makeDefaultProcessRecord(MOCKAPP4_PID, MOCKAPP4_UID, + MOCKAPP4_PROCESSNAME, MOCKAPP4_PACKAGENAME, false)); + client1.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + client2.mState.setMaxAdj(PERSISTENT_PROC_ADJ); + + final ServiceRecord s1 = bindService(app1, client1, null, + Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, mock(IBinder.class)); + final ServiceRecord s2 = bindService(app2, client2, null, + Context.BIND_IMPORTANT, mock(IBinder.class)); + + sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE); + + assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, VISIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT); + assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ, + SCHED_GROUP_DEFAULT); + + bindService(app2, client1, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, + mock(IBinder.class)); + sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE); + assertProcStates(app2, PROCESS_STATE_PERSISTENT, PERSISTENT_SERVICE_ADJ, + SCHED_GROUP_DEFAULT); + + s1.getConnections().clear(); + s2.getConnections().clear(); + client1.mState.setMaxAdj(UNKNOWN_ADJ); + client2.mState.setMaxAdj(UNKNOWN_ADJ); + client1.mServices.setHasForegroundServices(true, 0); + client2.mState.setHasOverlayUi(true); + + bindService(app1, client1, s1, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, + mock(IBinder.class)); + bindService(app2, client2, s2, Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE, + mock(IBinder.class)); + + sService.mOomAdjuster.updateOomAdjLocked(app1, OomAdjuster.OOM_ADJ_REASON_NONE); + sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE); + + assertProcStates(app1, PROCESS_STATE_FOREGROUND_SERVICE, PERCEPTIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT); + assertProcStates(app2, PROCESS_STATE_IMPORTANT_FOREGROUND, PERCEPTIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT); + + client2.mState.setHasOverlayUi(false); + doReturn(PROCESS_STATE_TOP).when(sService.mAtmInternal).getTopProcessState(); + doReturn(client2).when(sService).getTopApp(); + sService.mWakefulness.set(PowerManagerInternal.WAKEFULNESS_AWAKE); + + sService.mOomAdjuster.updateOomAdjLocked(app2, OomAdjuster.OOM_ADJ_REASON_NONE); + assertProcStates(app2, PROCESS_STATE_BOUND_TOP, VISIBLE_APP_ADJ, + SCHED_GROUP_DEFAULT); + } + + @SuppressWarnings("GuardedBy") + @Test public void testUpdateOomAdj_UidIdle_StopService() { final ProcessRecord app1 = spy(makeDefaultProcessRecord(MOCKAPP_PID, MOCKAPP_UID, MOCKAPP_PROCESSNAME, MOCKAPP_PACKAGENAME, false)); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java index 08df4381ad62..19df5a2ddd8b 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/AbstractAccessibilityServiceConnectionTest.java @@ -89,8 +89,10 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteCallback; import android.os.RemoteException; +import android.util.Pair; import android.view.Display; import android.view.KeyEvent; +import android.view.MagnificationSpec; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityWindowInfo; import android.view.accessibility.IAccessibilityInteractionConnection; @@ -145,6 +147,8 @@ public class AbstractAccessibilityServiceConnectionTest { private static final int USER_ID = 1; private static final int USER_ID2 = 2; private static final int INTERACTION_ID = 199; + private static final Pair<float[], MagnificationSpec> FAKE_MATRIX_AND_MAG_SPEC = + new Pair<>(new float[9], new MagnificationSpec()); private static final int PID = Process.myPid(); private static final long TID = Process.myTid(); private static final int UID = Process.myUid(); @@ -188,6 +192,8 @@ public class AbstractAccessibilityServiceConnectionTest { .thenReturn(mMockFingerprintGestureDispatcher); when(mMockSystemSupport.getMagnificationProcessor()) .thenReturn(mMockMagnificationProcessor); + when(mMockSystemSupport.getWindowTransformationMatrixAndMagnificationSpec(anyInt())) + .thenReturn(FAKE_MATRIX_AND_MAG_SPEC); PowerManager powerManager = new PowerManager(mMockContext, mMockIPowerManager, mMockIThermalService, mHandler); diff --git a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java index d1390c68e130..e68a8a0f3af8 100644 --- a/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java +++ b/services/tests/servicestests/src/com/android/server/am/DropboxRateLimiterTest.java @@ -49,10 +49,11 @@ public class DropboxRateLimiterTest { assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); // Different processes and tags should not get rate limited either. assertFalse(mRateLimiter.shouldRateLimit("tag", "process2").shouldRateLimit()); assertFalse(mRateLimiter.shouldRateLimit("tag2", "process").shouldRateLimit()); - // The 6th entry of the same process should be rate limited. + // The 7th entry of the same process should be rate limited. assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); } @@ -64,12 +65,13 @@ public class DropboxRateLimiterTest { assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); - // The 6th entry of the same process should be rate limited. + assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); + // The 7th entry of the same process should be rate limited. assertTrue(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); - // After 11 seconds there should be nothing left in the buffer and the same type of entry + // After 11 minutes there should be nothing left in the buffer and the same type of entry // should not get rate limited anymore. - mClock.setOffsetMillis(11000); + mClock.setOffsetMillis(11 * 60 * 1000); assertFalse(mRateLimiter.shouldRateLimit("tag", "process").shouldRateLimit()); } @@ -86,13 +88,15 @@ public class DropboxRateLimiterTest { mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); assertEquals(0, mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); + assertEquals(0, + mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); assertEquals(1, mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); assertEquals(2, mRateLimiter.shouldRateLimit("tag", "p").droppedCountSinceRateLimitActivated()); - // After 11 seconds the rate limiting buffer will be cleared and rate limiting will stop. - mClock.setOffsetMillis(11000); + // After 11 minutes the rate limiting buffer will be cleared and rate limiting will stop. + mClock.setOffsetMillis(11 * 60 * 1000); // The first call after rate limiting stops will still return the number of dropped events. assertEquals(2, diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java index b4bb04d2b1b4..77cbb3a6398c 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputControllerTest.java @@ -19,22 +19,22 @@ package com.android.server.companion.virtual; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.hardware.display.DisplayManagerInternal; import android.hardware.input.IInputManager; -import android.hardware.input.InputManager; import android.hardware.input.InputManagerInternal; import android.os.Binder; +import android.os.Handler; import android.os.IBinder; import android.platform.test.annotations.Presubmit; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; import android.view.Display; import android.view.DisplayInfo; -import androidx.test.runner.AndroidJUnit4; - import com.android.server.LocalServices; import org.junit.Before; @@ -44,7 +44,8 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @Presubmit -@RunWith(AndroidJUnit4.class) +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) public class InputControllerTest { @Mock @@ -56,13 +57,16 @@ public class InputControllerTest { @Mock private IInputManager mIInputManagerMock; + private InputManagerMockHelper mInputManagerMockHelper; private InputController mInputController; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); + mInputManagerMockHelper = new InputManagerMockHelper( + TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock); - doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); LocalServices.removeServiceForTest(InputManagerInternal.class); LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock); @@ -72,10 +76,10 @@ public class InputControllerTest { LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - InputManager.resetInstance(mIInputManagerMock); - doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString()); - doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString()); - mInputController = new InputController(new Object(), mNativeWrapperMock); + // Allow virtual devices to be created on the looper thread for testing. + final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true; + mInputController = new InputController(new Object(), mNativeWrapperMock, + new Handler(TestableLooper.get(this).getLooper()), threadVerifier); } @Test @@ -83,6 +87,7 @@ public class InputControllerTest { final IBinder deviceToken = new Binder(); mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1); + verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString()); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); doReturn(1).when(mInputManagerInternalMock).getVirtualMousePointerDisplayId(); mInputController.unregisterInputDevice(deviceToken); @@ -95,10 +100,12 @@ public class InputControllerTest { final IBinder deviceToken = new Binder(); mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken, /* displayId= */ 1); + verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString()); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); final IBinder deviceToken2 = new Binder(); mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken2, /* displayId= */ 2); + verify(mNativeWrapperMock, times(2)).openUinputMouse(eq("name"), eq(1), eq(1), anyString()); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(2)); mInputController.unregisterInputDevice(deviceToken); verify(mInputManagerInternalMock).setVirtualMousePointerDisplayId(eq(1)); diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java new file mode 100644 index 000000000000..aa2d97e38928 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/InputManagerMockHelper.java @@ -0,0 +1,101 @@ +/* + * 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.server.companion.virtual; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.notNull; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.when; + +import android.hardware.input.IInputDevicesChangedListener; +import android.hardware.input.IInputManager; +import android.hardware.input.InputManager; +import android.os.RemoteException; +import android.testing.TestableLooper; +import android.view.InputDevice; + +import org.mockito.invocation.InvocationOnMock; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.IntStream; + +/** + * A test utility class used to share the logic for setting up {@link InputManager}'s callback for + * when a virtual input device being added. + */ +class InputManagerMockHelper { + private final TestableLooper mTestableLooper; + private final InputController.NativeWrapper mNativeWrapperMock; + private final IInputManager mIInputManagerMock; + private final List<InputDevice> mDevices = new ArrayList<>(); + private IInputDevicesChangedListener mDevicesChangedListener; + + InputManagerMockHelper(TestableLooper testableLooper, + InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock) + throws Exception { + mTestableLooper = testableLooper; + mNativeWrapperMock = nativeWrapperMock; + mIInputManagerMock = iInputManagerMock; + + doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse( + anyString(), anyInt(), anyInt(), anyString()); + doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard( + anyString(), anyInt(), anyInt(), anyString()); + doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen( + anyString(), anyInt(), anyInt(), anyString(), anyInt(), anyInt()); + + doAnswer(inv -> { + mDevicesChangedListener = inv.getArgument(0); + return null; + }).when(mIInputManagerMock).registerInputDevicesChangedListener(notNull()); + when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]); + doAnswer(inv -> mDevices.get(inv.getArgument(0))) + .when(mIInputManagerMock).getInputDevice(anyInt()); + doNothing().when(mIInputManagerMock).addUniqueIdAssociation(anyString(), anyString()); + doNothing().when(mIInputManagerMock).removeUniqueIdAssociation(anyString()); + + // Set a new instance of InputManager for testing that uses the IInputManager mock as the + // interface to the server. + InputManager.resetInstance(mIInputManagerMock); + } + + private Void handleNativeOpenInputDevice(InvocationOnMock inv) { + Objects.requireNonNull(mDevicesChangedListener, + "InputController did not register an InputDevicesChangedListener."); + // We only use a subset of the fields of InputDevice in InputController. + final InputDevice device = new InputDevice(mDevices.size() /*id*/, 1 /*generation*/, 0, + inv.getArgument(0) /*name*/, inv.getArgument(1) /*vendorId*/, + inv.getArgument(2) /*productId*/, inv.getArgument(3) /*descriptor*/, + true /*isExternal*/, 0 /*sources*/, 0 /*keyboardType*/, + null /*keyCharacterMap*/, false /*hasVibrator*/, false /*hasMic*/, + false /*hasButtonUnderPad*/, false /*hasSensor*/, false /*hasBattery*/); + mDevices.add(device); + try { + mDevicesChangedListener.onInputDevicesChanged( + mDevices.stream().flatMapToInt( + d -> IntStream.of(d.getId(), d.getGeneration())).toArray()); + } catch (RemoteException ignored) { + } + // Process the device added notification. + mTestableLooper.processAllMessages(); + return null; + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 808f8c2cc626..cbb9fd7c30dd 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -54,6 +54,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.graphics.Point; import android.hardware.display.DisplayManagerInternal; +import android.hardware.input.IInputManager; import android.hardware.input.InputManagerInternal; import android.hardware.input.VirtualKeyEvent; import android.hardware.input.VirtualMouseButtonEvent; @@ -118,6 +119,7 @@ public class VirtualDeviceManagerServiceTest { private static final int FLAG_CANNOT_DISPLAY_ON_REMOTE_DEVICES = 0x00000; private Context mContext; + private InputManagerMockHelper mInputManagerMockHelper; private VirtualDeviceImpl mDeviceImpl; private InputController mInputController; private AssociationInfo mAssociationInfo; @@ -146,6 +148,8 @@ public class VirtualDeviceManagerServiceTest { private IAudioConfigChangedCallback mConfigChangedCallback; @Mock private ApplicationInfo mApplicationInfoMock; + @Mock + IInputManager mIInputManagerMock; private ArraySet<ComponentName> getBlockedActivities() { ArraySet<ComponentName> blockedActivities = new ArraySet<>(); @@ -170,13 +174,13 @@ public class VirtualDeviceManagerServiceTest { } @Before - public void setUp() { + public void setUp() throws Exception { MockitoAnnotations.initMocks(this); LocalServices.removeServiceForTest(DisplayManagerInternal.class); LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock); - doNothing().when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); + doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt()); doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt()); doNothing().when(mInputManagerInternalMock).setPointerIconVisible(anyBoolean(), anyInt()); LocalServices.removeServiceForTest(InputManagerInternal.class); @@ -199,7 +203,13 @@ public class VirtualDeviceManagerServiceTest { new Handler(TestableLooper.get(this).getLooper())); when(mContext.getSystemService(Context.POWER_SERVICE)).thenReturn(mPowerManager); - mInputController = new InputController(new Object(), mNativeWrapperMock); + mInputManagerMockHelper = new InputManagerMockHelper( + TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock); + // Allow virtual devices to be created on the looper thread for testing. + final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true; + mInputController = new InputController(new Object(), mNativeWrapperMock, + new Handler(TestableLooper.get(this).getLooper()), threadVerifier); + mAssociationInfo = new AssociationInfo(1, 0, null, MacAddress.BROADCAST_ADDRESS, "", null, true, false, 0, 0); diff --git a/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt new file mode 100644 index 000000000000..e78f0c77d6b3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/input/InputManagerServiceTests.kt @@ -0,0 +1,245 @@ +/* + * 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.server.input + +import android.content.Context +import android.content.ContextWrapper +import android.hardware.display.DisplayViewport +import android.hardware.input.InputManagerInternal +import android.os.IInputConstants +import android.os.test.TestLooper +import android.platform.test.annotations.Presubmit +import android.view.Display +import android.view.PointerIcon +import androidx.test.InstrumentationRegistry +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.never +import org.mockito.Mockito.spy +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.junit.MockitoJUnit +import java.util.concurrent.CountDownLatch +import java.util.concurrent.TimeUnit + +/** + * Tests for {@link InputManagerService}. + * + * Build/Install/Run: + * atest FrameworksServicesTests:InputManagerServiceTests + */ +@Presubmit +class InputManagerServiceTests { + + @get:Rule + val rule = MockitoJUnit.rule()!! + + @Mock + private lateinit var native: NativeInputManagerService + + @Mock + private lateinit var wmCallbacks: InputManagerService.WindowManagerCallbacks + + private lateinit var service: InputManagerService + private lateinit var localService: InputManagerInternal + private lateinit var context: Context + private lateinit var testLooper: TestLooper + + @Before + fun setup() { + context = spy(ContextWrapper(InstrumentationRegistry.getContext())) + testLooper = TestLooper() + service = + InputManagerService(object : InputManagerService.Injector(context, testLooper.looper) { + override fun getNativeService( + service: InputManagerService? + ): NativeInputManagerService { + return native + } + + override fun registerLocalService(service: InputManagerInternal?) { + localService = service!! + } + }) + assertTrue("Local service must be registered", this::localService.isInitialized) + service.setWindowManagerCallbacks(wmCallbacks) + } + + @Test + fun testPointerDisplayUpdatesWhenDisplayViewportsChanged() { + val displayId = 123 + `when`(wmCallbacks.pointerDisplayId).thenReturn(displayId) + val viewports = listOf<DisplayViewport>() + localService.setDisplayViewports(viewports) + verify(native).setDisplayViewports(any(Array<DisplayViewport>::class.java)) + verify(native).setPointerDisplayId(displayId) + + val x = 42f + val y = 314f + service.onPointerDisplayIdChanged(displayId, x, y) + testLooper.dispatchNext() + verify(wmCallbacks).notifyPointerDisplayIdChanged(displayId, x, y) + } + + @Test + fun testSetVirtualMousePointerDisplayId() { + // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked + // until the native callback happens. + var countDownLatch = CountDownLatch(1) + val overrideDisplayId = 123 + Thread { + assertTrue("Setting virtual pointer display should succeed", + localService.setVirtualMousePointerDisplayId(overrideDisplayId)) + countDownLatch.countDown() + }.start() + assertFalse("Setting virtual pointer display should block", + countDownLatch.await(100, TimeUnit.MILLISECONDS)) + + val x = 42f + val y = 314f + service.onPointerDisplayIdChanged(overrideDisplayId, x, y) + testLooper.dispatchNext() + verify(wmCallbacks).notifyPointerDisplayIdChanged(overrideDisplayId, x, y) + assertTrue("Native callback unblocks calling thread", + countDownLatch.await(100, TimeUnit.MILLISECONDS)) + verify(native).setPointerDisplayId(overrideDisplayId) + + // Ensure that setting the same override again succeeds immediately. + assertTrue("Setting the same virtual mouse pointer displayId again should succeed", + localService.setVirtualMousePointerDisplayId(overrideDisplayId)) + + // Ensure that we did not query WM for the pointerDisplayId when setting the override + verify(wmCallbacks, never()).pointerDisplayId + + // Unset the virtual mouse pointer displayId, and ensure that we query WM for the new + // pointer displayId and the calling thread is blocked until the native callback happens. + countDownLatch = CountDownLatch(1) + val pointerDisplayId = 42 + `when`(wmCallbacks.pointerDisplayId).thenReturn(pointerDisplayId) + Thread { + assertTrue("Unsetting virtual mouse pointer displayId should succeed", + localService.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY)) + countDownLatch.countDown() + }.start() + assertFalse("Unsetting virtual mouse pointer displayId should block", + countDownLatch.await(100, TimeUnit.MILLISECONDS)) + + service.onPointerDisplayIdChanged(pointerDisplayId, x, y) + testLooper.dispatchNext() + verify(wmCallbacks).notifyPointerDisplayIdChanged(pointerDisplayId, x, y) + assertTrue("Native callback unblocks calling thread", + countDownLatch.await(100, TimeUnit.MILLISECONDS)) + verify(native).setPointerDisplayId(pointerDisplayId) + } + + @Test + fun testSetVirtualMousePointerDisplayId_unsuccessfulUpdate() { + // Set the virtual mouse pointer displayId, and ensure that the calling thread is blocked + // until the native callback happens. + val countDownLatch = CountDownLatch(1) + val overrideDisplayId = 123 + Thread { + assertFalse("Setting virtual pointer display should be unsuccessful", + localService.setVirtualMousePointerDisplayId(overrideDisplayId)) + countDownLatch.countDown() + }.start() + assertFalse("Setting virtual pointer display should block", + countDownLatch.await(100, TimeUnit.MILLISECONDS)) + + val x = 42f + val y = 314f + // Assume the native callback updates the pointerDisplayId to the incorrect value. + service.onPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y) + testLooper.dispatchNext() + verify(wmCallbacks).notifyPointerDisplayIdChanged(Display.INVALID_DISPLAY, x, y) + assertTrue("Native callback unblocks calling thread", + countDownLatch.await(100, TimeUnit.MILLISECONDS)) + verify(native).setPointerDisplayId(overrideDisplayId) + } + + @Test + fun testSetVirtualMousePointerDisplayId_competingRequests() { + val firstRequestSyncLatch = CountDownLatch(1) + doAnswer { + firstRequestSyncLatch.countDown() + }.`when`(native).setPointerDisplayId(anyInt()) + + val firstRequestLatch = CountDownLatch(1) + val firstOverride = 123 + Thread { + assertFalse("Setting virtual pointer display from thread 1 should be unsuccessful", + localService.setVirtualMousePointerDisplayId(firstOverride)) + firstRequestLatch.countDown() + }.start() + assertFalse("Setting virtual pointer display should block", + firstRequestLatch.await(100, TimeUnit.MILLISECONDS)) + + assertTrue("Wait for first thread's request should succeed", + firstRequestSyncLatch.await(100, TimeUnit.MILLISECONDS)) + + val secondRequestLatch = CountDownLatch(1) + val secondOverride = 42 + Thread { + assertTrue("Setting virtual mouse pointer from thread 2 should be successful", + localService.setVirtualMousePointerDisplayId(secondOverride)) + secondRequestLatch.countDown() + }.start() + assertFalse("Setting virtual mouse pointer should block", + secondRequestLatch.await(100, TimeUnit.MILLISECONDS)) + + val x = 42f + val y = 314f + // Assume the native callback updates directly to the second request. + service.onPointerDisplayIdChanged(secondOverride, x, y) + testLooper.dispatchNext() + verify(wmCallbacks).notifyPointerDisplayIdChanged(secondOverride, x, y) + assertTrue("Native callback unblocks first thread", + firstRequestLatch.await(100, TimeUnit.MILLISECONDS)) + assertTrue("Native callback unblocks second thread", + secondRequestLatch.await(100, TimeUnit.MILLISECONDS)) + verify(native, times(2)).setPointerDisplayId(anyInt()) + } + + @Test + fun onDisplayRemoved_resetAllAdditionalInputProperties() { + localService.setVirtualMousePointerDisplayId(10) + localService.setPointerIconVisible(false, 10) + verify(native).setPointerIconType(eq(PointerIcon.TYPE_NULL)) + localService.setPointerAcceleration(5f, 10) + verify(native).setPointerAcceleration(eq(5f)) + + service.onDisplayRemoved(10) + verify(native).displayRemoved(eq(10)) + verify(native).setPointerIconType(eq(PointerIcon.TYPE_NOT_SPECIFIED)) + verify(native).setPointerAcceleration( + eq(IInputConstants.DEFAULT_POINTER_ACCELERATION.toFloat())) + + localService.setVirtualMousePointerDisplayId(10) + verify(native).setPointerDisplayId(eq(10)) + verifyNoMoreInteractions(native) + } +} diff --git a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java index a227cd3c6f5c..035249e32d74 100644 --- a/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/locksettings/recoverablekeystore/RecoverableKeyStoreManagerTest.java @@ -70,6 +70,7 @@ import com.google.common.collect.ImmutableMap; import org.junit.After; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; @@ -516,6 +517,7 @@ public class RecoverableKeyStoreManagerTest { } } + @Ignore("Causing breakages so ignoring to resolve, b/231667368") @Test public void initRecoveryService_alwaysUpdatesCertsWhenTestRootCertIsUsed() throws Exception { int uid = Binder.getCallingUid(); @@ -539,6 +541,7 @@ public class RecoverableKeyStoreManagerTest { testRootCertAlias)).isEqualTo(TestData.getInsecureCertPathForEndpoint2()); } + @Ignore("Causing breakages so ignoring to resolve, b/231667368") @Test public void initRecoveryService_updatesCertsIndependentlyForDifferentRoots() throws Exception { int uid = Binder.getCallingUid(); diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java index 40943774c0af..fdf9354747a0 100644 --- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java @@ -1975,7 +1975,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (si == null) { return null; } - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); return new File(si.getBitmapPath()).getName(); } @@ -1984,7 +1984,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { if (si == null) { return null; } - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); return new File(si.getBitmapPath()).getAbsolutePath(); } @@ -2139,7 +2139,7 @@ public abstract class BaseShortcutManagerTest extends InstrumentationTestCase { } protected boolean bitmapDirectoryExists(String packageName, int userId) { - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); final File path = new File(mService.getUserBitmapFilePath(userId), packageName); return path.isDirectory(); } diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java index 411b52155abb..867890f938ba 100644 --- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java +++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java @@ -1040,7 +1040,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { dumpsysOnLogcat(); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); // Check files and directories. // Package 3 has no bitmaps, so we don't create a directory. assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2); @@ -1096,7 +1096,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "3").createNewFile(); makeFile(mService.getUserBitmapFilePath(USER_10), CALLING_PACKAGE_2, "4").createNewFile(); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3, "a.b.c", "d.e.f"); @@ -1111,7 +1111,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { // The below check is the same as above, except this time USER_0 use the CALLING_PACKAGE_3 // directory. - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); assertBitmapDirectories(USER_0, CALLING_PACKAGE_1, CALLING_PACKAGE_2, CALLING_PACKAGE_3); assertBitmapDirectories(USER_10, CALLING_PACKAGE_1, CALLING_PACKAGE_2); @@ -1390,7 +1390,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithContentUri("test_uri")) .build() ))); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconUri()); @@ -1402,13 +1402,13 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32)) .build() ))); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconResource()); assertEquals(R.drawable.black_32x32, si.getIconResourceId()); }); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); mInjectedCurrentTimeMillis += INTERVAL; // reset throttling @@ -1419,7 +1419,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { getTestContext().getResources(), R.drawable.black_64x64))) .build() ))); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconFile()); @@ -1437,7 +1437,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { getTestContext().getResources(), R.drawable.black_64x64))) .build() ))); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconFile()); @@ -1451,7 +1451,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithResource(getTestContext(), R.drawable.black_32x32)) .build() ))); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconResource()); @@ -1463,7 +1463,7 @@ public class ShortcutManagerTest1 extends BaseShortcutManagerTest { .setIcon(Icon.createWithContentUri("test_uri")) .build() ))); - mService.waitForBitmapSaves(); + mService.waitForBitmapSavesForTest(); assertWith(getCallerShortcuts()) .forShortcutWithId("s1", si -> { assertTrue(si.hasIconUri()); diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java index 20486b3e396d..8167b44ee59d 100644 --- a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigTest.java @@ -454,14 +454,14 @@ public class SystemConfigTest { + " <library \n" + " name=\"foo\"\n" + " file=\"" + mFooJar + "\"\n" - + " on-bootclasspath-before=\"Q\"\n" + + " on-bootclasspath-before=\"A\"\n" + " on-bootclasspath-since=\"W\"\n" + " />\n\n" + " </permissions>"; parseSharedLibraries(contents); assertFooIsOnlySharedLibrary(); SystemConfig.SharedLibraryEntry entry = mSysConfig.getSharedLibraries().get("foo"); - assertThat(entry.onBootclasspathBefore).isEqualTo("Q"); + assertThat(entry.onBootclasspathBefore).isEqualTo("A"); assertThat(entry.onBootclasspathSince).isEqualTo("W"); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index 348e015500fe..c0cd7a755e25 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -198,6 +198,7 @@ import com.android.internal.app.IAppOpsService; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.logging.InstanceIdSequenceFake; +import com.android.internal.messages.nano.SystemMessageProto; import com.android.internal.statusbar.NotificationVisibility; import com.android.server.DeviceIdleInternal; import com.android.server.LocalServices; @@ -303,6 +304,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { ActivityManagerInternal mAmi; @Mock private Looper mMainLooper; + @Mock + private NotificationManager mMockNm; @Mock IIntentSender pi1; @@ -405,6 +408,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { LocalServices.removeServiceForTest(PermissionPolicyInternal.class); LocalServices.addService(PermissionPolicyInternal.class, mPermissionPolicyInternal); mContext.addMockSystemService(Context.ALARM_SERVICE, mAlarmManager); + mContext.addMockSystemService(NotificationManager.class, mMockNm); doNothing().when(mContext).sendBroadcastAsUser(any(), any(), any()); @@ -7516,46 +7520,53 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testOnBubbleNotificationSuppressionChanged() throws Exception { + public void testOnBubbleMetadataFlagChanged() throws Exception { setUpPrefsForBubbles(PKG, mUid, true /* global */, BUBBLE_PREFERENCE_ALL /* app */, true /* channel */); - // Bubble notification + // Post a bubble notification NotificationRecord nr = generateMessageBubbleNotifRecord(mTestNotificationChannel, "tag"); - + // Set this so that the bubble can be suppressed + nr.getNotification().getBubbleMetadata().setFlags( + Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE); mBinderService.enqueueNotificationWithTag(PKG, PKG, nr.getSbn().getTag(), nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); waitForIdle(); - // NOT suppressed + // Check the flags Notification n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); assertFalse(n.getBubbleMetadata().isNotificationSuppressed()); + assertFalse(n.getBubbleMetadata().getAutoExpandBubble()); + assertFalse(n.getBubbleMetadata().isBubbleSuppressed()); + assertTrue(n.getBubbleMetadata().isBubbleSuppressable()); // Reset as this is called when the notif is first sent reset(mListeners); - // Test: update suppression to true - mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), true, - false); + // Test: change the flags + int flags = Notification.BubbleMetadata.FLAG_SUPPRESSABLE_BUBBLE; + flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE; + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; + flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_BUBBLE; + mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), flags); waitForIdle(); // Check n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); - assertTrue(n.getBubbleMetadata().isNotificationSuppressed()); + assertEquals(flags, n.getBubbleMetadata().getFlags()); // Reset to check again reset(mListeners); - // Test: update suppression to false - mService.mNotificationDelegate.onBubbleNotificationSuppressionChanged(nr.getKey(), false, - false); + // Test: clear flags + mService.mNotificationDelegate.onBubbleMetadataFlagChanged(nr.getKey(), 0); waitForIdle(); // Check n = mBinderService.getActiveNotifications(PKG)[0].getNotification(); - assertFalse(n.getBubbleMetadata().isNotificationSuppressed()); + assertEquals(0, n.getBubbleMetadata().getFlags()); } @Test @@ -9294,4 +9305,77 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // the notifyPostedLocked function is called twice. verify(mListeners, times(2)).notifyPostedLocked(any(), any()); } + + @Test + public void testMaybeShowReviewPermissionsNotification_unknown() { + // Set up various possible states of the settings int and confirm whether or not the + // notification is shown as expected + + // Initial state: default/unknown setting, make sure nothing happens + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN); + mService.maybeShowInitialReviewPermissionsNotification(); + verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class)); + } + + @Test + public void testMaybeShowReviewPermissionsNotification_shouldShow() { + // If state is SHOULD_SHOW, it ... should show + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); + mService.maybeShowInitialReviewPermissionsNotification(); + verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG), + eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS), + any(Notification.class)); + } + + @Test + public void testMaybeShowReviewPermissionsNotification_alreadyShown() { + // If state is either USER_INTERACTED or DISMISSED, we should not show this on boot + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED); + mService.maybeShowInitialReviewPermissionsNotification(); + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED); + mService.maybeShowInitialReviewPermissionsNotification(); + + verify(mMockNm, never()).notify(anyString(), anyInt(), any(Notification.class)); + } + + @Test + public void testMaybeShowReviewPermissionsNotification_reshown() { + // If we have re-shown the notification and the user did not subsequently interacted with + // it, then make sure we show when trying on boot + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN); + mService.maybeShowInitialReviewPermissionsNotification(); + verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG), + eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS), + any(Notification.class)); + } + + @Test + public void testRescheduledReviewPermissionsNotification() { + // when rescheduled, the notification goes through the NotificationManagerInternal service + // this call doesn't need to know anything about previously scheduled state -- if called, + // it should send the notification & write the appropriate int to Settings + mInternalService.sendReviewPermissionsNotification(); + + // Notification should be sent + verify(mMockNm, times(1)).notify(eq(NotificationManagerService.TAG), + eq(SystemMessageProto.SystemMessage.NOTE_REVIEW_NOTIFICATION_PERMISSIONS), + any(Notification.class)); + + // write STATE_RESHOWN to settings + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 63d7453450d2..6d0895935877 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -289,6 +289,11 @@ public class PreferencesHelperTest extends UiServiceTestCase { .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .setFlags(AudioAttributes.FLAG_AUDIBILITY_ENFORCED) .build(); + + // make sure that the settings for review notification permissions are unset to begin with + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN); } private ByteArrayOutputStream writeXmlAndPurge( @@ -656,6 +661,13 @@ public class PreferencesHelperTest extends UiServiceTestCase { verify(mPermissionHelper).setNotificationPermission(nMr1Expected); verify(mPermissionHelper).setNotificationPermission(oExpected); verify(mPermissionHelper).setNotificationPermission(pExpected); + + // verify that we also write a state for review_permissions_notification to eventually + // show a notification + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); } @Test @@ -738,7 +750,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } @Test - public void testReadXml_newXml_noMigration() throws Exception { + public void testReadXml_newXml_noMigration_showPermissionNotification() throws Exception { when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); @@ -786,6 +798,70 @@ public class PreferencesHelperTest extends UiServiceTestCase { compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false)); verify(mPermissionHelper, never()).setNotificationPermission(any()); + + // verify that we do, however, write a state for review_permissions_notification to + // eventually show a notification, since this XML version is older than the notification + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } + + @Test + public void testReadXml_newXml_noMigration_noPermissionNotification() throws Exception { + when(mPermissionHelper.isMigrationEnabled()).thenReturn(true); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, + mPermissionHelper, mLogger, mAppOpsManager, mStatsEventBuilderFactory); + + String xml = "<ranking version=\"4\">\n" + + "<package name=\"" + PKG_N_MR1 + "\" show_badge=\"true\">\n" + + "<channel id=\"idn\" name=\"name\" importance=\"2\"/>\n" + + "<channel id=\"miscellaneous\" name=\"Uncategorized\" />\n" + + "</package>\n" + + "<package name=\"" + PKG_O + "\" >\n" + + "<channel id=\"ido\" name=\"name2\" importance=\"2\" show_badge=\"true\"/>\n" + + "</package>\n" + + "<package name=\"" + PKG_P + "\" >\n" + + "<channel id=\"idp\" name=\"name3\" importance=\"4\" locked=\"2\" />\n" + + "</package>\n" + + "</ranking>\n"; + NotificationChannel idn = new NotificationChannel("idn", "name", IMPORTANCE_LOW); + idn.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + idn.setShowBadge(false); + NotificationChannel ido = new NotificationChannel("ido", "name2", IMPORTANCE_LOW); + ido.setShowBadge(true); + ido.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + NotificationChannel idp = new NotificationChannel("idp", "name3", IMPORTANCE_HIGH); + idp.lockFields(2); + idp.setSound(null, new AudioAttributes.Builder() + .setUsage(USAGE_NOTIFICATION) + .setContentType(CONTENT_TYPE_SONIFICATION) + .setFlags(0) + .build()); + + loadByteArrayXml(xml.getBytes(), true, USER_SYSTEM); + + assertTrue(mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); + + assertEquals(idn, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, idn.getId(), false)); + compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false)); + compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false)); + + verify(mPermissionHelper, never()).setNotificationPermission(any()); + + // this XML is new enough, we should not be attempting to show a notification or anything + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); } @Test @@ -903,7 +979,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge( PKG_N_MR1, UID_N_MR1, false, USER_SYSTEM); - String expected = "<ranking version=\"3\">\n" + String expected = "<ranking version=\"4\">\n" + "<package name=\"com.example.o\" show_badge=\"true\" " + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" " + "sent_valid_msg=\"false\" user_demote_msg_app=\"false\" uid=\"1111\">\n" @@ -984,7 +1060,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge( PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM); - String expected = "<ranking version=\"3\">\n" + String expected = "<ranking version=\"4\">\n" // Importance 0 because off in permissionhelper + "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" " + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" " @@ -1067,7 +1143,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge( PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM); - String expected = "<ranking version=\"3\">\n" + String expected = "<ranking version=\"4\">\n" // Importance 0 because off in permissionhelper + "<package name=\"com.example.o\" importance=\"0\" show_badge=\"true\" " + "app_user_locked_fields=\"0\" sent_invalid_msg=\"false\" " @@ -1121,7 +1197,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { ByteArrayOutputStream baos = writeXmlAndPurge( PKG_N_MR1, UID_N_MR1, true, USER_SYSTEM); - String expected = "<ranking version=\"3\">\n" + String expected = "<ranking version=\"4\">\n" // Packages that exist solely in permissionhelper + "<package name=\"" + PKG_P + "\" importance=\"3\" />\n" + "<package name=\"" + PKG_O + "\" importance=\"0\" />\n" diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java new file mode 100644 index 000000000000..5a4ce5da676e --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsJobServiceTest.java @@ -0,0 +1,104 @@ +/* + * 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.server.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobScheduler; +import android.testing.AndroidTestingRunner; + +import androidx.test.rule.ServiceTestRule; + +import com.android.server.LocalServices; +import com.android.server.UiServiceTestCase; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; + +@RunWith(AndroidTestingRunner.class) +public class ReviewNotificationPermissionsJobServiceTest extends UiServiceTestCase { + private ReviewNotificationPermissionsJobService mJobService; + private JobParameters mJobParams = new JobParameters(null, + ReviewNotificationPermissionsJobService.JOB_ID, null, null, null, + 0, false, false, null, null, null); + + @Captor + ArgumentCaptor<JobInfo> mJobInfoCaptor; + + @Mock + private JobScheduler mMockJobScheduler; + + @Mock + private NotificationManagerInternal mMockNotificationManagerInternal; + + @Rule + public final ServiceTestRule mServiceRule = new ServiceTestRule(); + + @Before + public void setUp() throws Exception { + mJobService = new ReviewNotificationPermissionsJobService(); + mContext.addMockSystemService(JobScheduler.class, mMockJobScheduler); + + // add NotificationManagerInternal to LocalServices + LocalServices.removeServiceForTest(NotificationManagerInternal.class); + LocalServices.addService(NotificationManagerInternal.class, + mMockNotificationManagerInternal); + } + + @Test + public void testScheduleJob() { + // if asked, the job doesn't currently exist yet + when(mMockJobScheduler.getPendingJob(anyInt())).thenReturn(null); + + final int rescheduleTimeMillis = 350; // arbitrary number + + // attempt to schedule the job + ReviewNotificationPermissionsJobService.scheduleJob(mContext, rescheduleTimeMillis); + verify(mMockJobScheduler, times(1)).schedule(mJobInfoCaptor.capture()); + + // verify various properties of the job that is passed in to the job scheduler + JobInfo jobInfo = mJobInfoCaptor.getValue(); + assertEquals(ReviewNotificationPermissionsJobService.JOB_ID, jobInfo.getId()); + assertEquals(rescheduleTimeMillis, jobInfo.getMinLatencyMillis()); + assertTrue(jobInfo.isPersisted()); // should continue after reboot + assertFalse(jobInfo.isPeriodic()); // one time + } + + @Test + public void testOnStartJob() { + // the job need not be persisted after it does its work, so it'll return + // false + assertFalse(mJobService.onStartJob(mJobParams)); + + // verify that starting the job causes the notification to be sent + verify(mMockNotificationManagerInternal).sendReviewPermissionsNotification(); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java new file mode 100644 index 000000000000..12281a742a50 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/ReviewNotificationPermissionsReceiverTest.java @@ -0,0 +1,206 @@ +/* + * 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.server.notification; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + +import android.content.Context; +import android.content.Intent; +import android.provider.Settings; +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.server.UiServiceTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class ReviewNotificationPermissionsReceiverTest extends UiServiceTestCase { + + // Simple mock class that just overrides the reschedule and cancel behavior so that it's easy + // to tell whether the receiver has sent requests to either reschedule or cancel the + // notification (or both). + private class MockReviewNotificationPermissionsReceiver + extends ReviewNotificationPermissionsReceiver { + boolean mCanceled = false; + boolean mRescheduled = false; + + @Override + protected void cancelNotification(Context context) { + mCanceled = true; + } + + @Override + protected void rescheduleNotification(Context context) { + mRescheduled = true; + } + } + + private MockReviewNotificationPermissionsReceiver mReceiver; + private Intent mIntent; + + @Before + public void setUp() { + mReceiver = new MockReviewNotificationPermissionsReceiver(); + mIntent = new Intent(); // actions will be set in test cases + } + + @Test + public void testReceive_remindMeLater_firstTime() { + // Test what happens when we receive a "remind me later" intent coming from + // a previously-not-interacted notification + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); + + // set up Intent action + mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND); + + // Upon receipt of the intent, the following things should happen: + // - notification rescheduled + // - notification explicitly canceled + // - settings state updated to indicate user has interacted + mReceiver.onReceive(mContext, mIntent); + assertTrue(mReceiver.mRescheduled); + assertTrue(mReceiver.mCanceled); + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } + + @Test + public void testReceive_remindMeLater_laterTimes() { + // Test what happens when we receive a "remind me later" intent coming from + // a previously-interacted notification that has been rescheduled + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN); + + // set up Intent action + mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_REMIND); + + // Upon receipt of the intent, the following things should still happen + // regardless of the fact that the user has interacted before: + // - notification rescheduled + // - notification explicitly canceled + // - settings state still indicate user has interacted + mReceiver.onReceive(mContext, mIntent); + assertTrue(mReceiver.mRescheduled); + assertTrue(mReceiver.mCanceled); + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } + + @Test + public void testReceive_dismiss() { + // Test that dismissing the notification does *not* reschedule the notification, + // does cancel it, and writes that it has been dismissed to settings + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); + + // set up Intent action + mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_DISMISS); + + // send intent, watch what happens + mReceiver.onReceive(mContext, mIntent); + assertFalse(mReceiver.mRescheduled); + assertTrue(mReceiver.mCanceled); + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } + + @Test + public void testReceive_notificationCanceled_firstSwipe() { + // Test the basic swipe away case: the first time the user swipes the notification + // away, it will not have been interacted with yet, so make sure it's rescheduled + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); + + // set up Intent action, would be called from notification's delete intent + mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED); + + // send intent, make sure it gets: + // - rescheduled + // - not explicitly canceled, the notification was already canceled + // - noted that it's been interacted with + mReceiver.onReceive(mContext, mIntent); + assertTrue(mReceiver.mRescheduled); + assertFalse(mReceiver.mCanceled); + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } + + @Test + public void testReceive_notificationCanceled_secondSwipe() { + // Test the swipe away case for a rescheduled notification: in this case + // it should not be rescheduled anymore + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_RESHOWN); + + // set up Intent action, would be called from notification's delete intent + mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED); + + // send intent, make sure it gets: + // - not rescheduled on the second+ swipe + // - not explicitly canceled, the notification was already canceled + // - mark as user interacted + mReceiver.onReceive(mContext, mIntent); + assertFalse(mReceiver.mRescheduled); + assertFalse(mReceiver.mCanceled); + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_USER_INTERACTED, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } + + @Test + public void testReceive_notificationCanceled_fromDismiss() { + // Test that if the notification delete intent is called due to us canceling + // the notification from the receiver, we don't do anything extra + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED); + + // set up Intent action, would be called from notification's delete intent + mIntent.setAction(NotificationManagerService.REVIEW_NOTIF_ACTION_CANCELED); + + // nothing should happen, nothing at all + mReceiver.onReceive(mContext, mIntent); + assertFalse(mReceiver.mRescheduled); + assertFalse(mReceiver.mCanceled); + assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_DISMISSED, + Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, + NotificationManagerService.REVIEW_NOTIF_STATE_UNKNOWN)); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index f59ec42a4a71..b9432753c17f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -785,6 +785,34 @@ public class ActivityStarterTests extends WindowTestsBase { } /** + * This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will task the + * adjacent task of indicated launch target into account. So the existing task will be launched + * into closer target. + */ + @Test + public void testAdjustLaunchTargetWithAdjacentTask() { + // Create adjacent tasks and put one activity under it + final Task parent = new TaskBuilder(mSupervisor).build(); + final Task adjacentParent = new TaskBuilder(mSupervisor).build(); + parent.setAdjacentTaskFragment(adjacentParent, true); + final ActivityRecord activity = new ActivityBuilder(mAtm) + .setParentTask(parent) + .setCreateTask(true).build(); + + // Launch the activity to its adjacent parent + final ActivityOptions options = ActivityOptions.makeBasic() + .setLaunchRootTask(adjacentParent.mRemoteToken.toWindowContainerToken()); + prepareStarter(FLAG_ACTIVITY_NEW_TASK, false /* mockGetRootTask */) + .setReason("testAdjustLaunchTargetWithAdjacentTask") + .setIntent(activity.intent) + .setActivityOptions(options.toBundle()) + .execute(); + + // Verify the activity will be launched into the original parent + assertTrue(activity.isDescendantOf(parent)); + } + + /** * This test ensures that {@link ActivityStarter#setTargetRootTaskIfNeeded} will * move the existing task to front if the current focused root task doesn't have running task. */ diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 49cd343ef4af..873d9f3fc023 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -306,6 +306,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { if (focus) { doReturn(window.getWindowInfo().token) .when(mWindowManagerInternal).getFocusedWindowToken(); + doReturn(window).when(mWm).getFocusedWindowLocked(); } } } diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING index bc8c639df1c1..22a6445843a4 100644 --- a/services/voiceinteraction/TEST_MAPPING +++ b/services/voiceinteraction/TEST_MAPPING @@ -5,19 +5,6 @@ "options": [ { "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - // TODO(b/225076204): Remove the following four test cases after fixing the test fail. - { - "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess" - }, - { - "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success" - }, - { - "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_createDetectorTwiceQuickly_triggerSuccess" - }, - { - "exclude-filter": "android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromMic_success" } ] }, diff --git a/telecomm/java/android/telecom/PhoneAccountHandle.java b/telecomm/java/android/telecom/PhoneAccountHandle.java index e3485deb9080..ec94f8a1829f 100644 --- a/telecomm/java/android/telecom/PhoneAccountHandle.java +++ b/telecomm/java/android/telecom/PhoneAccountHandle.java @@ -46,6 +46,14 @@ import java.util.Objects; * See {@link PhoneAccount}, {@link TelecomManager}. */ public final class PhoneAccountHandle implements Parcelable { + /** + * Expected component name of Telephony phone accounts; ONLY used to determine if we should log + * the phone account handle ID. + */ + private static final ComponentName TELEPHONY_COMPONENT_NAME = + new ComponentName("com.android.phone", + "com.android.services.telephony.TelephonyConnectionService"); + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 127403196) private final ComponentName mComponentName; @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) @@ -136,14 +144,23 @@ public final class PhoneAccountHandle implements Parcelable { @Override public String toString() { - // Note: Log.pii called for mId as it can contain personally identifying phone account - // information such as SIP account IDs. - return new StringBuilder().append(mComponentName) - .append(", ") - .append(Log.pii(mId)) - .append(", ") - .append(mUserHandle) - .toString(); + StringBuilder sb = new StringBuilder() + .append(mComponentName) + .append(", "); + + if (TELEPHONY_COMPONENT_NAME.equals(mComponentName)) { + // Telephony phone account handles are now keyed by subscription id which is not + // sensitive. + sb.append(mId); + } else { + // Note: Log.pii called for mId as it can contain personally identifying phone account + // information such as SIP account IDs. + sb.append(Log.pii(mId)); + } + sb.append(", "); + sb.append(mUserHandle); + + return sb.toString(); } @Override diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index e21301eb32af..70fe6b10ef20 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -4980,6 +4980,25 @@ public class CarrierConfigManager { KEY_PREFIX + "use_sip_uri_for_presence_subscribe_bool"; /** + * Flag indicating whether or not to use TEL URI when setting the entity uri field and + * contact element of each tuple. + * + * When {@code true}, the device sets the entity uri field and contact element to be + * TEL URI. This is done by first searching for the first TEL URI provided in + * p-associated-uri header. If there are no TEL URIs in the p-associated-uri header, we will + * convert the first SIP URI provided in the header to a TEL URI. If there are no URIs in + * the p-associated-uri header, we will then fall back to using the SIM card to generate the + * TEL URI. + * If {@code false}, the first URI provided in the p-associated-uri header is used, + * independent of the URI scheme. If there are no URIs available from p-associated-uri + * header, we will try to generate a SIP URI or TEL URI from the information provided by the + * SIM card, depending on the information available. + * @hide + */ + public static final String KEY_USE_TEL_URI_FOR_PIDF_XML_BOOL = + KEY_PREFIX + "use_tel_uri_for_pidf_xml"; + + /** * An integer key associated with the period of time in seconds the non-rcs capability * information of each contact is cached on the device. * <p> diff --git a/telephony/java/android/telephony/DataFailCause.java b/telephony/java/android/telephony/DataFailCause.java index fa6de1ad2864..ac1f376cf6b5 100644 --- a/telephony/java/android/telephony/DataFailCause.java +++ b/telephony/java/android/telephony/DataFailCause.java @@ -1673,4 +1673,9 @@ public final class DataFailCause { return UNKNOWN; } } + + /** @hide */ + public static boolean isFailCauseExisting(@DataFailureCause int failCause) { + return sFailCauseMap.containsKey(failCause); + } } diff --git a/tests/ApkVerityTest/Android.bp b/tests/ApkVerityTest/Android.bp index d4fa1dda8bdf..62e16a5b83de 100644 --- a/tests/ApkVerityTest/Android.bp +++ b/tests/ApkVerityTest/Android.bp @@ -37,8 +37,8 @@ java_test_host { "general-tests", "vts", ], - data_device_bins: [ - "block_device_writer", + target_required: [ + "block_device_writer_module", ], data: [ ":ApkVerityTestCertDer", diff --git a/tests/ApkVerityTest/block_device_writer/Android.bp b/tests/ApkVerityTest/block_device_writer/Android.bp index e5d009dc10fd..fdfa41fd4ca9 100644 --- a/tests/ApkVerityTest/block_device_writer/Android.bp +++ b/tests/ApkVerityTest/block_device_writer/Android.bp @@ -24,7 +24,12 @@ package { } cc_test { - name: "block_device_writer", + // Depending on how the test runs, the executable may be uploaded to different location. + // Before the bug in the file pusher is fixed, workaround by making the name unique. + // See b/124718249#comment12. + name: "block_device_writer_module", + stem: "block_device_writer", + srcs: ["block_device_writer.cpp"], cflags: [ "-D_FILE_OFFSET_BITS=64", @@ -37,7 +42,22 @@ cc_test { "libbase", "libutils", ], - compile_multilib: "first", + // For some reasons, cuttlefish (x86) uses x86_64 test suites for testing. Unfortunately, when + // the uploader does not pick up the executable from correct output location. The following + // workaround allows the test to: + // * upload the 32-bit exectuable for both 32 and 64 bits devices to use + // * refer to the same executable name in Java + // * no need to force the Java test to be archiecture specific. + // + // See b/145573317 for details. + multilib: { + lib32: { + suffix: "", + }, + lib64: { + suffix: "64", // not really used + }, + }, auto_gen_config: false, test_suites: [ diff --git a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java index 730daf32f20d..5c2c15b22bb0 100644 --- a/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java +++ b/tests/ApkVerityTest/block_device_writer/src/com/android/blockdevicewriter/BlockDeviceWriter.java @@ -32,7 +32,7 @@ import java.util.ArrayList; * <p>To use this class, please push block_device_writer binary to /data/local/tmp. * 1. In Android.bp, add: * <pre> - * data_device_bins: ["block_device_writer"], + * target_required: ["block_device_writer_module"], * </pre> * 2. In AndroidText.xml, add: * <pre> diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt new file mode 100644 index 000000000000..16c4c254f9e3 --- /dev/null +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeStateInitializeHelper.kt @@ -0,0 +1,34 @@ +/* + * 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.server.wm.flicker.helpers + +import android.app.Instrumentation +import android.support.test.launcherhelper.ILauncherStrategy +import android.support.test.launcherhelper.LauncherStrategyFactory +import com.android.server.wm.flicker.testapp.ActivityOptions +import com.android.server.wm.traces.common.FlickerComponentName +import com.android.server.wm.traces.parser.toFlickerComponent + +class ImeStateInitializeHelper @JvmOverloads constructor( + instr: Instrumentation, + launcherName: String = ActivityOptions.IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME, + component: FlickerComponentName = + ActivityOptions.IME_ACTIVITY_INITIALIZE_COMPONENT_NAME.toFlickerComponent(), + launcherStrategy: ILauncherStrategy = LauncherStrategyFactory + .getInstance(instr) + .launcherStrategy +) : StandardAppHelper(instr, launcherName, component, launcherStrategy)
\ No newline at end of file diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt index 964390930d54..b897ca2a9c15 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/ime/LaunchAppShowImeOnStartTest.kt @@ -28,6 +28,7 @@ import com.android.server.wm.flicker.FlickerTestParameter import com.android.server.wm.flicker.FlickerTestParameterFactory import com.android.server.wm.flicker.dsl.FlickerBuilder import com.android.server.wm.flicker.helpers.ImeAppAutoFocusHelper +import com.android.server.wm.flicker.helpers.ImeStateInitializeHelper import com.android.server.wm.flicker.helpers.setRotation import com.android.server.wm.traces.common.FlickerComponentName import org.junit.FixMethodOrder @@ -71,17 +72,20 @@ import org.junit.runners.Parameterized class LaunchAppShowImeOnStartTest(private val testSpec: FlickerTestParameter) { private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() private val testApp = ImeAppAutoFocusHelper(instrumentation, testSpec.startRotation) + private val initializeApp = ImeStateInitializeHelper(instrumentation) @FlickerBuilderProvider fun buildFlicker(): FlickerBuilder { return FlickerBuilder(instrumentation).apply { setup { eachRun { + initializeApp.launchViaIntent() this.setRotation(testSpec.startRotation) } } teardown { eachRun { + initializeApp.exit() testApp.exit() } } diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index 28422321f5b6..43aa4b151548 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -52,6 +52,16 @@ <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> + <activity android:name=".ImeStateInitializeActivity" + android:theme="@style/no_starting_window" + android:windowSoftInputMode="stateAlwaysHidden" + android:label="ImeStateInitializeActivity" + android:exported="true"> + <intent-filter> + <action android:name="android.intent.action.MAIN"/> + <category android:name="android.intent.category.LAUNCHER"/> + </intent-filter> + </activity> <activity android:name=".SeamlessRotationActivity" android:taskAffinity="com.android.server.wm.flicker.testapp.SeamlessRotationActivity" android:theme="@style/CutoutShortEdges" diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml index 589df38eec83..1d21fd56a487 100644 --- a/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml +++ b/tests/FlickerTests/test-apps/flickerapp/res/values/styles.xml @@ -42,4 +42,8 @@ <item name="android:backgroundDimEnabled">false</item> <item name="android:windowSoftInputMode">stateUnchanged</item> </style> + + <style name="no_starting_window" parent="@android:style/Theme.DeviceDefault"> + <item name="android:windowDisablePreview">true</item> + </style> </resources> diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java index e080709038ff..6cda482dd30a 100644 --- a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ActivityOptions.java @@ -37,6 +37,11 @@ public class ActivityOptions { new ComponentName(FLICKER_APP_PACKAGE, FLICKER_APP_PACKAGE + ".ImeActivity"); + public static final String IME_ACTIVITY_INITIALIZE_LAUNCHER_NAME = "ImeStateInitializeActivity"; + public static final ComponentName IME_ACTIVITY_INITIALIZE_COMPONENT_NAME = + new ComponentName(FLICKER_APP_PACKAGE, + FLICKER_APP_PACKAGE + ".ImeStateInitializeActivity"); + public static final String SIMPLE_ACTIVITY_LAUNCHER_NAME = "SimpleApp"; public static final ComponentName SIMPLE_ACTIVITY_AUTO_FOCUS_COMPONENT_NAME = new ComponentName(FLICKER_APP_PACKAGE, diff --git a/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java new file mode 100644 index 000000000000..4be79c4f7bad --- /dev/null +++ b/tests/FlickerTests/test-apps/flickerapp/src/com/android/server/wm/flicker/testapp/ImeStateInitializeActivity.java @@ -0,0 +1,50 @@ +/* + * 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.server.wm.flicker.testapp; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Bundle; +import android.view.View; +import android.view.ViewGroup; + +/** + * A nop {@link Activity} to make sure that the test starts from a deterministic state. + * + * <p>Currently this {@link Activity} makes sure the following things</p> + * <li> + * <ul>Hide the software keyboard with + * {@link android.view.WindowManager.LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}</ul> + * <ul>Make sure that the navigation bar (if supported) is rendered with {@link Color#BLACK}. + * </ul> + * </li> + */ +public class ImeStateInitializeActivity extends Activity { + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + final View view = new View(this); + view.setBackgroundColor(Color.WHITE); + view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT)); + + // Make sure that navigation bar is rendered with black (if supported). + getWindow().setNavigationBarColor(Color.BLACK); + + setContentView(view); + } +} diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt index 3c6d54d24291..ae722477a2bc 100644 --- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt +++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt @@ -29,7 +29,7 @@ import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation import androidx.test.uiautomator.UiDevice -import com.google.common.truth.Truth.assertThat +import android.trust.test.lib.wait import org.junit.Before import org.junit.Rule import org.junit.Test @@ -74,9 +74,9 @@ class TemporaryAndRenewableTrustTest { uiDevice.sleep() lockStateTrackingRule.assertLocked() + uiDevice.wakeUp() trustAgentRule.agent.grantTrust( GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} - uiDevice.wakeUp() lockStateTrackingRule.assertLocked() } @@ -98,9 +98,9 @@ class TemporaryAndRenewableTrustTest { lockStateTrackingRule.assertLocked() + uiDevice.wakeUp() trustAgentRule.agent.grantTrust( GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} - uiDevice.wakeUp() lockStateTrackingRule.assertUnlocked() } @@ -116,6 +116,7 @@ class TemporaryAndRenewableTrustTest { uiDevice.sleep() lockStateTrackingRule.assertLocked() + uiDevice.wakeUp() Log.i(TAG, "Renewing trust and unlocking") var result: GrantTrustResult? = null @@ -124,10 +125,9 @@ class TemporaryAndRenewableTrustTest { Log.i(TAG, "Callback received; status=${it.status}") result = it } - uiDevice.wakeUp() lockStateTrackingRule.assertUnlocked() - assertThat(result?.status).isEqualTo(STATUS_UNLOCKED_BY_GRANT) + wait("callback triggered") { result?.status == STATUS_UNLOCKED_BY_GRANT } } @Test @@ -141,7 +141,6 @@ class TemporaryAndRenewableTrustTest { trustAgentRule.agent.revokeTrust() await(500) uiDevice.wakeUp() - await(500) trustAgentRule.agent.grantTrust( GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {} |