diff options
92 files changed, 1220 insertions, 701 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/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/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/com/android/internal/app/ChooserActivity.java b/core/java/com/android/internal/app/ChooserActivity.java index f0a685ec4d2e..3fee914f2def 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, 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/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/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/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/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 7513e5129ade..18a721561002 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 @@ -65,7 +65,6 @@ 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); 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..7df42e04ea9b 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 @@ -109,9 +109,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 +245,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 +283,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 +298,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 +309,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 +332,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/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..bf0826158c0f 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 @@ -75,7 +75,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 +99,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 +132,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/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/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/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index d59ad9286b10..932489372872 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -278,6 +278,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(); } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index 76c1dbcaf20c..d9aa1bae3c69 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 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/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/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/keyguard/KeyguardUnlockAnimationController.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt index fb09132684eb..9aebb9d3d822 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardUnlockAnimationController.kt @@ -813,6 +813,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..7becc82edc99 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(); } @@ -989,6 +979,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 +1062,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/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/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/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImpl.java index 24660b261c51..01aa2ec9bfe6 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) { @@ -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/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/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/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java index be923a68391c..5ff316e2efec 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/LockIconViewControllerTest.java @@ -413,6 +413,7 @@ public class LockIconViewControllerTest extends SysuiTestCase { /* max enrollments per user */ 5, /* component info */ new ArrayList<>(), /* sensorType */ 3, + /* halControlsIllumination */ true, /* resetLockoutRequiresHwToken */ false, List.of(new SensorLocationInternal("" /* displayId */, (int) udfpsLocation.x, (int) udfpsLocation.y, radius))); 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..dc101f3969f8 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 notifyListeners_onSettingChange_zenMode() { + public void hideSilentNotificationsPerUserSettingWithHighPriorityParent() { + when(mKeyguardStateController.isShowing()).thenReturn(true); + 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(mKeyguardStateController.isShowing()).thenReturn(true); + 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(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/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/NotificationShadeWindowControllerImplTest.java index ddccd834f76e..26199d53a2b4 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 @@ -30,6 +30,7 @@ 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 +104,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { mNotificationShadeWindowController.setNotificationShadeView(mNotificationShadeWindowView); mNotificationShadeWindowController.attach(); + verify(mWindowManager).addView(eq(mNotificationShadeWindowView), any()); } @Test @@ -174,6 +176,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 +231,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 +299,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(()-> { 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/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/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index b4c107c1a2c1..62bb9f155c34 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; } 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..24be1b6fd701 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java +++ b/services/companion/java/com/android/server/companion/presence/CompanionDevicePresenceMonitor.java @@ -206,6 +206,15 @@ 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); + } + + /** * Implements * {@link AssociationStore.OnChangeListener#onAssociationRemoved(AssociationInfo)} */ @@ -217,9 +226,7 @@ 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 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/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/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index d8b97b82855a..e7fcc5989467 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -2563,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/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/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/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 7710a25b95fc..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; @@ -1429,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) { @@ -1440,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())); @@ -7182,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) { 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/ShortcutPackage.java b/services/core/java/com/android/server/pm/ShortcutPackage.java index fef6ce1f67b3..4df54b74bb1d 100644 --- a/services/core/java/com/android/server/pm/ShortcutPackage.java +++ b/services/core/java/com/android/server/pm/ShortcutPackage.java @@ -349,7 +349,7 @@ class ShortcutPackage extends ShortcutPackageItem { private ShortcutInfo forceDeleteShortcutInner(@NonNull String id) { final ShortcutInfo shortcut = mShortcuts.remove(id); if (shortcut != null) { - mShortcutUser.mService.removeIconLocked(shortcut); + removeIcon(shortcut); shortcut.clearFlags(ShortcutInfo.FLAG_DYNAMIC | ShortcutInfo.FLAG_PINNED | ShortcutInfo.FLAG_MANIFEST | ShortcutInfo.FLAG_CACHED_ALL); } @@ -366,7 +366,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); } @@ -972,7 +972,8 @@ class ShortcutPackage extends ShortcutPackageItem { /** * 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 +983,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) { @@ -1608,6 +1629,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) { @@ -1729,7 +1755,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()); 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/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/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/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/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index d85c40ecebe8..c0cd7a755e25 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -7520,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 |