diff options
299 files changed, 8094 insertions, 2328 deletions
diff --git a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java index 7f02cb36dfc1..2f6b6897addb 100644 --- a/apex/jobscheduler/framework/java/android/app/job/JobInfo.java +++ b/apex/jobscheduler/framework/java/android/app/job/JobInfo.java @@ -432,6 +432,7 @@ public class JobInfo implements Parcelable { @UnsupportedAppUsage private final ComponentName service; private final int constraintFlags; + private final int mPreferredConstraintFlags; private final TriggerContentUri[] triggerContentUris; private final long triggerContentUpdateDelay; private final long triggerContentMaxDelay; @@ -522,6 +523,30 @@ public class JobInfo implements Parcelable { } /** + * @hide + * @see JobInfo.Builder#setPrefersBatteryNotLow(boolean) + */ + public boolean isPreferBatteryNotLow() { + return (mPreferredConstraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0; + } + + /** + * @hide + * @see JobInfo.Builder#setPrefersCharging(boolean) + */ + public boolean isPreferCharging() { + return (mPreferredConstraintFlags & CONSTRAINT_FLAG_CHARGING) != 0; + } + + /** + * @hide + * @see JobInfo.Builder#setPrefersDeviceIdle(boolean) + */ + public boolean isPreferDeviceIdle() { + return (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0; + } + + /** * @see JobInfo.Builder#setRequiresCharging(boolean) */ public boolean isRequireCharging() { @@ -557,6 +582,13 @@ public class JobInfo implements Parcelable { } /** + * @hide + */ + public int getPreferredConstraintFlags() { + return mPreferredConstraintFlags; + } + + /** * Which content: URIs must change for the job to be scheduled. Returns null * if there are none required. * @see JobInfo.Builder#addTriggerContentUri(TriggerContentUri) @@ -800,6 +832,9 @@ public class JobInfo implements Parcelable { if (constraintFlags != j.constraintFlags) { return false; } + if (mPreferredConstraintFlags != j.mPreferredConstraintFlags) { + return false; + } if (!Arrays.equals(triggerContentUris, j.triggerContentUris)) { return false; } @@ -880,6 +915,7 @@ public class JobInfo implements Parcelable { hashCode = 31 * hashCode + service.hashCode(); } hashCode = 31 * hashCode + constraintFlags; + hashCode = 31 * hashCode + mPreferredConstraintFlags; if (triggerContentUris != null) { hashCode = 31 * hashCode + Arrays.hashCode(triggerContentUris); } @@ -922,6 +958,7 @@ public class JobInfo implements Parcelable { } service = in.readParcelable(null); constraintFlags = in.readInt(); + mPreferredConstraintFlags = in.readInt(); triggerContentUris = in.createTypedArray(TriggerContentUri.CREATOR); triggerContentUpdateDelay = in.readLong(); triggerContentMaxDelay = in.readLong(); @@ -956,6 +993,7 @@ public class JobInfo implements Parcelable { clipGrantFlags = b.mClipGrantFlags; service = b.mJobService; constraintFlags = b.mConstraintFlags; + mPreferredConstraintFlags = b.mPreferredConstraintFlags; triggerContentUris = b.mTriggerContentUris != null ? b.mTriggerContentUris.toArray(new TriggerContentUri[b.mTriggerContentUris.size()]) : null; @@ -999,6 +1037,7 @@ public class JobInfo implements Parcelable { } out.writeParcelable(service, flags); out.writeInt(constraintFlags); + out.writeInt(mPreferredConstraintFlags); out.writeTypedArray(triggerContentUris, flags); out.writeLong(triggerContentUpdateDelay); out.writeLong(triggerContentMaxDelay); @@ -1146,6 +1185,7 @@ public class JobInfo implements Parcelable { private int mFlags; // Requirements. private int mConstraintFlags; + private int mPreferredConstraintFlags; private NetworkRequest mNetworkRequest; private long mNetworkDownloadBytes = NETWORK_BYTES_UNKNOWN; private long mNetworkUploadBytes = NETWORK_BYTES_UNKNOWN; @@ -1199,6 +1239,7 @@ public class JobInfo implements Parcelable { mBias = job.getBias(); mFlags = job.getFlags(); mConstraintFlags = job.getConstraintFlags(); + mPreferredConstraintFlags = job.getPreferredConstraintFlags(); mNetworkRequest = job.getRequiredNetwork(); mNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes(); mNetworkUploadBytes = job.getEstimatedNetworkUploadBytes(); @@ -1341,9 +1382,6 @@ public class JobInfo implements Parcelable { * Calling this method will override any requirements previously defined * by {@link #setRequiredNetwork(NetworkRequest)}; you typically only * want to call one of these methods. - * <p> Starting in Android version {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, - * {@link JobScheduler} may try to shift the execution of jobs requiring - * {@link #NETWORK_TYPE_ANY} to when there is access to an un-metered network. * <p class="note"> * When your job executes in * {@link JobService#onStartJob(JobParameters)}, be sure to use the @@ -1505,10 +1543,105 @@ public class JobInfo implements Parcelable { } /** - * Specify that to run this job, the device must be charging (or be a + * Specify that this job would prefer to be run when the device's battery is not low. + * This defaults to {@code false}. + * + * <p>The system may attempt to delay this job until the device's battery is not low, + * but may choose to run it even if the device's battery is low. JobScheduler will not stop + * this job if this constraint is no longer satisfied after the job has started running. + * If this job must only run when the device's battery is not low, + * use {@link #setRequiresBatteryNotLow(boolean)} instead. + * + * <p> + * Because it doesn't make sense for a constraint to be both preferred and required, + * calling both this and {@link #setRequiresBatteryNotLow(boolean)} with {@code true} + * will result in an {@link java.lang.IllegalArgumentException} when + * {@link android.app.job.JobInfo.Builder#build()} is called. + * + * @param prefersBatteryNotLow Pass {@code true} to prefer that the device's battery level + * not be low in order to run the job. + * @return This object for method chaining + * @see JobInfo#isPreferBatteryNotLow() + * @hide + */ + @NonNull + public Builder setPrefersBatteryNotLow(boolean prefersBatteryNotLow) { + mPreferredConstraintFlags = + (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_BATTERY_NOT_LOW) + | (prefersBatteryNotLow ? CONSTRAINT_FLAG_BATTERY_NOT_LOW : 0); + return this; + } + + /** + * Specify that this job would prefer to be run when the device is charging (or be a * non-battery-powered device connected to permanent power, such as Android TV * devices). This defaults to {@code false}. * + * <p> + * The system may attempt to delay this job until the device is charging, but may + * choose to run it even if the device is not charging. JobScheduler will not stop + * this job if this constraint is no longer satisfied after the job has started running. + * If this job must only run when the device is charging, + * use {@link #setRequiresCharging(boolean)} instead. + * + * <p> + * Because it doesn't make sense for a constraint to be both preferred and required, + * calling both this and {@link #setRequiresCharging(boolean)} with {@code true} + * will result in an {@link java.lang.IllegalArgumentException} when + * {@link android.app.job.JobInfo.Builder#build()} is called. + * + * @param prefersCharging Pass {@code true} to prefer that the device be + * charging in order to run the job. + * @return This object for method chaining + * @see JobInfo#isPreferCharging() + * @hide + */ + @NonNull + public Builder setPrefersCharging(boolean prefersCharging) { + mPreferredConstraintFlags = (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_CHARGING) + | (prefersCharging ? CONSTRAINT_FLAG_CHARGING : 0); + return this; + } + + /** + * Specify that this job would prefer to be run when the device is not in active use. + * This defaults to {@code false}. + * + * <p>The system may attempt to delay this job until the device is not in active use, + * but may choose to run it even if the device is not idle. JobScheduler will not stop + * this job if this constraint is no longer satisfied after the job has started running. + * If this job must only run when the device is not in active use, + * use {@link #setRequiresDeviceIdle(boolean)} instead. + * + * <p> + * Because it doesn't make sense for a constraint to be both preferred and required, + * calling both this and {@link #setRequiresDeviceIdle(boolean)} with {@code true} + * will result in an {@link java.lang.IllegalArgumentException} when + * {@link android.app.job.JobInfo.Builder#build()} is called. + * + * <p class="note">Despite the similar naming, this job constraint is <em>not</em> + * related to the system's "device idle" or "doze" states. This constraint only + * determines whether a job is allowed to run while the device is directly in use. + * + * @param prefersDeviceIdle Pass {@code true} to prefer that the device not be in active + * use when running this job. + * @return This object for method chaining + * @see JobInfo#isRequireDeviceIdle() + * @hide + */ + @NonNull + public Builder setPrefersDeviceIdle(boolean prefersDeviceIdle) { + mPreferredConstraintFlags = (mPreferredConstraintFlags & ~CONSTRAINT_FLAG_DEVICE_IDLE) + | (prefersDeviceIdle ? CONSTRAINT_FLAG_DEVICE_IDLE : 0); + return this; + } + + /** + * Specify that to run this job, the device must be charging (or be a + * non-battery-powered device connected to permanent power, such as Android TV + * devices). This defaults to {@code false}. Setting this to {@code false} <b>DOES NOT</b> + * mean the job will only run when the device is not charging. + * * <p class="note">For purposes of running jobs, a battery-powered device * "charging" is not quite the same as simply being connected to power. If the * device is so busy that the battery is draining despite a power connection, jobs @@ -1530,7 +1663,9 @@ public class JobInfo implements Parcelable { * Specify that to run this job, the device's battery level must not be low. * This defaults to false. If true, the job will only run when the battery level * is not low, which is generally the point where the user is given a "low battery" - * warning. + * warning. Setting this to {@code false} <b>DOES NOT</b> mean the job will only run + * when the battery is low. + * * @param batteryNotLow Whether or not the device's battery level must not be low. * @see JobInfo#isRequireBatteryNotLow() */ @@ -1543,7 +1678,8 @@ public class JobInfo implements Parcelable { /** * When set {@code true}, ensure that this job will not run if the device is in active use. * The default state is {@code false}: that is, the for the job to be runnable even when - * someone is interacting with the device. + * someone is interacting with the device. Setting this to {@code false} <b>DOES NOT</b> + * mean the job will only run when the device is not idle. * * <p>This state is a loose definition provided by the system. In general, it means that * the device is not currently being used interactively, and has not been in use for some @@ -2156,6 +2292,29 @@ public class JobInfo implements Parcelable { } } + if ((constraintFlags & mPreferredConstraintFlags) != 0) { + // Something is marked as both preferred and required. Try to give a clear exception + // reason. + if ((constraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0 + && (mPreferredConstraintFlags & CONSTRAINT_FLAG_BATTERY_NOT_LOW) != 0) { + throw new IllegalArgumentException( + "battery-not-low constraint cannot be both preferred and required"); + } + if ((constraintFlags & CONSTRAINT_FLAG_CHARGING) != 0 + && (mPreferredConstraintFlags & CONSTRAINT_FLAG_CHARGING) != 0) { + throw new IllegalArgumentException( + "charging constraint cannot be both preferred and required"); + } + if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0 + && (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { + throw new IllegalArgumentException( + "device idle constraint cannot be both preferred and required"); + } + // Couldn't figure out what the overlap was. Just use a generic message. + throw new IllegalArgumentException( + "constraints cannot be both preferred and required"); + } + if (isUserInitiated) { if (hasEarlyConstraint) { throw new IllegalArgumentException("A user-initiated job cannot have a time delay"); @@ -2173,7 +2332,8 @@ public class JobInfo implements Parcelable { if (mPriority != PRIORITY_MAX) { throw new IllegalArgumentException("A user-initiated job must be max priority."); } - if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { + if ((constraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0 + || (mPreferredConstraintFlags & CONSTRAINT_FLAG_DEVICE_IDLE) != 0) { throw new IllegalArgumentException( "A user-initiated job cannot have a device-idle constraint"); } diff --git a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java index 9b64edf53d8c..f50a90248030 100644 --- a/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java +++ b/apex/jobscheduler/framework/java/com/android/server/usage/AppStandbyInternal.java @@ -225,6 +225,8 @@ public interface AppStandbyInternal { void setActiveAdminApps(Set<String> adminPkgs, int userId); + void setAdminProtectedPackages(Set<String> packageNames, int userId); + /** * @return {@code true} if the given package is an active device admin app. */ 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 056b6b913255..08810b549e62 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java @@ -483,7 +483,7 @@ public class JobSchedulerService extends com.android.server.SystemService case Constants.KEY_RUNTIME_UI_LIMIT_MS: case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR: case Constants.KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS: - case Constants.KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS: + case Constants.KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS: if (!runtimeUpdated) { mConstants.updateRuntimeConstantsLocked(); runtimeUpdated = true; @@ -583,8 +583,8 @@ public class JobSchedulerService extends com.android.server.SystemService "runtime_min_ui_data_transfer_guarantee_buffer_factor"; private static final String KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = "runtime_min_ui_data_transfer_guarantee_ms"; - private static final String KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = - "runtime_ui_data_transfer_limit_ms"; + private static final String KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = + "runtime_use_data_estimates_for_limits"; private static final String KEY_PERSIST_IN_SPLIT_FILES = "persist_in_split_files"; @@ -616,15 +616,14 @@ public class JobSchedulerService extends com.android.server.SystemService @VisibleForTesting public static final long DEFAULT_RUNTIME_MIN_EJ_GUARANTEE_MS = 3 * MINUTE_IN_MILLIS; public static final long DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS = - Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS); + Math.max(6 * HOUR_IN_MILLIS, DEFAULT_RUNTIME_MIN_GUARANTEE_MS); public static final long DEFAULT_RUNTIME_UI_LIMIT_MS = - Math.max(60 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS); + Math.max(12 * HOUR_IN_MILLIS, DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS); public static final float DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.35f; public static final long DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS, DEFAULT_RUNTIME_MIN_UI_GUARANTEE_MS); - public static final long DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = - Math.min(Long.MAX_VALUE, DEFAULT_RUNTIME_UI_LIMIT_MS); + public static final boolean DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false; static final boolean DEFAULT_PERSIST_IN_SPLIT_FILES = true; static final int DEFAULT_MAX_NUM_PERSISTED_JOB_WORK_ITEMS = 100_000; @@ -755,13 +754,17 @@ public class JobSchedulerService extends com.android.server.SystemService /** * The minimum amount of time we try to guarantee user-initiated data transfer jobs - * will run for. + * will run for. This is only considered when using data estimates to calculate + * execution limits. */ public long RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS; - /** The maximum amount of time we will let a user-initiated data transfer job run for. */ - public long RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS; + /** + * Whether to use data estimates to determine execution limits for execution limits. + */ + public boolean RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = + DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS; /** * Whether to persist jobs in split files (by UID). If false, all persisted jobs will be @@ -879,7 +882,7 @@ public class JobSchedulerService extends com.android.server.SystemService KEY_RUNTIME_MIN_UI_GUARANTEE_MS, KEY_RUNTIME_UI_LIMIT_MS, KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, - KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS); + KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS); // Make sure min runtime for regular jobs is at least 10 minutes. RUNTIME_MIN_GUARANTEE_MS = Math.max(10 * MINUTE_IN_MILLIS, @@ -913,15 +916,10 @@ public class JobSchedulerService extends com.android.server.SystemService properties.getLong( KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, DEFAULT_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS)); - // User-initiated requires RUN_USER_INITIATED_JOBS permission, so the upper limit will - // be higher than other jobs. - // Max limit should be the min guarantee and the max of other user-initiated jobs. - RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = Math.max( - RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, - Math.max(RUNTIME_UI_LIMIT_MS, - properties.getLong( - KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS, - DEFAULT_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS))); + + RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = properties.getBoolean( + KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS, + DEFAULT_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS); } private boolean updateTareSettingsLocked(@EconomyManager.EnabledMode int enabledMode) { @@ -975,8 +973,8 @@ public class JobSchedulerService extends com.android.server.SystemService RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR).println(); pw.print(KEY_RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS).println(); - pw.print(KEY_RUNTIME_UI_DATA_TRANSFER_LIMIT_MS, - RUNTIME_UI_DATA_TRANSFER_LIMIT_MS).println(); + pw.print(KEY_RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS, + RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS).println(); pw.print(KEY_PERSIST_IN_SPLIT_FILES, PERSIST_IN_SPLIT_FILES).println(); pw.print(KEY_MAX_NUM_PERSISTED_JOB_WORK_ITEMS, MAX_NUM_PERSISTED_JOB_WORK_ITEMS) @@ -3306,20 +3304,24 @@ public class JobSchedulerService extends com.android.server.SystemService if (job.shouldTreatAsUserInitiatedJob() && checkRunUserInitiatedJobsPermission( job.getSourceUid(), job.getSourcePackageName())) { - if (job.getJob().getRequiredNetwork() != null) { // UI+DT - final long estimatedTransferTimeMs = - mConnectivityController.getEstimatedTransferTimeMs(job); - if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) { - return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS; + if (job.getJob().getRequiredNetwork() != null) { + // User-initiated data transfers. + if (mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS) { + final long estimatedTransferTimeMs = + mConnectivityController.getEstimatedTransferTimeMs(job); + if (estimatedTransferTimeMs == ConnectivityController.UNKNOWN_TIME) { + return mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS; + } + // Try to give the job at least as much time as we think the transfer + // will take, but cap it at the maximum limit. + final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs + * mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR); + return Math.min(mConstants.RUNTIME_UI_LIMIT_MS, + Math.max(factoredTransferTimeMs, + mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS)); } - // Try to give the job at least as much time as we think the transfer will take, - // but cap it at the maximum limit - final long factoredTransferTimeMs = (long) (estimatedTransferTimeMs - * mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR); - return Math.min(mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS, - Math.max(factoredTransferTimeMs, - mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS - )); + return Math.max(mConstants.RUNTIME_MIN_UI_GUARANTEE_MS, + mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS); } return mConstants.RUNTIME_MIN_UI_GUARANTEE_MS; } else if (job.shouldTreatAsExpeditedJob()) { @@ -3336,13 +3338,9 @@ public class JobSchedulerService extends com.android.server.SystemService /** Returns the maximum amount of time this job could run for. */ public long getMaxJobExecutionTimeMs(JobStatus job) { synchronized (mLock) { - final boolean allowLongerJob = job.shouldTreatAsUserInitiatedJob() + if (job.shouldTreatAsUserInitiatedJob() && checkRunUserInitiatedJobsPermission( - job.getSourceUid(), job.getSourcePackageName()); - if (job.getJob().getRequiredNetwork() != null && allowLongerJob) { // UI+DT - return mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS; - } - if (allowLongerJob) { // UI with LRJ permission + job.getSourceUid(), job.getSourcePackageName())) { return mConstants.RUNTIME_UI_LIMIT_MS; } if (job.shouldTreatAsUserInitiatedJob()) { diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java index 0dcb0b2456d6..a96a4ef951ea 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java @@ -883,6 +883,15 @@ public final class JobStore { if (job.isRequireStorageNotLow()) { out.attribute(null, "storage-not-low", Boolean.toString(true)); } + if (job.isPreferBatteryNotLow()) { + out.attributeBoolean(null, "prefer-battery-not-low", true); + } + if (job.isPreferCharging()) { + out.attributeBoolean(null, "prefer-charging", true); + } + if (job.isPreferDeviceIdle()) { + out.attributeBoolean(null, "prefer-idle", true); + } out.endTag(null, XML_TAG_PARAMS_CONSTRAINTS); } @@ -1538,6 +1547,13 @@ public final class JobStore { if (val != null) { jobBuilder.setRequiresStorageNotLow(true); } + + jobBuilder.setPrefersBatteryNotLow( + parser.getAttributeBoolean(null, "prefer-battery-not-low", false)); + jobBuilder.setPrefersCharging( + parser.getAttributeBoolean(null, "prefer-charging", false)); + jobBuilder.setPrefersDeviceIdle( + parser.getAttributeBoolean(null, "prefer-idle", false)); } /** diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java index e55bda7fab02..3859d89c22cd 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java @@ -1147,10 +1147,9 @@ public final class ConnectivityController extends RestrictingController implemen final boolean changed = jobStatus.setConnectivityConstraintSatisfied(nowElapsed, satisfied); + jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null + && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)); if (jobStatus.getPreferUnmetered()) { - jobStatus.setHasAccessToUnmetered(satisfied && capabilities != null - && capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)); - jobStatus.setFlexibilityConstraintSatisfied(nowElapsed, mFlexibilityController.isFlexibilitySatisfiedLocked(jobStatus)); } diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java index 620c48d2c343..234a93c8d168 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java @@ -239,14 +239,14 @@ public final class FlexibilityController extends StateController { return !mFlexibilityEnabled || mService.getUidBias(js.getSourceUid()) == JobInfo.BIAS_TOP_APP || mService.isCurrentlyRunningLocked(js) - || getNumSatisfiedRequiredConstraintsLocked(js) + || getNumSatisfiedFlexibleConstraintsLocked(js) >= js.getNumRequiredFlexibleConstraints(); } @VisibleForTesting @GuardedBy("mLock") - int getNumSatisfiedRequiredConstraintsLocked(JobStatus js) { - return Integer.bitCount(mSatisfiedFlexibleConstraints) + int getNumSatisfiedFlexibleConstraintsLocked(JobStatus js) { + return Integer.bitCount(mSatisfiedFlexibleConstraints & js.getPreferredConstraintFlags()) + (js.getHasAccessToUnmetered() ? 1 : 0); } @@ -651,7 +651,7 @@ public final class FlexibilityController extends StateController { static final String KEY_RESCHEDULED_JOB_DEADLINE_MS = FC_CONFIG_PREFIX + "rescheduled_job_deadline_ms"; - private static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; + private static final boolean DEFAULT_FLEXIBILITY_ENABLED = true; @VisibleForTesting static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; @VisibleForTesting diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java index 0cc775870f88..17dde9098926 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java @@ -16,7 +16,6 @@ package com.android.server.job.controllers; -import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED; import static android.text.format.DateUtils.HOUR_IN_MILLIS; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; @@ -25,8 +24,6 @@ import static com.android.server.job.JobSchedulerService.NEVER_INDEX; import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX; import static com.android.server.job.JobSchedulerService.WORKING_INDEX; import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; -import static com.android.server.job.controllers.FlexibilityController.NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; -import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; import android.annotation.ElapsedRealtimeLong; import android.annotation.NonNull; @@ -115,12 +112,11 @@ public final class JobStatus { static final int CONSTRAINT_WITHIN_QUOTA = 1 << 24; // Implicit constraint static final int CONSTRAINT_PREFETCH = 1 << 23; static final int CONSTRAINT_BACKGROUND_NOT_RESTRICTED = 1 << 22; // Implicit constraint - static final int CONSTRAINT_FLEXIBLE = 1 << 21; // Implicit constraint + static final int CONSTRAINT_FLEXIBLE = 1 << 21; private static final int IMPLICIT_CONSTRAINTS = 0 | CONSTRAINT_BACKGROUND_NOT_RESTRICTED | CONSTRAINT_DEVICE_NOT_DOZING - | CONSTRAINT_FLEXIBLE | CONSTRAINT_TARE_WEALTH | CONSTRAINT_WITHIN_QUOTA; @@ -298,6 +294,7 @@ public final class JobStatus { // Constraints. final int requiredConstraints; + private final int mPreferredConstraints; private final int mRequiredConstraintsOfInterest; int satisfiedConstraints = 0; private int mSatisfiedConstraintsOfInterest = 0; @@ -618,24 +615,26 @@ public final class JobStatus { } mHasExemptedMediaUrisOnly = exemptedMediaUrisOnly; - mPreferUnmetered = job.getRequiredNetwork() != null - && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED); + mPreferredConstraints = job.getPreferredConstraintFlags(); + + // Exposing a preferredNetworkRequest API requires that we make sure that the preferred + // NetworkRequest is a subset of the required NetworkRequest. We currently don't have the + // code to ensure that, so disable this part for now. + // TODO(236261941): look into enabling flexible network constraint requests + mPreferUnmetered = false; + // && job.getRequiredNetwork() != null + // && !job.getRequiredNetwork().hasCapability(NET_CAPABILITY_NOT_METERED); - final boolean lacksSomeFlexibleConstraints = - ((~requiredConstraints) & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS) != 0 - || mPreferUnmetered; final boolean satisfiesMinWindowException = (latestRunTimeElapsedMillis - earliestRunTimeElapsedMillis) >= MIN_WINDOW_FOR_FLEXIBILITY_MS; // The first time a job is rescheduled it will not be subject to flexible constraints. // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline. - if (!isRequestedExpeditedJob() && !job.isUserInitiated() + if (mPreferredConstraints != 0 && !isRequestedExpeditedJob() && !job.isUserInitiated() && satisfiesMinWindowException - && (numFailures + numSystemStops) != 1 - && lacksSomeFlexibleConstraints) { - mNumRequiredFlexibleConstraints = - NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0); + && (numFailures + numSystemStops) != 1) { + mNumRequiredFlexibleConstraints = Integer.bitCount(mPreferredConstraints); requiredConstraints |= CONSTRAINT_FLEXIBLE; } else { mNumRequiredFlexibleConstraints = 0; @@ -1142,6 +1141,10 @@ public final class JobStatus { mInternalFlags |= flags; } + int getPreferredConstraintFlags() { + return mPreferredConstraints; + } + public int getSatisfiedConstraintFlags() { return satisfiedConstraints; } @@ -2501,6 +2504,9 @@ public final class JobStatus { pw.print("Required constraints:"); dumpConstraints(pw, requiredConstraints); pw.println(); + pw.print("Preferred constraints:"); + dumpConstraints(pw, mPreferredConstraints); + pw.println(); pw.print("Dynamic constraints:"); dumpConstraints(pw, mDynamicConstraints); pw.println(); 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 c3118ff96413..ab0a8adb5daf 100644 --- a/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java +++ b/apex/jobscheduler/service/java/com/android/server/usage/AppStandbyController.java @@ -268,6 +268,10 @@ public class AppStandbyController @GuardedBy("mActiveAdminApps") private final SparseArray<Set<String>> mActiveAdminApps = new SparseArray<>(); + /** List of admin protected packages. Can contain {@link android.os.UserHandle#USER_ALL}. */ + @GuardedBy("mAdminProtectedPackages") + private final SparseArray<Set<String>> mAdminProtectedPackages = new SparseArray<>(); + /** * Set of system apps that are headless (don't have any "front door" activities, enabled or * disabled). Presence in this map indicates that the app is a headless system app. @@ -1380,6 +1384,9 @@ public class AppStandbyController synchronized (mActiveAdminApps) { mActiveAdminApps.remove(userId); } + synchronized (mAdminProtectedPackages) { + mAdminProtectedPackages.remove(userId); + } } } @@ -1469,6 +1476,10 @@ public class AppStandbyController return STANDBY_BUCKET_EXEMPTED; } + if (isAdminProtectedPackages(packageName, userId)) { + return STANDBY_BUCKET_EXEMPTED; + } + if (isActiveNetworkScorer(packageName)) { return STANDBY_BUCKET_EXEMPTED; } @@ -1948,6 +1959,17 @@ public class AppStandbyController } } + private boolean isAdminProtectedPackages(String packageName, int userId) { + synchronized (mAdminProtectedPackages) { + if (mAdminProtectedPackages.contains(UserHandle.USER_ALL) + && mAdminProtectedPackages.get(UserHandle.USER_ALL).contains(packageName)) { + return true; + } + return mAdminProtectedPackages.contains(userId) + && mAdminProtectedPackages.get(userId).contains(packageName); + } + } + @Override public void addActiveDeviceAdmin(String adminPkg, int userId) { synchronized (mActiveAdminApps) { @@ -1972,6 +1994,17 @@ public class AppStandbyController } @Override + public void setAdminProtectedPackages(Set<String> packageNames, int userId) { + synchronized (mAdminProtectedPackages) { + if (packageNames == null || packageNames.isEmpty()) { + mAdminProtectedPackages.remove(userId); + } else { + mAdminProtectedPackages.put(userId, packageNames); + } + } + } + + @Override public void onAdminDataAvailable() { mAdminDataAvailableLatch.countDown(); } @@ -1993,6 +2026,13 @@ public class AppStandbyController } } + @VisibleForTesting + Set<String> getAdminProtectedPackagesForTest(int userId) { + synchronized (mAdminProtectedPackages) { + return mAdminProtectedPackages.get(userId); + } + } + /** * Returns {@code true} if the supplied package is the device provisioning app. Otherwise, * returns {@code false}. diff --git a/core/api/current.txt b/core/api/current.txt index 79123c7f93ae..b292956e3527 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5281,18 +5281,15 @@ package android.app { public class BroadcastOptions { method public void clearDeferralPolicy(); - method public void clearDeliveryGroupMatchingFilter(); method public void clearDeliveryGroupMatchingKey(); method public void clearDeliveryGroupPolicy(); method @NonNull public static android.app.BroadcastOptions fromBundle(@NonNull android.os.Bundle); method public int getDeferralPolicy(); - method @Nullable public android.content.IntentFilter getDeliveryGroupMatchingFilter(); method @Nullable public String getDeliveryGroupMatchingKey(); method public int getDeliveryGroupPolicy(); method public boolean isShareIdentityEnabled(); method @NonNull public static android.app.BroadcastOptions makeBasic(); method @NonNull public android.app.BroadcastOptions setDeferralPolicy(int); - method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull android.content.IntentFilter); method @NonNull public android.app.BroadcastOptions setDeliveryGroupMatchingKey(@NonNull String, @NonNull String); method @NonNull public android.app.BroadcastOptions setDeliveryGroupPolicy(int); method @NonNull public android.app.BroadcastOptions setShareIdentityEnabled(boolean); @@ -6623,6 +6620,7 @@ package android.app { ctor public Notification.MediaStyle(); ctor @Deprecated public Notification.MediaStyle(android.app.Notification.Builder); method public android.app.Notification.MediaStyle setMediaSession(android.media.session.MediaSession.Token); + method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent); method public android.app.Notification.MediaStyle setShowActionsInCompactView(int...); } @@ -7575,23 +7573,23 @@ package android.app { method public android.content.Intent getCropAndSetWallpaperIntent(android.net.Uri); method public int getDesiredMinimumHeight(); method public int getDesiredMinimumWidth(); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable(); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getDrawable(int); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable(); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable getFastDrawable(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getDrawable(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getDrawable(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getFastDrawable(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable getFastDrawable(int); method public static android.app.WallpaperManager getInstance(android.content.Context); method @Nullable public android.app.WallpaperColors getWallpaperColors(int); - method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.os.ParcelFileDescriptor getWallpaperFile(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.os.ParcelFileDescriptor getWallpaperFile(int); method public int getWallpaperId(int); method public android.app.WallpaperInfo getWallpaperInfo(); method @Nullable public android.app.WallpaperInfo getWallpaperInfo(int); method public boolean hasResourceWallpaper(@RawRes int); method public boolean isSetWallpaperAllowed(); method public boolean isWallpaperSupported(); - method @Nullable public android.graphics.drawable.Drawable peekDrawable(); - method @Nullable public android.graphics.drawable.Drawable peekDrawable(int); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable(); - method @Nullable @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public android.graphics.drawable.Drawable peekFastDrawable(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekDrawable(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekDrawable(int); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable(); + method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_EXTERNAL_STORAGE, "android.permission.READ_WALLPAPER_INTERNAL"}) public android.graphics.drawable.Drawable peekFastDrawable(int); method public void removeOnColorsChangedListener(@NonNull android.app.WallpaperManager.OnColorsChangedListener); method public void sendWallpaperCommand(android.os.IBinder, String, int, int, int, android.os.Bundle); method @RequiresPermission(android.Manifest.permission.SET_WALLPAPER) public void setBitmap(android.graphics.Bitmap) throws java.io.IOException; @@ -7894,6 +7892,7 @@ package android.app.admin { method @Deprecated public boolean isCallerApplicationRestrictionsManagingPackage(); method public boolean isCommonCriteriaModeEnabled(@Nullable android.content.ComponentName); method public boolean isComplianceAcknowledgementRequired(); + method public boolean isDeviceFinanced(); method public boolean isDeviceIdAttestationSupported(); method public boolean isDeviceOwnerApp(String); method public boolean isEphemeralUser(@NonNull android.content.ComponentName); @@ -11186,7 +11185,6 @@ package android.content { method public final String getDataScheme(int); method public final android.os.PatternMatcher getDataSchemeSpecificPart(int); method public final String getDataType(int); - method @NonNull public final android.os.PersistableBundle getExtras(); method public final int getPriority(); method public final boolean hasAction(String); method public final boolean hasCategory(String); @@ -11205,7 +11203,6 @@ package android.content { method public void readFromXml(org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public final java.util.Iterator<android.os.PatternMatcher> schemeSpecificPartsIterator(); method public final java.util.Iterator<java.lang.String> schemesIterator(); - method public final void setExtras(@NonNull android.os.PersistableBundle); method public final void setPriority(int); method public final java.util.Iterator<java.lang.String> typesIterator(); method public final void writeToParcel(android.os.Parcel, int); @@ -27347,9 +27344,13 @@ package android.media.tv { field public static final int TIME_SHIFT_STATUS_UNAVAILABLE = 2; // 0x2 field public static final int TIME_SHIFT_STATUS_UNKNOWN = 0; // 0x0 field public static final int TIME_SHIFT_STATUS_UNSUPPORTED = 1; // 0x1 + field public static final long TV_MESSAGE_GROUP_ID_NONE = -1L; // 0xffffffffffffffffL + field public static final String TV_MESSAGE_KEY_GROUP_ID = "android.media.tv.TvInputManager.group_id"; field public static final String TV_MESSAGE_KEY_RAW_DATA = "android.media.tv.TvInputManager.raw_data"; field public static final String TV_MESSAGE_KEY_STREAM_ID = "android.media.tv.TvInputManager.stream_id"; field public static final String TV_MESSAGE_KEY_SUBTYPE = "android.media.tv.TvInputManager.subtype"; + field public static final String TV_MESSAGE_SUBTYPE_CC_608E = "CTA 608-E"; + field public static final String TV_MESSAGE_SUBTYPE_WATERMARKING_A335 = "ATSC A/335"; field public static final int TV_MESSAGE_TYPE_CLOSED_CAPTION = 2; // 0x2 field public static final int TV_MESSAGE_TYPE_OTHER = 1000; // 0x3e8 field public static final int TV_MESSAGE_TYPE_WATERMARK = 1; // 0x1 @@ -51287,10 +51288,12 @@ package android.view { method public long getDownTime(); method public int getEdgeFlags(); method public long getEventTime(); + method public long getEventTimeNanos(); method public int getFlags(); method public float getHistoricalAxisValue(int, int); method public float getHistoricalAxisValue(int, int, int); method public long getHistoricalEventTime(int); + method public long getHistoricalEventTimeNanos(int); method public float getHistoricalOrientation(int); method public float getHistoricalOrientation(int, int); method public void getHistoricalPointerCoords(int, int, android.view.MotionEvent.PointerCoords); diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 0a893f05e91c..5a8ee9456f87 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -968,10 +968,6 @@ package android.app { field public static final int SEMANTIC_ACTION_MARK_CONVERSATION_AS_PRIORITY = 11; // 0xb } - public static class Notification.MediaStyle extends android.app.Notification.Style { - method @NonNull @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) public android.app.Notification.MediaStyle setRemotePlaybackInfo(@NonNull CharSequence, @DrawableRes int, @Nullable android.app.PendingIntent); - } - public static final class Notification.TvExtender implements android.app.Notification.Extender { ctor public Notification.TvExtender(); ctor public Notification.TvExtender(android.app.Notification); @@ -1261,6 +1257,7 @@ package android.app.admin { method @Nullable public CharSequence getDeviceOwnerOrganizationName(); method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_USERS) public android.os.UserHandle getDeviceOwnerUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public android.app.admin.DevicePolicyState getDevicePolicyState(); + method @Nullable @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public String getFinancedDeviceKioskRoleHolder(); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedAccessibilityServices(int); method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.QUERY_ADMIN_POLICY}) public java.util.List<java.lang.String> getPermittedInputMethodsForCurrentUser(); method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) public java.util.List<android.os.UserHandle> getPolicyManagedProfiles(@NonNull android.os.UserHandle); @@ -9984,6 +9981,7 @@ package android.net.wifi.sharedconnectivity.app { public final class HotspotNetwork implements android.os.Parcelable { method public int describeContents(); method public long getDeviceId(); + method @NonNull public android.os.Bundle getExtras(); method public int getHostNetworkType(); method @Nullable public String getHotspotBssid(); method @NonNull public java.util.Set<java.lang.Integer> getHotspotSecurityTypes(); @@ -10003,6 +10001,7 @@ package android.net.wifi.sharedconnectivity.app { method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder addHotspotSecurityType(int); method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork build(); method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setDeviceId(long); + method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHostNetworkType(int); method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHotspotBssid(@NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.HotspotNetwork.Builder setHotspotSsid(@NonNull String); @@ -10039,6 +10038,7 @@ package android.net.wifi.sharedconnectivity.app { public final class KnownNetwork implements android.os.Parcelable { method public int describeContents(); + method @NonNull public android.os.Bundle getExtras(); method @Nullable public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo getNetworkProviderInfo(); method public int getNetworkSource(); method @NonNull public java.util.Set<java.lang.Integer> getSecurityTypes(); @@ -10054,6 +10054,7 @@ package android.net.wifi.sharedconnectivity.app { ctor public KnownNetwork.Builder(); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder addSecurityType(int); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork build(); + method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkProviderInfo(@Nullable android.net.wifi.sharedconnectivity.app.NetworkProviderInfo); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setNetworkSource(int); method @NonNull public android.net.wifi.sharedconnectivity.app.KnownNetwork.Builder setSsid(@NonNull String); @@ -10085,6 +10086,7 @@ package android.net.wifi.sharedconnectivity.app { method @IntRange(from=0, to=3) public int getConnectionStrength(); method @NonNull public String getDeviceName(); method public int getDeviceType(); + method @NonNull public android.os.Bundle getExtras(); method @NonNull public String getModelName(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.net.wifi.sharedconnectivity.app.NetworkProviderInfo> CREATOR; @@ -10103,6 +10105,7 @@ package android.net.wifi.sharedconnectivity.app { method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setConnectionStrength(@IntRange(from=0, to=3) int); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceName(@NonNull String); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setDeviceType(int); + method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setExtras(@NonNull android.os.Bundle); method @NonNull public android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.Builder setModelName(@NonNull String); } @@ -13105,12 +13108,14 @@ package android.service.voice { method public int describeContents(); method public int getAudioChannel(); method @NonNull public java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams(); + method public int getBackgroundAudioPower(); method public int getConfidenceLevel(); method @NonNull public android.service.voice.DetectedPhrase getDetectedPhrase(); method @NonNull public android.os.PersistableBundle getExtras(); method public int getHotwordDurationMillis(); method public int getHotwordOffsetMillis(); method @Deprecated public int getHotwordPhraseId(); + method public static int getMaxBackgroundAudioPower(); method public static int getMaxBundleSize(); method @Deprecated public static int getMaxHotwordPhraseId(); method public static int getMaxScore(); @@ -13121,6 +13126,7 @@ package android.service.voice { method public boolean isHotwordDetectionPersonalized(); method public void writeToParcel(@NonNull android.os.Parcel, int); field public static final int AUDIO_CHANNEL_UNSET = -1; // 0xffffffff + field public static final int BACKGROUND_AUDIO_POWER_UNSET = -1; // 0xffffffff field public static final int CONFIDENCE_LEVEL_HIGH = 5; // 0x5 field public static final int CONFIDENCE_LEVEL_LOW = 1; // 0x1 field public static final int CONFIDENCE_LEVEL_LOW_MEDIUM = 2; // 0x2 @@ -13140,6 +13146,7 @@ package android.service.voice { method @NonNull public android.service.voice.HotwordDetectedResult build(); method @NonNull public android.service.voice.HotwordDetectedResult.Builder setAudioChannel(int); method @NonNull public android.service.voice.HotwordDetectedResult.Builder setAudioStreams(@NonNull java.util.List<android.service.voice.HotwordAudioStream>); + method @NonNull public android.service.voice.HotwordDetectedResult.Builder setBackgroundAudioPower(int); method @NonNull public android.service.voice.HotwordDetectedResult.Builder setConfidenceLevel(int); method @NonNull public android.service.voice.HotwordDetectedResult.Builder setDetectedPhrase(@NonNull android.service.voice.DetectedPhrase); method @NonNull public android.service.voice.HotwordDetectedResult.Builder setExtras(@NonNull android.os.PersistableBundle); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 48e7e1a538e0..fb5d189d7e52 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -476,7 +476,7 @@ package android.app { method @Nullable public android.graphics.Rect peekBitmapDimensions(int); method public void setWallpaperZoomOut(@NonNull android.os.IBinder, float); method public boolean shouldEnableWideColorGamut(); - method @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int); + method public boolean wallpaperSupportsWcg(int); } public class WindowConfiguration implements java.lang.Comparable<android.app.WindowConfiguration> android.os.Parcelable { @@ -516,6 +516,10 @@ package android.app { package android.app.admin { + public final class AccountTypePolicyKey extends android.app.admin.PolicyKey { + ctor public AccountTypePolicyKey(@NonNull String, @NonNull String); + } + public final class DeviceAdminAuthority extends android.app.admin.Authority { field @NonNull public static final android.app.admin.DeviceAdminAuthority DEVICE_ADMIN_AUTHORITY; } @@ -612,6 +616,10 @@ package android.app.admin { field @NonNull public static final android.app.admin.FlagUnion FLAG_UNION; } + public final class IntentFilterPolicyKey extends android.app.admin.PolicyKey { + ctor public IntentFilterPolicyKey(@NonNull String, @NonNull android.content.IntentFilter); + } + public final class MostRecent<V> extends android.app.admin.ResolutionMechanism<V> { ctor public MostRecent(); method public int describeContents(); @@ -627,6 +635,14 @@ package android.app.admin { field @NonNull public static final android.os.Parcelable.Creator<android.app.admin.MostRestrictive<?>> CREATOR; } + public final class PackagePermissionPolicyKey extends android.app.admin.PolicyKey { + ctor public PackagePermissionPolicyKey(@NonNull String, @NonNull String, @NonNull String); + } + + public final class PackagePolicyKey extends android.app.admin.PolicyKey { + ctor public PackagePolicyKey(@NonNull String, @NonNull String); + } + public final class PolicyState<V> implements android.os.Parcelable { method @NonNull public android.app.admin.ResolutionMechanism<V> getResolutionMechanism(); } @@ -676,6 +692,10 @@ package android.app.admin { method public int getOperation(); } + public final class UserRestrictionPolicyKey extends android.app.admin.PolicyKey { + ctor public UserRestrictionPolicyKey(@NonNull String, @NonNull String); + } + } package android.app.assist { @@ -1391,8 +1411,12 @@ package android.hardware.biometrics { package android.hardware.camera2 { public final class CameraManager { + method @NonNull public android.hardware.camera2.CameraCharacteristics getCameraCharacteristics(@NonNull String, boolean) throws android.hardware.camera2.CameraAccessException; method public String[] getCameraIdListNoLazy() throws android.hardware.camera2.CameraAccessException; + method @RequiresPermission(android.Manifest.permission.CAMERA) public void openCamera(@NonNull String, boolean, @Nullable android.os.Handler, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; method @RequiresPermission(allOf={android.Manifest.permission.SYSTEM_CAMERA, android.Manifest.permission.CAMERA}) public void openCamera(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.hardware.camera2.CameraDevice.StateCallback) throws android.hardware.camera2.CameraAccessException; + method public static boolean shouldOverrideToPortrait(@Nullable android.content.pm.PackageManager, @Nullable String); + field public static final String LANDSCAPE_TO_PORTRAIT_PROP = "camera.enable_landscape_to_portrait"; field public static final long OVERRIDE_CAMERA_LANDSCAPE_TO_PORTRAIT = 250678880L; // 0xef10e60L } diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java index a81ef18c8022..d0ce70133414 100644 --- a/core/java/android/animation/Animator.java +++ b/core/java/android/animation/Animator.java @@ -69,7 +69,7 @@ public abstract class Animator implements Cloneable { * backing field for backgroundPauseDelay property. This could be simply a hardcoded * value in AnimationHandler, but it is useful to be able to change the value in tests. */ - private static long sBackgroundPauseDelay = 10000; + private static long sBackgroundPauseDelay = 1000; /** * Sets the duration for delaying pausing animators when apps go into the background. diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 90427cb51c26..b96f8c94aaa3 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -994,6 +994,16 @@ public abstract class ActivityManagerInternal { */ public abstract void logFgsApiEnd(int apiType, int uid, int pid); + /** + * Temporarily allow foreground service started by an uid to have while-in-use permission + * for durationMs. + * + * @param uid The UID of the app that starts the foreground service. + * @param durationMs elapsedRealTime duration in milliseconds. + * @hide + */ + public abstract void tempAllowWhileInUsePermissionInFgs(int uid, long durationMs); + /** * The list of the events about the {@link android.media.projection.IMediaProjection} itself. * diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java index a5095b2741fa..d23d3cd87fdb 100644 --- a/core/java/android/app/BroadcastOptions.java +++ b/core/java/android/app/BroadcastOptions.java @@ -889,6 +889,8 @@ public class BroadcastOptions extends ComponentOptions { * <p> If neither matching key using {@link #setDeliveryGroupMatchingKey(String, String)} nor * matching filter using this API is specified, then by default * {@link Intent#filterEquals(Intent)} will be used to identify the delivery group. + * + * @hide */ @NonNull public BroadcastOptions setDeliveryGroupMatchingFilter(@NonNull IntentFilter matchingFilter) { @@ -902,6 +904,7 @@ public class BroadcastOptions extends ComponentOptions { * * @return the {@link IntentFilter} object that was previously set using * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}. + * @hide */ @Nullable public IntentFilter getDeliveryGroupMatchingFilter() { @@ -911,6 +914,8 @@ public class BroadcastOptions extends ComponentOptions { /** * Clears the {@link IntentFilter} object that was previously set using * {@link #setDeliveryGroupMatchingFilter(IntentFilter)}. + * + * @hide */ public void clearDeliveryGroupMatchingFilter() { mDeliveryGroupMatchingFilter = null; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 0e89f57c8b54..502ef0db586b 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -9163,10 +9163,7 @@ public class Notification implements Parcelable * {@code null}, in which case the output switcher will be disabled. * This intent should open an Activity or it will be ignored. * @return MediaStyle - * - * @hide */ - @SystemApi @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL) @NonNull public MediaStyle setRemotePlaybackInfo(@NonNull CharSequence deviceName, diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index ff1782456596..540342b03f1a 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -16,6 +16,11 @@ package android.app; +import static android.Manifest.permission.MANAGE_EXTERNAL_STORAGE; +import static android.Manifest.permission.READ_WALLPAPER_INTERNAL; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.os.ParcelFileDescriptor.MODE_READ_ONLY; + import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; @@ -28,6 +33,9 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; import android.annotation.UiContext; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.ContentResolver; @@ -84,6 +92,7 @@ import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; @@ -108,8 +117,26 @@ import java.util.concurrent.TimeUnit; */ @SystemService(Context.WALLPAPER_SERVICE) public class WallpaperManager { + private static String TAG = "WallpaperManager"; private static final boolean DEBUG = false; + + /** + * Trying to read the wallpaper file or bitmap in T will return + * the default wallpaper bitmap/file instead of throwing a SecurityException. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU) + static final long RETURN_DEFAULT_ON_SECURITY_EXCEPTION = 239784307L; + + /** + * In U and later, attempting to read the wallpaper file or bitmap will throw an exception, + * (except with the READ_WALLPAPER_INTERNAL permission). + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + static final long THROW_ON_SECURITY_EXCEPTION = 237508058L; + private float mWallpaperXStep = -1; private float mWallpaperYStep = -1; private static final @NonNull RectF LOCAL_COLOR_BOUNDS = @@ -585,7 +612,8 @@ public class WallpaperManager { } } synchronized (this) { - if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which)) { + if (mCachedWallpaper != null && mCachedWallpaper.isValid(userId, which) && context + .checkSelfPermission(READ_WALLPAPER_INTERNAL) == PERMISSION_GRANTED) { return mCachedWallpaper.mCachedWallpaper; } mCachedWallpaper = null; @@ -596,6 +624,19 @@ public class WallpaperManager { } catch (OutOfMemoryError e) { Log.w(TAG, "Out of memory loading the current wallpaper: " + e); } catch (SecurityException e) { + /* + * Apps with target SDK <= S can still access the wallpaper through + * READ_EXTERNAL_STORAGE. In T however, app that previously had access to the + * wallpaper via READ_EXTERNAL_STORAGE will get a SecurityException here. + * Thus, in T specifically, return the default wallpaper instead of crashing. + */ + if (CompatChanges.isChangeEnabled(RETURN_DEFAULT_ON_SECURITY_EXCEPTION) + && !CompatChanges.isChangeEnabled(THROW_ON_SECURITY_EXCEPTION)) { + Log.w(TAG, "No permission to access wallpaper, returning default" + + " wallpaper to avoid crashing legacy app."); + return getDefaultWallpaper(context, FLAG_SYSTEM); + } + if (context.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { Log.w(TAG, "No permission to access wallpaper, suppressing" + " exception to avoid crashing legacy app."); @@ -808,6 +849,18 @@ public class WallpaperManager { } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default system wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Retrieve the current system wallpaper; if * no wallpaper is set, the system built-in static wallpaper is returned. * This is returned as an @@ -821,14 +874,28 @@ public class WallpaperManager { * @return Returns a Drawable object that will draw the system wallpaper, * or {@code null} if no system wallpaper exists or if the calling application * is not able to access the wallpaper. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getDrawable() { return getDrawable(FLAG_SYSTEM); } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default system wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Retrieve the requested wallpaper; if * no wallpaper is set, the requested built-in static wallpaper is returned. * This is returned as an @@ -844,9 +911,11 @@ public class WallpaperManager { * @return Returns a Drawable object that will draw the requested wallpaper, * or {@code null} if the requested wallpaper does not exist or if the calling application * is not able to access the wallpaper. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); @@ -1069,6 +1138,18 @@ public class WallpaperManager { } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default system wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Retrieve the current system wallpaper; if there is no wallpaper set, * a null pointer is returned. This is returned as an * abstract Drawable that you can install in a View to display whatever @@ -1076,13 +1157,28 @@ public class WallpaperManager { * * @return Returns a Drawable object that will draw the wallpaper or a * null pointer if wallpaper is unset. + * + * @throws SecurityException as described in the note */ @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekDrawable() { return peekDrawable(FLAG_SYSTEM); } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default system wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Retrieve the requested wallpaper; if there is no wallpaper set, * a null pointer is returned. This is returned as an * abstract Drawable that you can install in a View to display whatever @@ -1092,11 +1188,14 @@ public class WallpaperManager { * IllegalArgumentException if an invalid wallpaper is requested. * @return Returns a Drawable object that will draw the wallpaper or a null pointer if * wallpaper is unset. + * + * @throws SecurityException as described in the note */ @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); if (bm != null) { Drawable dr = new BitmapDrawable(mContext.getResources(), bm); dr.setDither(false); @@ -1106,6 +1205,18 @@ public class WallpaperManager { } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Like {@link #getDrawable()}, but the returned Drawable has a number * of limitations to reduce its overhead as much as possible. It will * never scale the wallpaper (only centering it if the requested bounds @@ -1117,14 +1228,28 @@ public class WallpaperManager { * the same density as the screen (not in density compatibility mode). * * @return Returns a Drawable object that will draw the wallpaper. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getFastDrawable() { return getFastDrawable(FLAG_SYSTEM); } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default system wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Like {@link #getDrawable(int)}, but the returned Drawable has a number * of limitations to reduce its overhead as much as possible. It will * never scale the wallpaper (only centering it if the requested bounds @@ -1138,9 +1263,11 @@ public class WallpaperManager { * @param which The {@code FLAG_*} identifier of a valid wallpaper type. Throws * IllegalArgumentException if an invalid wallpaper is requested. * @return Returns a Drawable object that will draw the wallpaper. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable getFastDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); @@ -1151,19 +1278,45 @@ public class WallpaperManager { } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default system wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Like {@link #getFastDrawable()}, but if there is no wallpaper set, * a null pointer is returned. * * @return Returns an optimized Drawable object that will draw the * wallpaper or a null pointer if these is none. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekFastDrawable() { return peekFastDrawable(FLAG_SYSTEM); } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default system wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Like {@link #getFastDrawable()}, but if there is no wallpaper set, * a null pointer is returned. * @@ -1171,12 +1324,14 @@ public class WallpaperManager { * IllegalArgumentException if an invalid wallpaper is requested. * @return Returns an optimized Drawable object that will draw the * wallpaper or a null pointer if these is none. + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public Drawable peekFastDrawable(@SetWallpaperFlags int which) { final ColorManagementProxy cmProxy = getColorManagementProxy(); - Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false, which, cmProxy); + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true, which, cmProxy); if (bm != null) { return new FastBitmapDrawable(bm); } @@ -1194,7 +1349,6 @@ public class WallpaperManager { * @hide */ @TestApi - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) public boolean wallpaperSupportsWcg(int which) { if (!shouldEnableWideColorGamut()) { return false; @@ -1295,6 +1449,18 @@ public class WallpaperManager { } /** + * <strong> Important note: </strong> + * <ul> + * <li>Up to version S, this method requires the + * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} permission.</li> + * <li>Starting in T, directly accessing the wallpaper is not possible anymore, + * instead the default system wallpaper is returned + * (some versions of T may throw a {@code SecurityException}).</li> + * <li>From version U, this method should not be used + * and will always throw a @code SecurityException}.</li> + * </ul> + * <br> + * * Get an open, readable file descriptor to the given wallpaper image file. * The caller is responsible for closing the file descriptor when done ingesting the file. * @@ -1305,14 +1471,17 @@ public class WallpaperManager { * @param which The wallpaper whose image file is to be retrieved. Must be a single * defined kind of wallpaper, either {@link #FLAG_SYSTEM} or * {@link #FLAG_LOCK}. - * @return An open, readable file desriptor to the requested wallpaper image file; + * @return An open, readable file descriptor to the requested wallpaper image file; * or {@code null} if no such wallpaper is configured or if the calling app does * not have permission to read the current wallpaper. * * @see #FLAG_LOCK * @see #FLAG_SYSTEM + * + * @throws SecurityException as described in the note */ - @RequiresPermission(android.Manifest.permission.READ_EXTERNAL_STORAGE) + @Nullable + @RequiresPermission(anyOf = {MANAGE_EXTERNAL_STORAGE, READ_WALLPAPER_INTERNAL}) public ParcelFileDescriptor getWallpaperFile(@SetWallpaperFlags int which) { return getWallpaperFile(which, mContext.getUserId()); } @@ -1475,13 +1644,18 @@ public class WallpaperManager { } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } catch (SecurityException e) { + if (CompatChanges.isChangeEnabled(RETURN_DEFAULT_ON_SECURITY_EXCEPTION) + && !CompatChanges.isChangeEnabled(THROW_ON_SECURITY_EXCEPTION)) { + Log.w(TAG, "No permission to access wallpaper, returning default" + + " wallpaper file to avoid crashing legacy app."); + return getDefaultSystemWallpaperFile(); + } if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.O_MR1) { Log.w(TAG, "No permission to access wallpaper, suppressing" + " exception to avoid crashing legacy app."); return null; - } else { - throw e; } + throw e; } } } @@ -2586,6 +2760,24 @@ public class WallpaperManager { return null; } + /** + * util used in T to return a default system wallpaper file + * when third party apps attempt to read the wallpaper with {@link #getWallpaperFile} + */ + private static ParcelFileDescriptor getDefaultSystemWallpaperFile() { + for (String path: getDefaultSystemWallpaperPaths()) { + File file = new File(path); + if (file.exists()) { + try { + return ParcelFileDescriptor.open(file, MODE_READ_ONLY); + } catch (FileNotFoundException e) { + // continue; default wallpaper file not found on this path + } + } + } + return null; + } + private static InputStream getWallpaperInputStream(String path) { if (!TextUtils.isEmpty(path)) { final File file = new File(path); @@ -2600,6 +2792,14 @@ public class WallpaperManager { return null; } + /** + * @return a list of paths to the system default wallpapers, in order of priority: + * if the file exists for the first path of this list, the first path should be used. + */ + private static List<String> getDefaultSystemWallpaperPaths() { + return List.of(SystemProperties.get(PROP_WALLPAPER), getCmfWallpaperPath()); + } + private static String getCmfWallpaperPath() { return Environment.getProductDirectory() + WALLPAPER_CMF_PATH + "default_wallpaper_" + VALUE_CMF_COLOR; diff --git a/core/java/android/app/admin/AccountTypePolicyKey.java b/core/java/android/app/admin/AccountTypePolicyKey.java index 6417cd47b504..9e376a7e2bee 100644 --- a/core/java/android/app/admin/AccountTypePolicyKey.java +++ b/core/java/android/app/admin/AccountTypePolicyKey.java @@ -23,6 +23,7 @@ import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Bundle; import android.os.Parcel; @@ -49,6 +50,7 @@ public final class AccountTypePolicyKey extends PolicyKey { /** * @hide */ + @TestApi public AccountTypePolicyKey(@NonNull String key, @NonNull String accountType) { super(key); mAccountType = Objects.requireNonNull((accountType)); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 6bbbfe1ef4b0..924a7c659b08 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -15840,9 +15840,8 @@ public class DevicePolicyManager { * Called by a device owner or a profile owner or holder of the permission * {@link android.Manifest.permission#MANAGE_DEVICE_POLICY_APPS_CONTROL} to disable user * control over apps. User will not be able to clear app data or force-stop packages. When - * called by a device owner, applies to all users on the device. Starting from Android 13, - * packages with user control disabled are exempted from being put in the "restricted" App - * Standby Bucket. + * called by a device owner, applies to all users on the device. Packages with user control + * disabled are exempted from App Standby Buckets. * * @param admin Which {@link DeviceAdminReceiver} this request is associated with. Null if the * caller is not a device admin. @@ -16902,4 +16901,55 @@ public class DevicePolicyManager { } return false; } + + /** + * Returns {@code true} if this device is marked as a financed device. + * + * <p>A financed device can be entered into lock task mode (see {@link #setLockTaskPackages}) + * by the holder of the role {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}. + * If this occurs, Device Owners and Profile Owners that have set lock task packages or + * features, or that attempt to set lock task packages or features, will receive a callback + * indicating that it could not be set. See {@link PolicyUpdateReceiver#onPolicyChanged} and + * {@link PolicyUpdateReceiver#onPolicySetResult}. + * + * <p>To be informed of changes to this status you can subscribe to the broadcast + * {@link ACTION_DEVICE_FINANCING_STATE_CHANGED}. + * + * @throws SecurityException if the caller is not a device owner, profile owner of an + * organization-owned managed profile, profile owner on the primary user or holder of one of the + * following roles: {@link android.app.role.RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT}, + * android.app.role.RoleManager.ROLE_SYSTEM_SUPERVISION. + */ + public boolean isDeviceFinanced() { + if (mService != null) { + try { + return mService.isDeviceFinanced(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return false; + } + + /** + * Returns the package name of the application holding the role: + * {@link android.app.role.RoleManager#ROLE_FINANCED_DEVICE_KIOSK}. + * + * @return the package name of the application holding the role or {@code null} if the role is + * not held by any applications. + * @hide + */ + @SystemApi + @RequiresPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + @Nullable + public String getFinancedDeviceKioskRoleHolder() { + if (mService != null) { + try { + return mService.getFinancedDeviceKioskRoleHolder(mContext.getPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + return null; + } } diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index 51aff9ef71ba..e202ac2c9245 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -605,4 +605,7 @@ interface IDevicePolicyManager { void setOverrideKeepProfilesRunning(boolean enabled); boolean triggerDevicePolicyEngineMigration(boolean forceMigration); + + boolean isDeviceFinanced(String callerPackageName); + String getFinancedDeviceKioskRoleHolder(String callerPackageName); } diff --git a/core/java/android/app/admin/IntentFilterPolicyKey.java b/core/java/android/app/admin/IntentFilterPolicyKey.java index b0af4cd77749..30aad965c008 100644 --- a/core/java/android/app/admin/IntentFilterPolicyKey.java +++ b/core/java/android/app/admin/IntentFilterPolicyKey.java @@ -23,6 +23,7 @@ import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.content.IntentFilter; import android.os.Bundle; import android.os.Parcel; @@ -49,6 +50,7 @@ public final class IntentFilterPolicyKey extends PolicyKey { /** * @hide */ + @TestApi public IntentFilterPolicyKey(@NonNull String identifier, @NonNull IntentFilter filter) { super(identifier); mFilter = Objects.requireNonNull(filter); diff --git a/core/java/android/app/admin/PackagePermissionPolicyKey.java b/core/java/android/app/admin/PackagePermissionPolicyKey.java index 08c4224021f1..7fd514cebd7c 100644 --- a/core/java/android/app/admin/PackagePermissionPolicyKey.java +++ b/core/java/android/app/admin/PackagePermissionPolicyKey.java @@ -24,6 +24,7 @@ import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -53,6 +54,7 @@ public final class PackagePermissionPolicyKey extends PolicyKey { /** * @hide */ + @TestApi public PackagePermissionPolicyKey(@NonNull String identifier, @NonNull String packageName, @NonNull String permissionName) { super(identifier); diff --git a/core/java/android/app/admin/PackagePolicyKey.java b/core/java/android/app/admin/PackagePolicyKey.java index b2a8d5d24b84..2ab00bc3146c 100644 --- a/core/java/android/app/admin/PackagePolicyKey.java +++ b/core/java/android/app/admin/PackagePolicyKey.java @@ -23,6 +23,7 @@ import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -50,6 +51,7 @@ public final class PackagePolicyKey extends PolicyKey { /** * @hide */ + @TestApi public PackagePolicyKey(@NonNull String key, @NonNull String packageName) { super(key); mPackageName = Objects.requireNonNull((packageName)); diff --git a/core/java/android/app/admin/UserRestrictionPolicyKey.java b/core/java/android/app/admin/UserRestrictionPolicyKey.java index 880b58b8c321..aeb238041605 100644 --- a/core/java/android/app/admin/UserRestrictionPolicyKey.java +++ b/core/java/android/app/admin/UserRestrictionPolicyKey.java @@ -20,6 +20,7 @@ import static android.app.admin.PolicyUpdateReceiver.EXTRA_POLICY_KEY; import android.annotation.NonNull; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.os.Bundle; import android.os.Parcel; @@ -40,6 +41,7 @@ public final class UserRestrictionPolicyKey extends PolicyKey { /** * @hide */ + @TestApi public UserRestrictionPolicyKey(@NonNull String identifier, @NonNull String restriction) { super(identifier); mRestriction = Objects.requireNonNull(restriction); diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index fe10b7f8b3f4..27f6a266597c 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -31,6 +31,7 @@ import android.content.pm.LauncherActivityInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; +import android.graphics.Canvas; import android.graphics.Color; import android.graphics.PointF; import android.graphics.Rect; @@ -311,20 +312,27 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW super.onLayout(changed, left, top, right, bottom); } catch (final RuntimeException e) { Log.e(TAG, "Remote provider threw runtime exception, using error view instead.", e); - removeViewInLayout(mView); - View child = getErrorView(); - prepareView(child); - addViewInLayout(child, 0, child.getLayoutParams()); - measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); - child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, - child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); - mView = child; - mViewMode = VIEW_MODE_ERROR; + handleViewError(); } } /** + * Remove bad view and replace with error message view + */ + private void handleViewError() { + removeViewInLayout(mView); + View child = getErrorView(); + prepareView(child); + addViewInLayout(child, 0, child.getLayoutParams()); + measureChild(child, MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), + MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); + child.layout(0, 0, child.getMeasuredWidth() + mPaddingLeft + mPaddingRight, + child.getMeasuredHeight() + mPaddingTop + mPaddingBottom); + mView = child; + mViewMode = VIEW_MODE_ERROR; + } + + /** * Provide guidance about the size of this widget to the AppWidgetManager. The widths and * heights should correspond to the full area the AppWidgetHostView is given. Padding added by * the framework will be accounted for automatically. This information gets embedded into the @@ -953,4 +961,15 @@ public class AppWidgetHostView extends FrameLayout implements AppWidgetHost.AppW reapplyLastRemoteViews(); } } + + @Override + protected void dispatchDraw(@NonNull Canvas canvas) { + try { + super.dispatchDraw(canvas); + } catch (Exception e) { + // Catch draw exceptions that may be caused by RemoteViews + Log.e(TAG, "Drawing view failed: " + e); + post(this::handleViewError); + } + } } diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 667ec7ecfc5f..df8da246c976 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -5914,6 +5914,7 @@ public class Intent implements Parcelable, Cloneable { /** * A Parcelable[] of {@link ChooserAction} objects to provide the Android Sharesheet with * app-specific actions to be presented to the user when invoking {@link #ACTION_CHOOSER}. + * You can provide as many as five custom actions. */ public static final String EXTRA_CHOOSER_CUSTOM_ACTIONS = "android.intent.extra.CHOOSER_CUSTOM_ACTIONS"; diff --git a/core/java/android/content/IntentFilter.java b/core/java/android/content/IntentFilter.java index 5928a505d06b..6ff42711ef1a 100644 --- a/core/java/android/content/IntentFilter.java +++ b/core/java/android/content/IntentFilter.java @@ -2204,6 +2204,7 @@ public class IntentFilter implements Parcelable { * <p> Subsequent calls to this method will override any previously set extras. * * @param extras The intent extras to match against. + * @hide */ public final void setExtras(@NonNull PersistableBundle extras) { mExtras = extras; @@ -2214,6 +2215,7 @@ public class IntentFilter implements Parcelable { * * @return the extras that were previously set using {@link #setExtras(PersistableBundle)} or * an empty {@link PersistableBundle} object if no extras were set. + * @hide */ public final @NonNull PersistableBundle getExtras() { return mExtras == null ? new PersistableBundle() : mExtras; diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index c95d081f5f7d..dfb9cf65a4b9 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -2550,41 +2550,15 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <ul> * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED UNSPECIFIED}</li> * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SRGB SRGB}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_SRGB LINEAR_SRGB}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_EXTENDED_SRGB EXTENDED_SRGB}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_EXTENDED_SRGB LINEAR_EXTENDED_SRGB}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT709 BT709}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020 BT2020}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DCI_P3 DCI_P3}</li> * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DISPLAY_P3 DISPLAY_P3}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_NTSC_1953 NTSC_1953}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SMPTE_C SMPTE_C}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ADOBE_RGB ADOBE_RGB}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_PRO_PHOTO_RGB PRO_PHOTO_RGB}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACES ACES}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACESCG ACESCG}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_XYZ CIE_XYZ}</li> - * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_LAB CIE_LAB}</li> + * <li>{@link #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020_HLG BT2020_HLG}</li> * </ul> * * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_UNSPECIFIED * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SRGB - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_SRGB - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_EXTENDED_SRGB - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_LINEAR_EXTENDED_SRGB - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT709 - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020 - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DCI_P3 * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_DISPLAY_P3 - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_NTSC_1953 - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_SMPTE_C - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ADOBE_RGB - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_PRO_PHOTO_RGB - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACES - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_ACESCG - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_XYZ - * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_CIE_LAB + * @see #REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP_BT2020_HLG * @hide */ public static final Key<long[]> REQUEST_AVAILABLE_COLOR_SPACE_PROFILES_MAP = diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index e6b306955ef0..696873f7e5e4 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -121,6 +121,7 @@ public final class CameraManager { * System property for allowing the above * @hide */ + @TestApi public static final String LANDSCAPE_TO_PORTRAIT_PROP = "camera.enable_landscape_to_portrait"; @@ -622,6 +623,16 @@ public final class CameraManager { @NonNull public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId) throws CameraAccessException { + return getCameraCharacteristics(cameraId, shouldOverrideToPortrait(mContext)); + } + + /** + * @hide + */ + @TestApi + @NonNull + public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId, + boolean overrideToPortrait) throws CameraAccessException { CameraCharacteristics characteristics = null; if (CameraManagerGlobal.sCameraServiceDisabled) { throw new IllegalArgumentException("No cameras available on device"); @@ -635,7 +646,6 @@ public final class CameraManager { try { Size displaySize = getDisplaySize(); - boolean overrideToPortrait = shouldOverrideToPortrait(mContext); CameraMetadataNative info = cameraService.getCameraCharacteristics(cameraId, mContext.getApplicationInfo().targetSdkVersion, overrideToPortrait); try { @@ -727,7 +737,7 @@ public final class CameraManager { */ private CameraDevice openCameraDeviceUserAsync(String cameraId, CameraDevice.StateCallback callback, Executor executor, final int uid, - final int oomScoreOffset) throws CameraAccessException { + final int oomScoreOffset, boolean overrideToPortrait) throws CameraAccessException { CameraCharacteristics characteristics = getCameraCharacteristics(cameraId); CameraDevice device = null; Map<String, CameraCharacteristics> physicalIdsToChars = @@ -755,7 +765,6 @@ public final class CameraManager { "Camera service is currently unavailable"); } - boolean overrideToPortrait = shouldOverrideToPortrait(mContext); cameraUser = cameraService.connectDevice(callbacks, cameraId, mContext.getOpPackageName(), mContext.getAttributionTag(), uid, oomScoreOffset, mContext.getApplicationInfo().targetSdkVersion, @@ -891,6 +900,43 @@ public final class CameraManager { } /** + * Open a connection to a camera with the given ID. Also specify overrideToPortrait for testing. + * + * @param cameraId + * The unique identifier of the camera device to open + * @param handler + * The handler on which the callback should be invoked, or + * {@code null} to use the current thread's {@link android.os.Looper looper}. + * @param callback + * The callback which is invoked once the camera is opened + * @param overrideToPortrait + * Whether to apply the landscape to portrait override, using rotate and crop. + * + * @throws CameraAccessException if the camera is disabled by device policy, + * has been disconnected, or is being used by a higher-priority camera API client. + * + * @throws IllegalArgumentException if cameraId, the callback or the executor was null, + * or the cameraId does not match any currently or previously available + * camera device. + * + * @throws SecurityException if the application does not have permission to + * access the camera + * + * @see #getCameraIdList + * @see android.app.admin.DevicePolicyManager#setCameraDisabled + * + * @hide + */ + @TestApi + @RequiresPermission(android.Manifest.permission.CAMERA) + public void openCamera(@NonNull String cameraId, boolean overrideToPortrait, + @Nullable Handler handler, + @NonNull final CameraDevice.StateCallback callback) throws CameraAccessException { + openCameraForUid(cameraId, callback, CameraDeviceImpl.checkAndWrapHandler(handler), + USE_CALLING_UID, /*oomScoreOffset*/0, overrideToPortrait); + } + + /** * Open a connection to a camera with the given ID. * * <p>The behavior of this method matches that of @@ -994,7 +1040,8 @@ public final class CameraManager { throw new IllegalArgumentException( "oomScoreOffset < 0, cannot increase priority of camera client"); } - openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset); + openCameraForUid(cameraId, callback, executor, USE_CALLING_UID, oomScoreOffset, + shouldOverrideToPortrait(mContext)); } /** @@ -1016,7 +1063,8 @@ public final class CameraManager { */ public void openCameraForUid(@NonNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, - int clientUid, int oomScoreOffset) throws CameraAccessException { + int clientUid, int oomScoreOffset, boolean overrideToPortrait) + throws CameraAccessException { if (cameraId == null) { throw new IllegalArgumentException("cameraId was null"); @@ -1027,7 +1075,8 @@ public final class CameraManager { throw new IllegalArgumentException("No cameras available on device"); } - openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset); + openCameraDeviceUserAsync(cameraId, callback, executor, clientUid, oomScoreOffset, + overrideToPortrait); } /** @@ -1048,7 +1097,8 @@ public final class CameraManager { public void openCameraForUid(@NonNull String cameraId, @NonNull final CameraDevice.StateCallback callback, @NonNull Executor executor, int clientUid) throws CameraAccessException { - openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0); + openCameraForUid(cameraId, callback, executor, clientUid, /*oomScoreOffset*/0, + shouldOverrideToPortrait(mContext)); } /** @@ -1191,17 +1241,32 @@ public final class CameraManager { * @hide */ public static boolean shouldOverrideToPortrait(@Nullable Context context) { + PackageManager packageManager = null; + String packageName = null; + + if (context != null) { + packageManager = context.getPackageManager(); + packageName = context.getOpPackageName(); + } + + return shouldOverrideToPortrait(packageManager, packageName); + } + + /** + * @hide + */ + @TestApi + public static boolean shouldOverrideToPortrait(@Nullable PackageManager packageManager, + @Nullable String packageName) { if (!CameraManagerGlobal.sLandscapeToPortrait) { return false; } - if (context != null) { - PackageManager packageManager = context.getPackageManager(); - + if (packageManager != null && packageName != null) { try { return packageManager.getProperty( PackageManager.PROPERTY_COMPAT_OVERRIDE_LANDSCAPE_TO_PORTRAIT, - context.getOpPackageName()).getBoolean(); + packageName).getBoolean(); } catch (PackageManager.NameNotFoundException e) { // No such property } diff --git a/core/java/android/hardware/input/InputDeviceLightsManager.java b/core/java/android/hardware/input/InputDeviceLightsManager.java index 802e6dde497a..f4ee9a21c42c 100644 --- a/core/java/android/hardware/input/InputDeviceLightsManager.java +++ b/core/java/android/hardware/input/InputDeviceLightsManager.java @@ -18,6 +18,7 @@ package android.hardware.input; import android.annotation.NonNull; import android.app.ActivityThread; +import android.content.Context; import android.hardware.lights.Light; import android.hardware.lights.LightState; import android.hardware.lights.LightsManager; @@ -30,22 +31,22 @@ import java.lang.ref.Reference; import java.util.List; /** - * LightsManager manages an input device's lights {@link android.hardware.input.Light}. + * LightsManager manages an input device's lights {@link android.hardware.lights.Light} */ class InputDeviceLightsManager extends LightsManager { private static final String TAG = "InputDeviceLightsManager"; private static final boolean DEBUG = false; - private final InputManager mInputManager; + private final InputManagerGlobal mGlobal; // The input device ID. private final int mDeviceId; // Package name private final String mPackageName; - InputDeviceLightsManager(InputManager inputManager, int deviceId) { - super(ActivityThread.currentActivityThread().getSystemContext()); - mInputManager = inputManager; + InputDeviceLightsManager(Context context, int deviceId) { + super(context); + mGlobal = InputManagerGlobal.getInstance(); mDeviceId = deviceId; mPackageName = ActivityThread.currentPackageName(); } @@ -57,7 +58,7 @@ class InputDeviceLightsManager extends LightsManager { */ @Override public @NonNull List<Light> getLights() { - return mInputManager.getLights(mDeviceId); + return mGlobal.getLights(mDeviceId); } /** @@ -68,7 +69,7 @@ class InputDeviceLightsManager extends LightsManager { @Override public @NonNull LightState getLightState(@NonNull Light light) { Preconditions.checkNotNull(light); - return mInputManager.getLightState(mDeviceId, light); + return mGlobal.getLightState(mDeviceId, light); } /** @@ -77,7 +78,7 @@ class InputDeviceLightsManager extends LightsManager { @Override public @NonNull LightsSession openSession() { final LightsSession session = new InputDeviceLightsSession(); - mInputManager.openLightSession(mDeviceId, mPackageName, session.getToken()); + mGlobal.openLightSession(mDeviceId, mPackageName, session.getToken()); return session; } @@ -113,7 +114,7 @@ class InputDeviceLightsManager extends LightsManager { Preconditions.checkNotNull(request); Preconditions.checkArgument(!mClosed); - mInputManager.requestLights(mDeviceId, request, getToken()); + mGlobal.requestLights(mDeviceId, request, getToken()); } /** @@ -122,7 +123,7 @@ class InputDeviceLightsManager extends LightsManager { @Override public void close() { if (!mClosed) { - mInputManager.closeLightSession(mDeviceId, getToken()); + mGlobal.closeLightSession(mDeviceId, getToken()); mClosed = true; mCloseGuard.close(); } diff --git a/core/java/android/hardware/input/InputDeviceVibrator.java b/core/java/android/hardware/input/InputDeviceVibrator.java index ce6b52391f12..9c1826071822 100644 --- a/core/java/android/hardware/input/InputDeviceVibrator.java +++ b/core/java/android/hardware/input/InputDeviceVibrator.java @@ -45,14 +45,14 @@ final class InputDeviceVibrator extends Vibrator { private final int mDeviceId; private final VibratorInfo mVibratorInfo; private final Binder mToken; - private final InputManager mInputManager; + private final InputManagerGlobal mGlobal; @GuardedBy("mDelegates") private final ArrayMap<OnVibratorStateChangedListener, OnVibratorStateChangedListenerDelegate> mDelegates = new ArrayMap<>(); - InputDeviceVibrator(InputManager inputManager, int deviceId, int vibratorId) { - mInputManager = inputManager; + InputDeviceVibrator(int deviceId, int vibratorId) { + mGlobal = InputManagerGlobal.getInstance(); mDeviceId = deviceId; mVibratorInfo = new VibratorInfo.Builder(vibratorId) .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) @@ -93,7 +93,7 @@ final class InputDeviceVibrator extends Vibrator { @Override public boolean isVibrating() { - return mInputManager.isVibrating(mDeviceId); + return mGlobal.isVibrating(mDeviceId); } /** @@ -132,7 +132,7 @@ final class InputDeviceVibrator extends Vibrator { final OnVibratorStateChangedListenerDelegate delegate = new OnVibratorStateChangedListenerDelegate(listener, executor); - if (!mInputManager.registerVibratorStateListener(mDeviceId, delegate)) { + if (!mGlobal.registerVibratorStateListener(mDeviceId, delegate)) { Log.w(TAG, "Failed to register vibrate state listener"); return; } @@ -156,7 +156,7 @@ final class InputDeviceVibrator extends Vibrator { if (mDelegates.containsKey(listener)) { final OnVibratorStateChangedListenerDelegate delegate = mDelegates.get(listener); - if (!mInputManager.unregisterVibratorStateListener(mDeviceId, delegate)) { + if (!mGlobal.unregisterVibratorStateListener(mDeviceId, delegate)) { Log.w(TAG, "Failed to unregister vibrate state listener"); return; } @@ -176,12 +176,12 @@ final class InputDeviceVibrator extends Vibrator { @Override public void vibrate(int uid, String opPkg, @NonNull VibrationEffect effect, String reason, @NonNull VibrationAttributes attributes) { - mInputManager.vibrate(mDeviceId, effect, mToken); + mGlobal.vibrate(mDeviceId, effect, mToken); } @Override public void cancel() { - mInputManager.cancelVibrate(mDeviceId, mToken); + mGlobal.cancelVibrate(mDeviceId, mToken); } @Override diff --git a/core/java/android/hardware/input/InputDeviceVibratorManager.java b/core/java/android/hardware/input/InputDeviceVibratorManager.java index d77f9c351b8e..64b566772884 100644 --- a/core/java/android/hardware/input/InputDeviceVibratorManager.java +++ b/core/java/android/hardware/input/InputDeviceVibratorManager.java @@ -40,7 +40,7 @@ public class InputDeviceVibratorManager extends VibratorManager private static final boolean DEBUG = false; private final Binder mToken; - private final InputManager mInputManager; + private final InputManagerGlobal mGlobal; // The input device Id. private final int mDeviceId; @@ -48,8 +48,8 @@ public class InputDeviceVibratorManager extends VibratorManager @GuardedBy("mVibrators") private final SparseArray<Vibrator> mVibrators = new SparseArray<>(); - public InputDeviceVibratorManager(InputManager inputManager, int deviceId) { - mInputManager = inputManager; + public InputDeviceVibratorManager(int deviceId) { + mGlobal = InputManagerGlobal.getInstance(); mDeviceId = deviceId; mToken = new Binder(); @@ -61,10 +61,10 @@ public class InputDeviceVibratorManager extends VibratorManager mVibrators.clear(); InputDevice inputDevice = InputDevice.getDevice(mDeviceId); final int[] vibratorIds = - mInputManager.getVibratorIds(mDeviceId); + mGlobal.getVibratorIds(mDeviceId); for (int i = 0; i < vibratorIds.length; i++) { mVibrators.put(vibratorIds[i], - new InputDeviceVibrator(mInputManager, mDeviceId, vibratorIds[i])); + new InputDeviceVibrator(mDeviceId, vibratorIds[i])); } } } @@ -127,12 +127,12 @@ public class InputDeviceVibratorManager extends VibratorManager @Override public void vibrate(int uid, String opPkg, @NonNull CombinedVibration effect, String reason, @Nullable VibrationAttributes attributes) { - mInputManager.vibrate(mDeviceId, effect, mToken); + mGlobal.vibrate(mDeviceId, effect, mToken); } @Override public void cancel() { - mInputManager.cancelVibrate(mDeviceId, mToken); + mGlobal.cancelVibrate(mDeviceId, mToken); } @Override diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java index 054ae21be14a..5dc3825215c0 100644 --- a/core/java/android/hardware/input/InputManager.java +++ b/core/java/android/hardware/input/InputManager.java @@ -33,21 +33,15 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.hardware.BatteryState; import android.hardware.SensorManager; -import android.hardware.lights.Light; -import android.hardware.lights.LightState; import android.hardware.lights.LightsManager; -import android.hardware.lights.LightsRequest; import android.os.Binder; import android.os.Build; -import android.os.CombinedVibration; import android.os.Handler; import android.os.IBinder; -import android.os.IVibratorStateListener; import android.os.InputEventInjectionSync; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; -import android.os.VibrationEffect; import android.os.Vibrator; import android.os.VibratorManager; import android.util.Log; @@ -1380,7 +1374,7 @@ public final class InputManager { * @hide */ public Vibrator getInputDeviceVibrator(int deviceId, int vibratorId) { - return new InputDeviceVibrator(this, deviceId, vibratorId); + return new InputDeviceVibrator(deviceId, vibratorId); } /** @@ -1391,85 +1385,7 @@ public final class InputManager { */ @NonNull public VibratorManager getInputDeviceVibratorManager(int deviceId) { - return new InputDeviceVibratorManager(InputManager.this, deviceId); - } - - /* - * Get the list of device vibrators - * @return The list of vibrators IDs - */ - int[] getVibratorIds(int deviceId) { - try { - return mIm.getVibratorIds(deviceId); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /* - * Perform vibration effect - */ - void vibrate(int deviceId, VibrationEffect effect, IBinder token) { - try { - mIm.vibrate(deviceId, effect, token); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /* - * Perform combined vibration effect - */ - void vibrate(int deviceId, CombinedVibration effect, IBinder token) { - try { - mIm.vibrateCombined(deviceId, effect, token); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /* - * Cancel an ongoing vibration - */ - void cancelVibrate(int deviceId, IBinder token) { - try { - mIm.cancelVibrate(deviceId, token); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /* - * Check if input device is vibrating - */ - boolean isVibrating(int deviceId) { - try { - return mIm.isVibrating(deviceId); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /** - * Register input device vibrator state listener - */ - boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) { - try { - return mIm.registerVibratorStateListener(deviceId, listener); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } - } - - /** - * Unregister input device vibrator state listener - */ - boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) { - try { - return mIm.unregisterVibratorStateListener(deviceId, listener); - } catch (RemoteException ex) { - throw ex.rethrowFromSystemServer(); - } + return new InputDeviceVibratorManager(deviceId); } /** @@ -1499,77 +1415,7 @@ public final class InputManager { */ @NonNull public LightsManager getInputDeviceLightsManager(int deviceId) { - return new InputDeviceLightsManager(InputManager.this, deviceId); - } - - /** - * Gets a list of light objects associated with an input device. - * @return The list of lights, never null. - */ - @NonNull List<Light> getLights(int deviceId) { - try { - return mIm.getLights(deviceId); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Returns the state of an input device light. - * @return the light state - */ - @NonNull LightState getLightState(int deviceId, @NonNull Light light) { - try { - return mIm.getLightState(deviceId, light.getId()); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Request to modify the states of multiple lights. - * - * @param request the settings for lights that should change - */ - void requestLights(int deviceId, @NonNull LightsRequest request, IBinder token) { - try { - List<Integer> lightIdList = request.getLights(); - int[] lightIds = new int[lightIdList.size()]; - for (int i = 0; i < lightIds.length; i++) { - lightIds[i] = lightIdList.get(i); - } - List<LightState> lightStateList = request.getLightStates(); - mIm.setLightStates(deviceId, lightIds, - lightStateList.toArray(new LightState[0]), - token); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Open light session for input device manager - * - * @param token The token for the light session - */ - void openLightSession(int deviceId, String opPkg, @NonNull IBinder token) { - try { - mIm.openLightSession(deviceId, opPkg, token); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - } - - /** - * Close light session - * - */ - void closeLightSession(int deviceId, @NonNull IBinder token) { - try { - mIm.closeLightSession(deviceId, token); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + return new InputDeviceLightsManager(getContext(), deviceId); } /** diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java index 524d8206bddf..08d81bd3c325 100644 --- a/core/java/android/hardware/input/InputManagerGlobal.java +++ b/core/java/android/hardware/input/InputManagerGlobal.java @@ -27,12 +27,18 @@ import android.hardware.input.InputManager.InputDeviceBatteryListener; import android.hardware.input.InputManager.InputDeviceListener; import android.hardware.input.InputManager.KeyboardBacklightListener; import android.hardware.input.InputManager.OnTabletModeChangedListener; +import android.hardware.lights.Light; +import android.hardware.lights.LightState; +import android.hardware.lights.LightsRequest; +import android.os.CombinedVibration; import android.os.Handler; import android.os.IBinder; +import android.os.IVibratorStateListener; import android.os.Looper; import android.os.Message; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.VibrationEffect; import android.util.Log; import android.util.SparseArray; import android.view.Display; @@ -903,4 +909,152 @@ public final class InputManagerGlobal { throw ex.rethrowFromSystemServer(); } } + + /** + * Gets a list of light objects associated with an input device. + * @return The list of lights, never null. + */ + @NonNull List<Light> getLights(int deviceId) { + try { + return mIm.getLights(deviceId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Returns the state of an input device light. + * @return the light state + */ + @NonNull LightState getLightState(int deviceId, @NonNull Light light) { + try { + return mIm.getLightState(deviceId, light.getId()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Request to modify the states of multiple lights. + * + * @param request the settings for lights that should change + */ + void requestLights(int deviceId, @NonNull LightsRequest request, IBinder token) { + try { + List<Integer> lightIdList = request.getLights(); + int[] lightIds = new int[lightIdList.size()]; + for (int i = 0; i < lightIds.length; i++) { + lightIds[i] = lightIdList.get(i); + } + List<LightState> lightStateList = request.getLightStates(); + mIm.setLightStates(deviceId, lightIds, + lightStateList.toArray(new LightState[0]), + token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Open light session for input device manager + * + * @param token The token for the light session + */ + void openLightSession(int deviceId, String opPkg, @NonNull IBinder token) { + try { + mIm.openLightSession(deviceId, opPkg, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Close light session + * + */ + void closeLightSession(int deviceId, @NonNull IBinder token) { + try { + mIm.closeLightSession(deviceId, token); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /* + * Get the list of device vibrators + * @return The list of vibrators IDs + */ + int[] getVibratorIds(int deviceId) { + try { + return mIm.getVibratorIds(deviceId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /* + * Perform vibration effect + */ + void vibrate(int deviceId, VibrationEffect effect, IBinder token) { + try { + mIm.vibrate(deviceId, effect, token); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /* + * Perform combined vibration effect + */ + void vibrate(int deviceId, CombinedVibration effect, IBinder token) { + try { + mIm.vibrateCombined(deviceId, effect, token); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /* + * Cancel an ongoing vibration + */ + void cancelVibrate(int deviceId, IBinder token) { + try { + mIm.cancelVibrate(deviceId, token); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /* + * Check if input device is vibrating + */ + boolean isVibrating(int deviceId) { + try { + return mIm.isVibrating(deviceId); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Register input device vibrator state listener + */ + boolean registerVibratorStateListener(int deviceId, IVibratorStateListener listener) { + try { + return mIm.registerVibratorStateListener(deviceId, listener); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } + + /** + * Unregister input device vibrator state listener + */ + boolean unregisterVibratorStateListener(int deviceId, IVibratorStateListener listener) { + try { + return mIm.unregisterVibratorStateListener(deviceId, listener); + } catch (RemoteException ex) { + throw ex.rethrowFromSystemServer(); + } + } } diff --git a/core/java/android/hardware/input/VirtualKeyboardConfig.java b/core/java/android/hardware/input/VirtualKeyboardConfig.java index d788df49bfc5..6d03065214ca 100644 --- a/core/java/android/hardware/input/VirtualKeyboardConfig.java +++ b/core/java/android/hardware/input/VirtualKeyboardConfig.java @@ -110,10 +110,7 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem /** * Sets the preferred input language of the virtual keyboard using an IETF - * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> - * conformant tag. See {@code keyboardLocale} attribute in - * frameworks/base/packages/InputDevices/res/xml/keyboard_layouts.xml for a list of - * supported language tags. + * <a href="https://tools.ietf.org/html/bcp47">BCP-47</a> conformant tag. * * The passed in {@code languageTag} will be canonized using {@link * ULocale} and used by the system as a hint to configure the keyboard layout. @@ -135,7 +132,7 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem public Builder setLanguageTag(@NonNull String languageTag) { Objects.requireNonNull(languageTag, "languageTag cannot be null"); ULocale locale = ULocale.forLanguageTag(languageTag); - if (locale.getLanguage().isEmpty() || locale.getCountry().isEmpty()) { + if (locale.getLanguage().isEmpty()) { throw new IllegalArgumentException("The language tag is not valid."); } mLanguageTag = ULocale.createCanonical(locale).toLanguageTag(); @@ -144,8 +141,8 @@ public final class VirtualKeyboardConfig extends VirtualInputDeviceConfig implem /** * Sets the preferred layout type of the virtual keyboard. See {@code keyboardLayoutType} - * attribute in frameworks/base/packages/InputDevices/res/xml/keyboard_layouts.xml for a - * list of supported layout types. + * attribute in frameworks/base/core/res/res/values/attrs.xml for a list of supported + * layout types. * * Note that the preferred layout is not guaranteed. If the specified layout type is * well-formed but not supported, the keyboard will be using English US QWERTY layout. diff --git a/core/java/android/hardware/radio/IRadioService.aidl b/core/java/android/hardware/radio/IRadioService.aidl index c7131a7fe9c9..9349cf7d0c32 100644 --- a/core/java/android/hardware/radio/IRadioService.aidl +++ b/core/java/android/hardware/radio/IRadioService.aidl @@ -31,7 +31,7 @@ interface IRadioService { List<RadioManager.ModuleProperties> listModules(); ITuner openTuner(int moduleId, in RadioManager.BandConfig bandConfig, boolean withAudio, - in ITunerCallback callback, int targetSdkVersion); + in ITunerCallback callback); ICloseHandle addAnnouncementListener(in int[] enabledTypes, in IAnnouncementListener listener); diff --git a/core/java/android/hardware/radio/RadioManager.java b/core/java/android/hardware/radio/RadioManager.java index f072e3b43b4a..8c6083ce49b6 100644 --- a/core/java/android/hardware/radio/RadioManager.java +++ b/core/java/android/hardware/radio/RadioManager.java @@ -1796,7 +1796,7 @@ public class RadioManager { ITuner tuner; TunerCallbackAdapter halCallback = new TunerCallbackAdapter(callback, handler); try { - tuner = mService.openTuner(moduleId, config, withAudio, halCallback, mTargetSdkVersion); + tuner = mService.openTuner(moduleId, config, withAudio, halCallback); } catch (RemoteException | IllegalArgumentException | IllegalStateException ex) { Log.e(TAG, "Failed to open tuner", ex); return null; @@ -1873,7 +1873,6 @@ public class RadioManager { @NonNull private final Context mContext; @NonNull private final IRadioService mService; - private final int mTargetSdkVersion; /** * @hide @@ -1890,6 +1889,5 @@ public class RadioManager { public RadioManager(Context context, IRadioService service) { mContext = context; mService = service; - mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion; } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 127c7a0476f6..123f48067be8 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -10022,6 +10022,21 @@ public final class Settings { "emergency_gesture_sound_enabled"; /** + * Whether the emergency gesture UI is currently showing. + * + * @hide + */ + public static final String EMERGENCY_GESTURE_UI_SHOWING = "emergency_gesture_ui_showing"; + + /** + * The last time the emergency gesture UI was started. + * + * @hide + */ + public static final String EMERGENCY_GESTURE_UI_LAST_STARTED_MILLIS = + "emergency_gesture_ui_last_started_millis"; + + /** * Whether the camera launch gesture to double tap the power button when the screen is off * should be disabled. * @@ -11433,21 +11448,46 @@ public final class Settings { public @interface DeviceStateRotationLockSetting { } + /** @hide */ + public static final int DEVICE_STATE_ROTATION_KEY_UNKNOWN = -1; + /** @hide */ + public static final int DEVICE_STATE_ROTATION_KEY_FOLDED = 0; + /** @hide */ + public static final int DEVICE_STATE_ROTATION_KEY_HALF_FOLDED = 1; + /** @hide */ + public static final int DEVICE_STATE_ROTATION_KEY_UNFOLDED = 2; + + /** + * The different postures that can be used as keys with + * {@link #DEVICE_STATE_ROTATION_LOCK}. + * @hide + */ + @IntDef(prefix = {"DEVICE_STATE_ROTATION_KEY_"}, value = { + DEVICE_STATE_ROTATION_KEY_UNKNOWN, + DEVICE_STATE_ROTATION_KEY_FOLDED, + DEVICE_STATE_ROTATION_KEY_HALF_FOLDED, + DEVICE_STATE_ROTATION_KEY_UNFOLDED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface DeviceStateRotationLockKey { + } + /** * Rotation lock setting keyed on device state. * - * This holds a serialized map using int keys that represent Device States and value of + * This holds a serialized map using int keys that represent postures in + * {@link DeviceStateRotationLockKey} and value of * {@link DeviceStateRotationLockSetting} representing the rotation lock setting for that - * device state. + * posture. * * Serialized as key0:value0:key1:value1:...:keyN:valueN. * * Example: "0:1:1:2:2:1" * This example represents a map of: * <ul> - * <li>0 -> DEVICE_STATE_ROTATION_LOCK_LOCKED</li> - * <li>1 -> DEVICE_STATE_ROTATION_LOCK_UNLOCKED</li> - * <li>2 -> DEVICE_STATE_ROTATION_LOCK_IGNORED</li> + * <li>DEVICE_STATE_ROTATION_KEY_FOLDED -> DEVICE_STATE_ROTATION_LOCK_LOCKED</li> + * <li>DEVICE_STATE_ROTATION_KEY_HALF_FOLDED -> DEVICE_STATE_ROTATION_LOCK_UNLOCKED</li> + * <li>DEVICE_STATE_ROTATION_KEY_UNFOLDED -> DEVICE_STATE_ROTATION_LOCK_IGNORED</li> * </ul> * * @hide @@ -15030,6 +15070,16 @@ public final class Settings { "emergency_gesture_tap_detection_min_time_ms"; /** + * The maximum duration in milliseconds for which the emergency gesture UI can stay + * "sticky", where the notification pull-down shade and navigation gestures/buttons are + * temporarily disabled. The feature is disabled completely if the value is set to zero. + * + * @hide + */ + public static final String EMERGENCY_GESTURE_STICKY_UI_MAX_DURATION_MILLIS = + "emergency_gesture_sticky_ui_max_duration_millis"; + + /** * Whether to enable automatic system server heap dumps. This only works on userdebug or * eng builds, not on user builds. This is set by the user and overrides the config value. * 1 means enable, 0 means disable. @@ -18582,7 +18632,7 @@ public final class Settings { * The modes that can be used when disabling syncs to the 'config' settings. * @hide */ - @IntDef(prefix = "DISABLE_SYNC_MODE_", + @IntDef(prefix = "SYNC_DISABLED_MODE_", value = { SYNC_DISABLED_MODE_NONE, SYNC_DISABLED_MODE_PERSISTENT, SYNC_DISABLED_MODE_UNTIL_REBOOT }) @Retention(RetentionPolicy.SOURCE) @@ -18592,23 +18642,36 @@ public final class Settings { /** * Sync is not disabled. * + * @deprecated use the constant in DeviceConfig + * * @hide */ - public static final int SYNC_DISABLED_MODE_NONE = 0; + @Deprecated + public static final int SYNC_DISABLED_MODE_NONE = DeviceConfig.SYNC_DISABLED_MODE_NONE; /** * Disabling of Config bulk update / syncing is persistent, i.e. it survives a device * reboot. + * + * @deprecated use the constant in DeviceConfig + * * @hide */ - public static final int SYNC_DISABLED_MODE_PERSISTENT = 1; + @Deprecated + public static final int SYNC_DISABLED_MODE_PERSISTENT = + DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT; /** * Disabling of Config bulk update / syncing is not persistent, i.e. it will not survive a * device reboot. + * + * @deprecated use the constant in DeviceConfig + * * @hide */ - public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = 2; + @Deprecated + public static final int SYNC_DISABLED_MODE_UNTIL_REBOOT = + DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT; /** * The content:// style URL for the config table. diff --git a/core/java/android/security/net/config/SystemCertificateSource.java b/core/java/android/security/net/config/SystemCertificateSource.java index 13f7e5d4232b..3a254c1d92fc 100644 --- a/core/java/android/security/net/config/SystemCertificateSource.java +++ b/core/java/android/security/net/config/SystemCertificateSource.java @@ -39,9 +39,13 @@ public final class SystemCertificateSource extends DirectoryCertificateSource { } private static File getDirectory() { - // TODO(miguelaranda): figure out correct code path. + if ((System.getProperty("system.certs.enabled") != null) + && (System.getProperty("system.certs.enabled")).equals("true")) { + return new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"); + } File updatable_dir = new File("/apex/com.android.conscrypt/cacerts"); - if (updatable_dir.exists() && !(updatable_dir.list().length == 0)) { + if (updatable_dir.exists() + && !(updatable_dir.list().length == 0)) { return updatable_dir; } return new File(System.getenv("ANDROID_ROOT") + "/etc/security/cacerts"); diff --git a/core/java/android/service/dreams/DreamOverlayConnectionHandler.java b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java new file mode 100644 index 000000000000..cafe02ad8658 --- /dev/null +++ b/core/java/android/service/dreams/DreamOverlayConnectionHandler.java @@ -0,0 +1,242 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.dreams; + +import android.annotation.NonNull; +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.util.Log; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.ObservableServiceConnection; +import com.android.internal.util.PersistentServiceConnection; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.function.Consumer; + +/** + * Handles the service connection to {@link IDreamOverlay} + * + * @hide + */ +@VisibleForTesting +public final class DreamOverlayConnectionHandler { + private static final String TAG = "DreamOverlayConnection"; + + private static final int MSG_ADD_CONSUMER = 1; + private static final int MSG_REMOVE_CONSUMER = 2; + private static final int MSG_OVERLAY_CLIENT_READY = 3; + + private final Handler mHandler; + private final PersistentServiceConnection<IDreamOverlay> mConnection; + // Retrieved Client + private IDreamOverlayClient mClient; + // A list of pending requests to execute on the overlay. + private final List<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>(); + private final OverlayConnectionCallback mCallback; + + DreamOverlayConnectionHandler( + Context context, + Looper looper, + Intent serviceIntent, + int minConnectionDurationMs, + int maxReconnectAttempts, + int baseReconnectDelayMs) { + this(context, looper, serviceIntent, minConnectionDurationMs, maxReconnectAttempts, + baseReconnectDelayMs, new Injector()); + } + + @VisibleForTesting + public DreamOverlayConnectionHandler( + Context context, + Looper looper, + Intent serviceIntent, + int minConnectionDurationMs, + int maxReconnectAttempts, + int baseReconnectDelayMs, + Injector injector) { + mCallback = new OverlayConnectionCallback(); + mHandler = new Handler(looper, new OverlayHandlerCallback()); + mConnection = injector.buildConnection( + context, + mHandler, + serviceIntent, + minConnectionDurationMs, + maxReconnectAttempts, + baseReconnectDelayMs + ); + } + + /** + * Bind to the overlay service. If binding fails, we automatically call unbind to clean + * up resources. + * + * @return true if binding was successful, false otherwise. + */ + public boolean bind() { + mConnection.addCallback(mCallback); + final boolean success = mConnection.bind(); + if (!success) { + unbind(); + } + return success; + } + + /** + * Unbind from the overlay service, clearing any pending callbacks. + */ + public void unbind() { + mConnection.removeCallback(mCallback); + // Remove any pending messages. + mHandler.removeCallbacksAndMessages(null); + mClient = null; + mConsumers.clear(); + mConnection.unbind(); + } + + /** + * Adds a consumer to run once the overlay service has connected. If the overlay service + * disconnects (eg binding dies) and then reconnects, this consumer will be re-run unless + * removed. + * + * @param consumer The consumer to run. This consumer is always executed asynchronously. + */ + public void addConsumer(Consumer<IDreamOverlayClient> consumer) { + final Message msg = mHandler.obtainMessage(MSG_ADD_CONSUMER, consumer); + mHandler.sendMessage(msg); + } + + /** + * Removes the consumer, preventing this consumer from being called again. + * + * @param consumer The consumer to remove. + */ + public void removeConsumer(Consumer<IDreamOverlayClient> consumer) { + final Message msg = mHandler.obtainMessage(MSG_REMOVE_CONSUMER, consumer); + mHandler.sendMessage(msg); + // Clear any pending messages to add this consumer + mHandler.removeMessages(MSG_ADD_CONSUMER, consumer); + } + + private final class OverlayHandlerCallback implements Handler.Callback { + @Override + public boolean handleMessage(@NonNull Message msg) { + switch (msg.what) { + case MSG_OVERLAY_CLIENT_READY: + onOverlayClientReady((IDreamOverlayClient) msg.obj); + break; + case MSG_ADD_CONSUMER: + onAddConsumer((Consumer<IDreamOverlayClient>) msg.obj); + break; + case MSG_REMOVE_CONSUMER: + onRemoveConsumer((Consumer<IDreamOverlayClient>) msg.obj); + break; + } + return true; + } + } + + private void onOverlayClientReady(IDreamOverlayClient client) { + mClient = client; + for (Consumer<IDreamOverlayClient> consumer : mConsumers) { + consumer.accept(mClient); + } + } + + private void onAddConsumer(Consumer<IDreamOverlayClient> consumer) { + if (mClient != null) { + consumer.accept(mClient); + } + mConsumers.add(consumer); + } + + private void onRemoveConsumer(Consumer<IDreamOverlayClient> consumer) { + mConsumers.remove(consumer); + } + + private final class OverlayConnectionCallback implements + ObservableServiceConnection.Callback<IDreamOverlay> { + + private final IDreamOverlayClientCallback mClientCallback = + new IDreamOverlayClientCallback.Stub() { + @Override + public void onDreamOverlayClient(IDreamOverlayClient client) { + final Message msg = + mHandler.obtainMessage(MSG_OVERLAY_CLIENT_READY, client); + mHandler.sendMessage(msg); + } + }; + + @Override + public void onConnected( + ObservableServiceConnection<IDreamOverlay> connection, + IDreamOverlay service) { + try { + service.getClient(mClientCallback); + } catch (RemoteException e) { + Log.e(TAG, "could not get DreamOverlayClient", e); + } + } + + @Override + public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection, + int reason) { + mClient = null; + // Cancel any pending messages about the overlay being ready, since it is no + // longer ready. + mHandler.removeMessages(MSG_OVERLAY_CLIENT_READY); + } + } + + /** + * Injector for testing + */ + @VisibleForTesting + public static class Injector { + /** + * Returns milliseconds since boot, not counting time spent in deep sleep. Can be overridden + * in tests with a fake clock. + */ + public PersistentServiceConnection<IDreamOverlay> buildConnection( + Context context, + Handler handler, + Intent serviceIntent, + int minConnectionDurationMs, + int maxReconnectAttempts, + int baseReconnectDelayMs) { + final Executor executor = handler::post; + final int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; + return new PersistentServiceConnection<>( + context, + executor, + handler, + IDreamOverlay.Stub::asInterface, + serviceIntent, + flags, + minConnectionDurationMs, + maxReconnectAttempts, + baseReconnectDelayMs + ); + } + } +} diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index d0f3820704f7..3a323524b68e 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -68,8 +68,6 @@ import android.view.accessibility.AccessibilityEvent; import com.android.internal.R; import com.android.internal.util.DumpUtils; -import com.android.internal.util.ObservableServiceConnection; -import com.android.internal.util.PersistentServiceConnection; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -77,8 +75,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.concurrent.Executor; import java.util.function.Consumer; /** @@ -234,7 +230,6 @@ public class DreamService extends Service implements Window.Callback { private boolean mCanDoze; private boolean mDozing; private boolean mWindowless; - private boolean mOverlayFinishing; private int mDozeScreenState = Display.STATE_UNKNOWN; private int mDozeScreenBrightness = PowerManager.BRIGHTNESS_DEFAULT; @@ -246,88 +241,7 @@ public class DreamService extends Service implements Window.Callback { private DreamServiceWrapper mDreamServiceWrapper; private Runnable mDispatchAfterOnAttachedToWindow; - private OverlayConnection mOverlayConnection; - - private static class OverlayConnection extends PersistentServiceConnection<IDreamOverlay> { - // Retrieved Client - private IDreamOverlayClient mClient; - - // A list of pending requests to execute on the overlay. - private final ArrayList<Consumer<IDreamOverlayClient>> mConsumers = new ArrayList<>(); - - private final IDreamOverlayClientCallback mClientCallback = - new IDreamOverlayClientCallback.Stub() { - @Override - public void onDreamOverlayClient(IDreamOverlayClient client) { - mClient = client; - - for (Consumer<IDreamOverlayClient> consumer : mConsumers) { - consumer.accept(mClient); - } - } - }; - - private final Callback<IDreamOverlay> mCallback = new Callback<IDreamOverlay>() { - @Override - public void onConnected(ObservableServiceConnection<IDreamOverlay> connection, - IDreamOverlay service) { - try { - service.getClient(mClientCallback); - } catch (RemoteException e) { - Log.e(TAG, "could not get DreamOverlayClient", e); - } - } - - @Override - public void onDisconnected(ObservableServiceConnection<IDreamOverlay> connection, - int reason) { - mClient = null; - } - }; - - OverlayConnection(Context context, - Executor executor, - Handler handler, - ServiceTransformer<IDreamOverlay> transformer, - Intent serviceIntent, - int flags, - int minConnectionDurationMs, - int maxReconnectAttempts, - int baseReconnectDelayMs) { - super(context, executor, handler, transformer, serviceIntent, flags, - minConnectionDurationMs, - maxReconnectAttempts, baseReconnectDelayMs); - } - - @Override - public boolean bind() { - addCallback(mCallback); - return super.bind(); - } - - @Override - public void unbind() { - removeCallback(mCallback); - super.unbind(); - } - - public void addConsumer(Consumer<IDreamOverlayClient> consumer) { - execute(() -> { - mConsumers.add(consumer); - if (mClient != null) { - consumer.accept(mClient); - } - }); - } - - public void removeConsumer(Consumer<IDreamOverlayClient> consumer) { - execute(() -> mConsumers.remove(consumer)); - } - - public void clearConsumers() { - execute(() -> mConsumers.clear()); - } - } + private DreamOverlayConnectionHandler mOverlayConnection; private final IDreamOverlayCallback mOverlayCallback = new IDreamOverlayCallback.Stub() { @Override @@ -1030,18 +944,18 @@ public class DreamService extends Service implements Window.Callback { final Resources resources = getResources(); final Intent overlayIntent = new Intent().setComponent(overlayComponent); - mOverlayConnection = new OverlayConnection( + mOverlayConnection = new DreamOverlayConnectionHandler( /* context= */ this, - getMainExecutor(), - mHandler, - IDreamOverlay.Stub::asInterface, + Looper.getMainLooper(), overlayIntent, - /* flags= */ Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE, resources.getInteger(R.integer.config_minDreamOverlayDurationMs), resources.getInteger(R.integer.config_dreamOverlayMaxReconnectAttempts), resources.getInteger(R.integer.config_dreamOverlayReconnectTimeoutMs)); - mOverlayConnection.bind(); + if (!mOverlayConnection.bind()) { + // Binding failed. + mOverlayConnection = null; + } } return mDreamServiceWrapper; @@ -1069,9 +983,7 @@ public class DreamService extends Service implements Window.Callback { // If there is an active overlay connection, signal that the dream is ending before // continuing. Note that the overlay cannot rely on the unbound state, since another dream // might have bound to it in the meantime. - if (mOverlayConnection != null && !mOverlayFinishing) { - // Set mOverlayFinish to true to only allow this consumer to be added once. - mOverlayFinishing = true; + if (mOverlayConnection != null) { mOverlayConnection.addConsumer(overlay -> { try { overlay.endDream(); @@ -1082,7 +994,6 @@ public class DreamService extends Service implements Window.Callback { Log.e(mTag, "could not inform overlay of dream end:" + e); } }); - mOverlayConnection.clearConsumers(); return; } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index e55e2e5ae874..1d49049ab7d4 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -77,6 +77,7 @@ import java.util.Objects; * <pre> * <service android:name=".NotificationListener" * android:label="@string/service_name" + * android:exported="false" * android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> * <intent-filter> * <action android:name="android.service.notification.NotificationListenerService" /> @@ -1420,7 +1421,7 @@ public abstract class NotificationListenerService extends Service { if (getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P) { ArrayList<Person> people = notification.extras.getParcelableArrayList( Notification.EXTRA_PEOPLE_LIST, android.app.Person.class); - if (people != null && people.isEmpty()) { + if (people != null && !people.isEmpty()) { int size = people.size(); String[] peopleArray = new String[size]; for (int i = 0; i < size; i++) { diff --git a/core/java/android/service/voice/HotwordDetectedResult.java b/core/java/android/service/voice/HotwordDetectedResult.java index dd3f99cc27ef..ab6f05535a2f 100644 --- a/core/java/android/service/voice/HotwordDetectedResult.java +++ b/core/java/android/service/voice/HotwordDetectedResult.java @@ -94,6 +94,9 @@ public final class HotwordDetectedResult implements Parcelable { /** Represents unset value for the triggered audio channel. */ public static final int AUDIO_CHANNEL_UNSET = -1; + /** Represents unset value for the background audio signal power. */ + public static final int BACKGROUND_AUDIO_POWER_UNSET = -1; + /** Limits the max value for the hotword offset. */ private static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE = 60 * 60 * 1000; // 1 hour @@ -296,6 +299,24 @@ public final class HotwordDetectedResult implements Parcelable { new DetectedPhrase.Builder().build(); /** + * Power of the background audio signal in which the hotword phrase was detected. + * + * <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive) + * and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid. + */ + private final int mBackgroundAudioPower; + private static int defaultBackgroundAudioPower() { + return BACKGROUND_AUDIO_POWER_UNSET; + } + + /** + * Returns the maximum value of {@link #getBackgroundAudioPower()}. + */ + public static int getMaxBackgroundAudioPower() { + return 255; + } + + /** * Returns how many bytes should be written into the Parcel * * @hide @@ -346,6 +367,10 @@ public final class HotwordDetectedResult implements Parcelable { if (!persistableBundle.isEmpty()) { totalBits += getParcelableSize(persistableBundle) * Byte.SIZE; } + if (hotwordDetectedResult.getBackgroundAudioPower() != defaultBackgroundAudioPower()) { + totalBits += bitCount(HotwordDetectedResult.getMaxBackgroundAudioPower()); + } + return totalBits; } @@ -362,6 +387,10 @@ public final class HotwordDetectedResult implements Parcelable { Preconditions.checkArgumentInRange(mScore, 0, getMaxScore(), "score"); Preconditions.checkArgumentInRange(mPersonalizedScore, 0, getMaxScore(), "personalizedScore"); + if (mBackgroundAudioPower != BACKGROUND_AUDIO_POWER_UNSET) { + Preconditions.checkArgumentInRange(mBackgroundAudioPower, + 0, getMaxBackgroundAudioPower(), "backgroundAudioPower"); + } Preconditions.checkArgumentInRange((long) mHotwordDurationMillis, 0, AudioRecord.getMaxSharedAudioHistoryMillis(), "hotwordDurationMillis"); if (mHotwordOffsetMillis != HOTWORD_OFFSET_UNSET) { @@ -493,7 +522,8 @@ public final class HotwordDetectedResult implements Parcelable { .setPersonalizedScore(mPersonalizedScore) .setAudioStreams(mAudioStreams) .setExtras(mExtras) - .setDetectedPhrase(mDetectedPhrase); + .setDetectedPhrase(mDetectedPhrase) + .setBackgroundAudioPower(mBackgroundAudioPower); } @@ -604,7 +634,8 @@ public final class HotwordDetectedResult implements Parcelable { int personalizedScore, @NonNull List<HotwordAudioStream> audioStreams, @NonNull PersistableBundle extras, - @NonNull DetectedPhrase detectedPhrase) { + @NonNull DetectedPhrase detectedPhrase, + int backgroundAudioPower) { this.mConfidenceLevel = confidenceLevel; com.android.internal.util.AnnotationValidations.validate( HotwordConfidenceLevelValue.class, null, mConfidenceLevel); @@ -624,6 +655,7 @@ public final class HotwordDetectedResult implements Parcelable { this.mDetectedPhrase = detectedPhrase; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mDetectedPhrase); + this.mBackgroundAudioPower = backgroundAudioPower; onConstructed(); } @@ -732,6 +764,17 @@ public final class HotwordDetectedResult implements Parcelable { return mDetectedPhrase; } + /** + * Power of the background audio signal in which the hotword phrase was detected. + * + * <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive) + * and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid. + */ + @DataClass.Generated.Member + public int getBackgroundAudioPower() { + return mBackgroundAudioPower; + } + @Override @DataClass.Generated.Member public String toString() { @@ -749,7 +792,8 @@ public final class HotwordDetectedResult implements Parcelable { "personalizedScore = " + mPersonalizedScore + ", " + "audioStreams = " + mAudioStreams + ", " + "extras = " + mExtras + ", " + - "detectedPhrase = " + mDetectedPhrase + + "detectedPhrase = " + mDetectedPhrase + ", " + + "backgroundAudioPower = " + mBackgroundAudioPower + " }"; } @@ -776,7 +820,8 @@ public final class HotwordDetectedResult implements Parcelable { && mPersonalizedScore == that.mPersonalizedScore && Objects.equals(mAudioStreams, that.mAudioStreams) && Objects.equals(mExtras, that.mExtras) - && Objects.equals(mDetectedPhrase, that.mDetectedPhrase); + && Objects.equals(mDetectedPhrase, that.mDetectedPhrase) + && mBackgroundAudioPower == that.mBackgroundAudioPower; } @Override @@ -797,6 +842,7 @@ public final class HotwordDetectedResult implements Parcelable { _hash = 31 * _hash + Objects.hashCode(mAudioStreams); _hash = 31 * _hash + Objects.hashCode(mExtras); _hash = 31 * _hash + Objects.hashCode(mDetectedPhrase); + _hash = 31 * _hash + mBackgroundAudioPower; return _hash; } @@ -820,6 +866,7 @@ public final class HotwordDetectedResult implements Parcelable { dest.writeParcelableList(mAudioStreams, flags); dest.writeTypedObject(mExtras, flags); dest.writeTypedObject(mDetectedPhrase, flags); + dest.writeInt(mBackgroundAudioPower); } @Override @@ -846,6 +893,7 @@ public final class HotwordDetectedResult implements Parcelable { in.readParcelableList(audioStreams, HotwordAudioStream.class.getClassLoader()); PersistableBundle extras = (PersistableBundle) in.readTypedObject(PersistableBundle.CREATOR); DetectedPhrase detectedPhrase = (DetectedPhrase) in.readTypedObject(DetectedPhrase.CREATOR); + int backgroundAudioPower = in.readInt(); this.mConfidenceLevel = confidenceLevel; com.android.internal.util.AnnotationValidations.validate( @@ -866,6 +914,7 @@ public final class HotwordDetectedResult implements Parcelable { this.mDetectedPhrase = detectedPhrase; com.android.internal.util.AnnotationValidations.validate( NonNull.class, null, mDetectedPhrase); + this.mBackgroundAudioPower = backgroundAudioPower; onConstructed(); } @@ -902,6 +951,7 @@ public final class HotwordDetectedResult implements Parcelable { private @NonNull List<HotwordAudioStream> mAudioStreams; private @NonNull PersistableBundle mExtras; private @NonNull DetectedPhrase mDetectedPhrase; + private int mBackgroundAudioPower; private long mBuilderFieldsSet = 0L; @@ -1052,10 +1102,24 @@ public final class HotwordDetectedResult implements Parcelable { return this; } + /** + * Power of the background audio signal in which the hotword phrase was detected. + * + * <p> Only values between 0 and {@link #getMaxBackgroundAudioPower} (inclusive) + * and the special value {@link #BACKGROUND_AUDIO_POWER_UNSET} are valid. + */ + @DataClass.Generated.Member + public @NonNull Builder setBackgroundAudioPower(int value) { + checkNotUsed(); + mBuilderFieldsSet |= 0x800; + mBackgroundAudioPower = value; + return this; + } + /** Builds the instance. This builder should not be touched after calling this! */ public @NonNull HotwordDetectedResult build() { checkNotUsed(); - mBuilderFieldsSet |= 0x800; // Mark builder used + mBuilderFieldsSet |= 0x1000; // Mark builder used if ((mBuilderFieldsSet & 0x1) == 0) { mConfidenceLevel = defaultConfidenceLevel(); @@ -1090,6 +1154,9 @@ public final class HotwordDetectedResult implements Parcelable { if ((mBuilderFieldsSet & 0x400) == 0) { mDetectedPhrase = new DetectedPhrase.Builder().build(); } + if ((mBuilderFieldsSet & 0x800) == 0) { + mBackgroundAudioPower = defaultBackgroundAudioPower(); + } HotwordDetectedResult o = new HotwordDetectedResult( mConfidenceLevel, mMediaSyncEvent, @@ -1101,12 +1168,13 @@ public final class HotwordDetectedResult implements Parcelable { mPersonalizedScore, mAudioStreams, mExtras, - mDetectedPhrase); + mDetectedPhrase, + mBackgroundAudioPower); return o; } private void checkNotUsed() { - if ((mBuilderFieldsSet & 0x800) != 0) { + if ((mBuilderFieldsSet & 0x1000) != 0) { throw new IllegalStateException( "This Builder should not be reused. Use a new Builder instance instead"); } @@ -1114,10 +1182,10 @@ public final class HotwordDetectedResult implements Parcelable { } @DataClass.Generated( - time = 1676870324215L, + time = 1679010159293L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/service/voice/HotwordDetectedResult.java", - inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate @android.annotation.NonNull android.service.voice.DetectedPhrase mDetectedPhrase\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\npublic @java.lang.Deprecated int getHotwordPhraseId()\npublic static @java.lang.Deprecated int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nstatic int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []") + inputSignatures = "public static final int CONFIDENCE_LEVEL_NONE\npublic static final int CONFIDENCE_LEVEL_LOW\npublic static final int CONFIDENCE_LEVEL_LOW_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM\npublic static final int CONFIDENCE_LEVEL_MEDIUM_HIGH\npublic static final int CONFIDENCE_LEVEL_HIGH\npublic static final int CONFIDENCE_LEVEL_VERY_HIGH\npublic static final int HOTWORD_OFFSET_UNSET\npublic static final int AUDIO_CHANNEL_UNSET\npublic static final int BACKGROUND_AUDIO_POWER_UNSET\nprivate static final int LIMIT_HOTWORD_OFFSET_MAX_VALUE\nprivate static final int LIMIT_AUDIO_CHANNEL_MAX_VALUE\nprivate static final java.lang.String EXTRA_PROXIMITY\npublic static final int PROXIMITY_UNKNOWN\npublic static final int PROXIMITY_NEAR\npublic static final int PROXIMITY_FAR\nprivate final @android.service.voice.HotwordDetectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate @android.annotation.Nullable android.media.MediaSyncEvent mMediaSyncEvent\nprivate int mHotwordOffsetMillis\nprivate int mHotwordDurationMillis\nprivate int mAudioChannel\nprivate boolean mHotwordDetectionPersonalized\nprivate final int mScore\nprivate final int mPersonalizedScore\nprivate final @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> mAudioStreams\nprivate final @android.annotation.NonNull android.os.PersistableBundle mExtras\nprivate static int sMaxBundleSize\nprivate @android.annotation.NonNull android.service.voice.DetectedPhrase mDetectedPhrase\nprivate final int mBackgroundAudioPower\nprivate static int defaultConfidenceLevel()\nprivate static int defaultScore()\nprivate static int defaultPersonalizedScore()\npublic static int getMaxScore()\npublic @java.lang.Deprecated int getHotwordPhraseId()\npublic static @java.lang.Deprecated int getMaxHotwordPhraseId()\nprivate static java.util.List<android.service.voice.HotwordAudioStream> defaultAudioStreams()\nprivate static android.os.PersistableBundle defaultExtras()\npublic static int getMaxBundleSize()\npublic @android.annotation.Nullable android.media.MediaSyncEvent getMediaSyncEvent()\nprivate static int defaultBackgroundAudioPower()\npublic static int getMaxBackgroundAudioPower()\npublic static int getParcelableSize(android.os.Parcelable)\npublic static int getUsageSize(android.service.voice.HotwordDetectedResult)\nstatic int bitCount(long)\nprivate void onConstructed()\npublic @android.annotation.NonNull java.util.List<android.service.voice.HotwordAudioStream> getAudioStreams()\npublic void setProximity(double)\npublic @android.service.voice.HotwordDetectedResult.ProximityValue int getProximity()\nprivate @android.service.voice.HotwordDetectedResult.ProximityValue int convertToProximityLevel(double)\npublic android.service.voice.HotwordDetectedResult.Builder buildUpon()\nclass HotwordDetectedResult extends java.lang.Object implements [android.os.Parcelable]\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)\npublic @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setAudioStreams(java.util.List<android.service.voice.HotwordAudioStream>)\npublic @java.lang.Deprecated @android.annotation.NonNull android.service.voice.HotwordDetectedResult.Builder setHotwordPhraseId(int)\nclass BaseBuilder extends java.lang.Object implements []") @Deprecated private void __metadata() {} diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index 259012f5eb30..8d95c0251203 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -1458,7 +1458,7 @@ public abstract class WallpaperService extends Service { com.android.internal.R.dimen.config_wallpaperDimAmount); mWallpaperDimAmount = mDefaultDimAmount; mPreviousWallpaperDimAmount = mWallpaperDimAmount; - mDisplayState = mDisplay.getState(); + mDisplayState = mDisplay.getCommittedState(); if (DEBUG) Log.v(TAG, "onCreate(): " + this); Trace.beginSection("WPMS.Engine.onCreate"); @@ -1548,7 +1548,8 @@ public abstract class WallpaperService extends Service { return; } if (!mDestroyed) { - mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : mDisplay.getState(); + mDisplayState = mDisplay == null ? Display.STATE_UNKNOWN : + mDisplay.getCommittedState(); boolean visible = mVisible && mDisplayState != Display.STATE_OFF; if (DEBUG) { Log.v( diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl index 48b112d0816d..83de2a0fafbe 100644 --- a/core/java/android/view/IWindowSession.aidl +++ b/core/java/android/view/IWindowSession.aidl @@ -294,7 +294,7 @@ interface IWindowSession { * an input channel where the client can receive input. */ void grantInputChannel(int displayId, in SurfaceControl surface, in IWindow window, - in IBinder hostInputToken, int flags, int privateFlags, int type, + in IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type, in IBinder windowToken, in IBinder focusGrantToken, String inputHandleName, out InputChannel outInputChannel); @@ -302,7 +302,8 @@ interface IWindowSession { * Update the flags on an input channel associated with a particular surface. */ oneway void updateInputChannel(in IBinder channelToken, int displayId, - in SurfaceControl surface, int flags, int privateFlags, in Region region); + in SurfaceControl surface, int flags, int privateFlags, int inputFeatures, + in Region region); /** * Transfer window focus to an embedded window if the calling window has focus. diff --git a/core/java/android/view/InputEvent.java b/core/java/android/view/InputEvent.java index cb9e746543f6..0b4adaeb9890 100644 --- a/core/java/android/view/InputEvent.java +++ b/core/java/android/view/InputEvent.java @@ -207,7 +207,7 @@ public abstract class InputEvent implements Parcelable { * * @hide */ - public abstract long getEventTimeNano(); + public abstract long getEventTimeNanos(); /** * Marks the input event as being canceled. diff --git a/core/java/android/view/InputEventConsistencyVerifier.java b/core/java/android/view/InputEventConsistencyVerifier.java index c0a3cec4547a..5c38a1597433 100644 --- a/core/java/android/view/InputEventConsistencyVerifier.java +++ b/core/java/android/view/InputEventConsistencyVerifier.java @@ -721,7 +721,7 @@ public final class InputEventConsistencyVerifier { private static void appendEvent(StringBuilder message, int index, InputEvent event, boolean unhandled) { - message.append(index).append(": sent at ").append(event.getEventTimeNano()); + message.append(index).append(": sent at ").append(event.getEventTimeNanos()); message.append(", "); if (unhandled) { message.append("(unhandled) "); diff --git a/core/java/android/view/KeyEvent.java b/core/java/android/view/KeyEvent.java index ab8134559189..2af025469df4 100644 --- a/core/java/android/view/KeyEvent.java +++ b/core/java/android/view/KeyEvent.java @@ -2711,7 +2711,7 @@ public class KeyEvent extends InputEvent implements Parcelable { * @hide */ @Override - public final long getEventTimeNano() { + public final long getEventTimeNanos() { return mEventTime; } diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index a71ab8a5852c..39029896331c 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -2440,13 +2440,31 @@ public final class MotionEvent extends InputEvent implements Parcelable { * * @hide */ - @Override - @UnsupportedAppUsage + @UnsupportedAppUsage(publicAlternatives = + "Use {@link #getEventTimeNanos()} public API instead.", + maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) public final long getEventTimeNano() { return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT); } /** + * Retrieve the time this event occurred, + * in the {@link android.os.SystemClock#uptimeMillis} time base but with + * nanosecond precision. + * <p> + * The value is in nanosecond precision but it may not have nanosecond accuracy. + * </p> + * + * @return Returns the time this event occurred, + * in the {@link android.os.SystemClock#uptimeMillis} time base but with + * nanosecond precision. + */ + @Override + public long getEventTimeNanos() { + return nativeGetEventTimeNanos(mNativePtr, HISTORY_CURRENT); + } + + /** * Equivalent to {@link #getX(int)} for pointer index 0 (regardless of the * pointer identifier). * @@ -3104,10 +3122,8 @@ public final class MotionEvent extends InputEvent implements Parcelable { * * @see #getHistorySize * @see #getEventTime - * - * @hide */ - public final long getHistoricalEventTimeNano(int pos) { + public long getHistoricalEventTimeNanos(int pos) { return nativeGetEventTimeNanos(mNativePtr, pos); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index fb25e7a6bfe1..d1f9fbded003 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -417,7 +417,8 @@ public final class ViewRootImpl implements ViewParent, private boolean mUseBLASTAdapter; private boolean mForceDisableBLAST; - private boolean mFastScrollSoundEffectsEnabled; + /** lazily-initialized in getAudioManager() */ + private boolean mFastScrollSoundEffectsEnabled = false; /** * Signals that compatibility booleans have been initialized according to @@ -1028,8 +1029,6 @@ public final class ViewRootImpl implements ViewParent, loadSystemProperties(); mImeFocusController = new ImeFocusController(this); - AudioManager audioManager = mContext.getSystemService(AudioManager.class); - mFastScrollSoundEffectsEnabled = audioManager.areNavigationRepeatSoundEffectsEnabled(); mScrollCaptureRequestTimeout = SCROLL_CAPTURE_REQUEST_TIMEOUT_MILLIS; mOnBackInvokedDispatcher = new WindowOnBackInvokedDispatcher(context); @@ -8340,6 +8339,7 @@ public final class ViewRootImpl implements ViewParent, } if (mAudioManager == null) { mAudioManager = (AudioManager) mView.getContext().getSystemService(Context.AUDIO_SERVICE); + mFastScrollSoundEffectsEnabled = mAudioManager.areNavigationRepeatSoundEffectsEnabled(); } return mAudioManager; } @@ -9171,7 +9171,7 @@ public final class ViewRootImpl implements ViewParent, * Represents a pending input event that is waiting in a queue. * * Input events are processed in serial order by the timestamp specified by - * {@link InputEvent#getEventTimeNano()}. In general, the input dispatcher delivers + * {@link InputEvent#getEventTimeNanos()}. In general, the input dispatcher delivers * one input event to the application at a time and waits for the application * to finish handling it before delivering the next one. * @@ -9361,7 +9361,7 @@ public final class ViewRootImpl implements ViewParent, if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { Trace.traceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent src=0x" + Integer.toHexString(q.mEvent.getSource()) + " eventTimeNano=" - + q.mEvent.getEventTimeNano() + " id=0x" + + q.mEvent.getEventTimeNanos() + " id=0x" + Integer.toHexString(q.mEvent.getId())); } try { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index cc846e3537e1..cda1f3adb9a4 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -3616,9 +3616,20 @@ public interface WindowManager extends ViewManager { /** * The preferred refresh rate for the window. * <p> - * This must be one of the supported refresh rates obtained for the display(s) the window - * is on. The selected refresh rate will be applied to the display's default mode. + * Before API 34, this must be one of the supported refresh rates obtained + * for the display(s) the window is on. The selected refresh rate will be + * applied to the display's default mode. * <p> + * Starting API 34, this value is not limited to the supported refresh rates + * obtained from the display(s) for the window: it can be any refresh rate + * the window intends to run at. Any refresh rate can be provided as the + * preferred window refresh rate. The OS will select the refresh rate that + * best matches the {@link #preferredRefreshRate}. + * <p> + * Setting this value is the equivalent of calling {@link Surface#setFrameRate} with ( + * preferred_frame_rate, + * {@link Surface#FRAME_RATE_COMPATIBILITY_DEFAULT}, + * {@link Surface#CHANGE_FRAME_RATE_ONLY_IF_SEAMLESS}). * This should be used in favor of {@link LayoutParams#preferredDisplayModeId} for * applications that want to specify the refresh rate, but do not want to specify a * preference for any other displayMode properties (e.g., resolution). diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java index b77b7a698bd0..0560cafe3e52 100644 --- a/core/java/android/view/WindowlessWindowManager.java +++ b/core/java/android/view/WindowlessWindowManager.java @@ -139,7 +139,7 @@ public class WindowlessWindowManager implements IWindowSession { try { mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, state.mSurfaceControl, state.mParams.flags, state.mParams.privateFlags, - state.mInputRegion); + state.mParams.inputFeatures, state.mInputRegion); } catch (RemoteException e) { Log.e(TAG, "Failed to update surface input channel: ", e); } @@ -189,12 +189,13 @@ public class WindowlessWindowManager implements IWindowSession { mRealWm.grantInputChannel(displayId, new SurfaceControl(sc, "WindowlessWindowManager.addToDisplay"), window, mHostInputToken, - attrs.flags, attrs.privateFlags, attrs.type, attrs.token, - mFocusGrantToken, attrs.getTitle().toString(), outInputChannel); + attrs.flags, attrs.privateFlags, attrs.inputFeatures, attrs.type, + attrs.token, mFocusGrantToken, attrs.getTitle().toString(), + outInputChannel); } else { mRealWm.grantInputChannel(displayId, sc, window, mHostInputToken, attrs.flags, - attrs.privateFlags, attrs.type, attrs.token, mFocusGrantToken, - attrs.getTitle().toString(), outInputChannel); + attrs.privateFlags, attrs.inputFeatures, attrs.type, attrs.token, + mFocusGrantToken, attrs.getTitle().toString(), outInputChannel); } } catch (RemoteException e) { Log.e(TAG, "Failed to grant input to surface: ", e); @@ -381,16 +382,19 @@ public class WindowlessWindowManager implements IWindowSession { outMergedConfiguration.setConfiguration(mConfiguration, mConfiguration); } - if ((attrChanges & WindowManager.LayoutParams.FLAGS_CHANGED) != 0 - && state.mInputChannelToken != null) { + final int inputChangeMask = WindowManager.LayoutParams.FLAGS_CHANGED + | WindowManager.LayoutParams.INPUT_FEATURES_CHANGED; + if ((attrChanges & inputChangeMask) != 0 && state.mInputChannelToken != null) { try { - if(mRealWm instanceof IWindowSession.Stub) { + if (mRealWm instanceof IWindowSession.Stub) { mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, new SurfaceControl(sc, "WindowlessWindowManager.relayout"), - attrs.flags, attrs.privateFlags, state.mInputRegion); + attrs.flags, attrs.privateFlags, attrs.inputFeatures, + state.mInputRegion); } else { mRealWm.updateInputChannel(state.mInputChannelToken, state.mDisplayId, sc, - attrs.flags, attrs.privateFlags, state.mInputRegion); + attrs.flags, attrs.privateFlags, attrs.inputFeatures, + state.mInputRegion); } } catch (RemoteException e) { Log.e(TAG, "Failed to update surface input channel: ", e); @@ -560,14 +564,14 @@ public class WindowlessWindowManager implements IWindowSession { @Override public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, - IBinder hostInputToken, int flags, int privateFlags, int type, + IBinder hostInputToken, int flags, int privateFlags, int inputFeatures, int type, IBinder windowToken, IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) { } @Override public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface, - int flags, int privateFlags, Region region) { + int flags, int privateFlags, int inputFeatures, Region region) { } @Override diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index ca57c84a1631..fceee4e01799 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -189,6 +189,9 @@ public class Toast { /** * Show the view for the specified duration. + * + * <p>Note that toasts being sent from the background are rate limited, so avoid sending such + * toasts in quick succession. */ public void show() { if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { diff --git a/core/java/android/window/ITaskOrganizerController.aidl b/core/java/android/window/ITaskOrganizerController.aidl index 0032b9ce0512..e10f7c838c74 100644 --- a/core/java/android/window/ITaskOrganizerController.aidl +++ b/core/java/android/window/ITaskOrganizerController.aidl @@ -73,11 +73,17 @@ interface ITaskOrganizerController { /** * Controls whether ignore orientation request logic in {@link - * com.android.server.wm.DisplayArea} is disabled at runtime. + * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some + * requested orientations to others. * * @param isDisabled when {@code true}, the system always ignores the value of {@link * com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app * requested orientation is respected. + * @param fromOrientations The orientations we want to map to the correspondent orientations + * in toOrientation. + * @param toOrientations The orientations we map to the ones in fromOrientations at the same + * index */ - void setIsIgnoreOrientationRequestDisabled(boolean isDisabled); + void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled, + in int[] fromOrientations, in int[] toOrientations); } diff --git a/core/java/android/window/TaskConstants.java b/core/java/android/window/TaskConstants.java index c40384027adf..3a04198a3add 100644 --- a/core/java/android/window/TaskConstants.java +++ b/core/java/android/window/TaskConstants.java @@ -80,14 +80,6 @@ public class TaskConstants { public static final int TASK_CHILD_LAYER_TASK_OVERLAY = 5 * TASK_CHILD_LAYER_REGION_SIZE; /** - * Legacy machanism to force an activity to the top of the task (i.e. for work profile - * comfirmation). - * @hide - */ - public static final int TASK_CHILD_LAYER_TASK_OVERLAY_ACTIVITIES = - 6 * TASK_CHILD_LAYER_REGION_SIZE; - - /** * Z-orders of task child layers other than activities, task fragments and layers interleaved * with them, e.g. IME windows. [-10000, 10000) is reserved for these layers. * @hide @@ -99,8 +91,7 @@ public class TaskConstants { TASK_CHILD_LAYER_LETTERBOX_EDUCATION, TASK_CHILD_LAYER_WINDOW_DECORATIONS, TASK_CHILD_LAYER_RECENTS_ANIMATION_PIP_OVERLAY, - TASK_CHILD_LAYER_TASK_OVERLAY, - TASK_CHILD_LAYER_TASK_OVERLAY_ACTIVITIES + TASK_CHILD_LAYER_TASK_OVERLAY }) public @interface TaskChildLayer {} } diff --git a/core/java/android/window/TaskOrganizer.java b/core/java/android/window/TaskOrganizer.java index d4728c1187d7..2913faf9d74d 100644 --- a/core/java/android/window/TaskOrganizer.java +++ b/core/java/android/window/TaskOrganizer.java @@ -267,17 +267,24 @@ public class TaskOrganizer extends WindowOrganizer { /** * Controls whether ignore orientation request logic in {@link - * com.android.server.wm.DisplayArea} is disabled at runtime. + * com.android.server.wm.DisplayArea} is disabled at runtime and how to optionally map some + * requested orientation to others. * - * @param isDisabled when {@code true}, the system always ignores the value of {@link - * com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} and app - * requested orientation is respected. + * @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the + * value of {@link com.android.server.wm.DisplayArea#getIgnoreOrientationRequest} + * and app requested orientation is respected. + * @param fromOrientations The orientations we want to map to the correspondent orientations + * in toOrientation. + * @param toOrientations The orientations we map to the ones in fromOrientations at the same + * index * @hide */ @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS) - public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) { + public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled, + @Nullable int[] fromOrientations, @Nullable int[] toOrientations) { try { - mTaskOrganizerController.setIsIgnoreOrientationRequestDisabled(isDisabled); + mTaskOrganizerController.setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled, + fromOrientations, toOrientations); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/jank/EventLogTags.logtags b/core/java/com/android/internal/jank/EventLogTags.logtags index ad47b810aa50..66ee131badac 100644 --- a/core/java/com/android/internal/jank/EventLogTags.logtags +++ b/core/java/com/android/internal/jank/EventLogTags.logtags @@ -3,7 +3,7 @@ option java_package com.android.internal.jank; # Marks a request to start tracing a CUJ. Doesn't mean the request was executed. -37001 jank_cuj_events_begin_request (CUJ Type|1|5),(Unix Time Ns|2|3),(Elapsed Time Ns|2|3),(Uptime Ns|2|3) +37001 jank_cuj_events_begin_request (CUJ Type|1|5),(Unix Time Ns|2|3),(Elapsed Time Ns|2|3),(Uptime Ns|2|3),(Tag|3) # Marks a request to end tracing a CUJ. Doesn't mean the request was executed. 37002 jank_cuj_events_end_request (CUJ Type|1|5),(Unix Time Ns|2|3),(Elapsed Time Ns|2|3),(Uptime Time Ns|2|3) # Marks a request to cancel tracing a CUJ. Doesn't mean the request was executed. diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index 928a09700e2e..6344568480b7 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -590,7 +590,7 @@ public class InteractionJankMonitor { final Configuration config = builder.build(); postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> { EventLogTags.writeJankCujEventsBeginRequest( - config.mCujType, unixNanos, elapsedNanos, realtimeNanos); + config.mCujType, unixNanos, elapsedNanos, realtimeNanos, config.mTag); }); final TrackerResult result = new TrackerResult(); final boolean success = config.getHandler().runWithScissors( diff --git a/core/proto/android/server/windowmanagertransitiontrace.proto b/core/proto/android/server/windowmanagertransitiontrace.proto index ab8738444aaa..9e53a9162429 100644 --- a/core/proto/android/server/windowmanagertransitiontrace.proto +++ b/core/proto/android/server/windowmanagertransitiontrace.proto @@ -36,8 +36,10 @@ message TransitionTraceProto { MAGIC_NUMBER_H = 0x45434152; /* RACE (little-endian ASCII) */ } - required fixed64 magic_number = 1; /* Must be the first field, set to value in MagicNumber */ - repeated Transition sent_transitions = 2; + // Must be the first field, set to value in MagicNumber + required fixed64 magic_number = 1; + // Transitions that don't have a finish time are considered aborted + repeated Transition finished_transitions = 2; // Additional debugging info only collected and dumped when explicitly requested to trace repeated TransitionState transition_states = 3; @@ -50,7 +52,9 @@ message Transition { required uint64 finish_transaction_id = 3; required int64 create_time_ns = 4; required int64 send_time_ns = 5; - repeated Target targets = 6; + optional int64 finish_time_ns = 6; // consider aborted if not provided + required int32 type = 7; + repeated Target targets = 8; } message Target { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 70a1354e3046..55edc245b7ce 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -824,6 +824,9 @@ <protected-broadcast android:name="android.companion.virtual.action.VIRTUAL_DEVICE_REMOVED" /> <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_START_PREVIEW" /> <protected-broadcast android:name="com.android.internal.intent.action.FLASH_NOTIFICATION_STOP_PREVIEW" /> + <protected-broadcast android:name="android.app.admin.action.DEVICE_FINANCING_STATE_CHANGED" /> + <protected-broadcast android:name="android.app.admin.action.DEVICE_POLICY_SET_RESULT" /> + <protected-broadcast android:name="android.app.admin.action.DEVICE_POLICY_CHANGED" /> <!-- ====================================================================== --> <!-- RUNTIME PERMISSIONS --> @@ -4035,8 +4038,7 @@ android:description="@string/permdesc_setWallpaperHints" android:protectionLevel="normal" /> - <!-- Allow the app to read the system wallpaper image without - holding the READ_EXTERNAL_STORAGE permission. + <!-- Allow the app to read the system and lock wallpaper images. <p>Not for use by third-party applications. @hide @SystemApi diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 2ed181771cb7..220a1935f51a 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -720,14 +720,13 @@ display is powered on at the same time. --> <bool name="config_supportsConcurrentInternalDisplays">true</bool> - <!-- Map of DeviceState to rotation lock setting. Each entry must be in the format - "key:value", for example: "0:1". - The keys are device states, and the values are one of - Settings.Secure.DeviceStateRotationLockSetting. - Any device state that doesn't have a default set here will be treated as - DEVICE_STATE_ROTATION_LOCK_IGNORED meaning it will not have its own rotation lock setting. - If this map is missing, the feature is disabled and only one global rotation lock setting - will apply, regardless of device state. --> + <!-- Map of device posture to rotation lock setting. Each entry must be in the format + "key:value", or "key:value:fallback_key" for example: "0:1" or "2:0:1". The keys are one of + Settings.Secure.DeviceStateRotationLockKey, and the values are one of + Settings.Secure.DeviceStateRotationLockSetting. + The fallback is a key to a device posture that can be specified when the value is + Settings.Secure.DEVICE_STATE_ROTATION_LOCK_IGNORED. + --> <string-array name="config_perDeviceStateRotationLockDefaults" /> <!-- Dock behavior --> diff --git a/core/tests/BroadcastRadioTests/AndroidManifest.xml b/core/tests/BroadcastRadioTests/AndroidManifest.xml index 8f655efe4f07..fef3d16a183b 100644 --- a/core/tests/BroadcastRadioTests/AndroidManifest.xml +++ b/core/tests/BroadcastRadioTests/AndroidManifest.xml @@ -18,6 +18,8 @@ package="com.android.frameworks.broadcastradiotests"> <uses-permission android:name="android.permission.ACCESS_BROADCAST_RADIO" /> + <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE" /> + <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG" /> <application android:debuggable="true"> <uses-library android:name="android.test.runner" /> diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java index 75f8c954be5e..7c3d2f2138a4 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/ProgramListTest.java @@ -580,7 +580,7 @@ public final class ProgramListTest { doAnswer(invocation -> { mTunerCallback = (ITunerCallback) invocation.getArguments()[3]; return mTunerMock; - }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()); + }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any()); mRadioTuner = radioManager.openTuner(/* moduleId= */ 0, band, /* withAudio= */ true, mTunerCallbackMock, /* handler= */ null); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java index ce3e019520c8..b9f4c3fa0a77 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/RadioManagerTest.java @@ -30,7 +30,6 @@ import static org.mockito.Mockito.when; import android.annotation.Nullable; import android.content.Context; import android.content.pm.ApplicationInfo; -import android.os.Build; import android.os.Parcel; import android.os.RemoteException; import android.util.ArrayMap; @@ -50,8 +49,6 @@ import java.util.Set; @RunWith(MockitoJUnitRunner.class) public final class RadioManagerTest { - private static final int TEST_TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; - private static final int REGION = RadioManager.REGION_ITU_2; private static final int FM_LOWER_LIMIT = 87500; private static final int FM_UPPER_LIMIT = 108000; @@ -1043,14 +1040,13 @@ public final class RadioManagerTest { mRadioManager.openTuner(moduleId, FM_BAND_CONFIG, withAudio, mCallbackMock, /* handler= */ null); - verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any(), - anyInt()); + verify(mRadioServiceMock).openTuner(eq(moduleId), eq(FM_BAND_CONFIG), eq(withAudio), any()); } @Test public void openTuner_whenServiceDied_returnsNull() throws Exception { createRadioManager(); - when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any(), anyInt())) + when(mRadioServiceMock.openTuner(anyInt(), any(), anyBoolean(), any())) .thenThrow(new RemoteException()); RadioTuner nullTuner = mRadioManager.openTuner(/* moduleId= */ 0, FM_BAND_CONFIG, @@ -1166,7 +1162,6 @@ public final class RadioManagerTest { } private void createRadioManager() throws RemoteException { - mApplicationInfo.targetSdkVersion = TEST_TARGET_SDK_VERSION; when(mContextMock.getApplicationInfo()).thenReturn(mApplicationInfo); when(mRadioServiceMock.listModules()).thenReturn(Arrays.asList(AMFM_PROPERTIES)); when(mRadioServiceMock.addAnnouncementListener(any(), any())).thenReturn(mCloseHandleMock); diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java index 8b257e832e43..c7b82b1caca6 100644 --- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java +++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/TunerAdapterTest.java @@ -86,7 +86,7 @@ public final class TunerAdapterTest { doAnswer(invocation -> { mTunerCallback = (ITunerCallback) invocation.getArguments()[3]; return mTunerMock; - }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any(), anyInt()); + }).when(mRadioServiceMock).openTuner(anyInt(), any(), anyBoolean(), any()); doAnswer(invocation -> { ProgramSelector program = (ProgramSelector) invocation.getArguments()[0]; diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java index 16c1499a2775..da51ba45174c 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceAidlImplTest.java @@ -24,7 +24,6 @@ import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -35,7 +34,6 @@ import android.hardware.radio.ICloseHandle; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; -import android.os.Build; import android.os.IBinder; import android.os.ServiceManager; @@ -58,7 +56,6 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas "android.hardware.broadcastradio.IBroadcastRadio/amfm"; private static final String DAB_SERVICE_NAME = "android.hardware.broadcastradio.IBroadcastRadio/dab"; - private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; private IRadioServiceAidlImpl mAidlImpl; @@ -86,7 +83,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas doNothing().when(mServiceMock).enforcePolicyAccess(); when(mHalMock.listModules()).thenReturn(List.of(mModuleMock)); - when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any(), eq(TARGET_SDK_VERSION))) + when(mHalMock.openSession(anyInt(), any(), anyBoolean(), any())) .thenReturn(mTunerMock); when(mHalMock.addAnnouncementListener(any(), any())).thenReturn(mICloseHandle); @@ -118,7 +115,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas @Test public void openTuner_forAidlImpl() throws Exception { ITuner tuner = mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock, - /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION); + /* withAudio= */ true, mTunerCallbackMock); assertWithMessage("Tuner opened in AIDL HAL") .that(tuner).isEqualTo(mTunerMock); @@ -128,7 +125,7 @@ public final class IRadioServiceAidlImplTest extends ExtendedRadioMockitoTestCas public void openTuner_withNullCallbackForAidlImpl_fails() throws Exception { IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> mAidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock, - /* withAudio= */ true, /* callback= */ null, TARGET_SDK_VERSION)); + /* withAudio= */ true, /* callback= */ null)); assertWithMessage("Exception for opening tuner with null callback") .that(thrown).hasMessageThat().contains("Callback must not be null"); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java index 164c9aff127a..20bc8d404d76 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/IRadioServiceHidlImplTest.java @@ -33,7 +33,6 @@ import android.hardware.radio.ICloseHandle; import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; -import android.os.Build; import org.junit.Before; import org.junit.Test; @@ -51,7 +50,6 @@ public final class IRadioServiceHidlImplTest { private static final int HAL1_MODULE_ID = 0; private static final int[] ENABLE_TYPES = new int[]{Announcement.TYPE_TRAFFIC}; - private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; private IRadioServiceHidlImpl mHidlImpl; @@ -106,7 +104,7 @@ public final class IRadioServiceHidlImplTest { @Test public void openTuner_withHal1ModuleId_forHidlImpl() throws Exception { ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID, mBandConfigMock, - /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION); + /* withAudio= */ true, mTunerCallbackMock); assertWithMessage("Tuner opened in HAL 1") .that(tuner).isEqualTo(mHal1TunerMock); @@ -115,7 +113,7 @@ public final class IRadioServiceHidlImplTest { @Test public void openTuner_withHal2ModuleId_forHidlImpl() throws Exception { ITuner tuner = mHidlImpl.openTuner(HAL1_MODULE_ID + 1, mBandConfigMock, - /* withAudio= */ true, mTunerCallbackMock, TARGET_SDK_VERSION); + /* withAudio= */ true, mTunerCallbackMock); assertWithMessage("Tuner opened in HAL 2") .that(tuner).isEqualTo(mHal2TunerMock); @@ -125,7 +123,7 @@ public final class IRadioServiceHidlImplTest { public void openTuner_withNullCallbackForHidlImpl_fails() throws Exception { NullPointerException thrown = assertThrows(NullPointerException.class, () -> mHidlImpl.openTuner(/* moduleId= */ 0, mBandConfigMock, - /* withAudio= */ true, /* callback= */ null, TARGET_SDK_VERSION)); + /* withAudio= */ true, /* callback= */ null)); assertWithMessage("Exception for opening tuner with null callback") .that(thrown).hasMessageThat().contains("Callback must not be null"); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java index 161ac2dc6aa0..3e9e9922431b 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/RadioServiceUserControllerTest.java @@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.Mockito.when; import android.app.ActivityManager; +import android.app.compat.CompatChanges; import android.os.Binder; import android.os.UserHandle; @@ -46,8 +47,8 @@ public final class RadioServiceUserControllerTest extends ExtendedRadioMockitoTe @Override protected void initializeSession(StaticMockitoSessionBuilder builder) { - builder.spyStatic(ActivityManager.class) - .spyStatic(Binder.class); + builder.spyStatic(ActivityManager.class).spyStatic(Binder.class) + .spyStatic(CompatChanges.class); } @Before diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java index 98103f6eddd3..22f3bd4abe11 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImplTest.java @@ -36,7 +36,6 @@ import android.hardware.radio.ITuner; import android.hardware.radio.ITunerCallback; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioTuner; -import android.os.Build; import android.os.IBinder; import android.os.IServiceCallback; import android.os.RemoteException; @@ -55,8 +54,6 @@ import java.util.Arrays; public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTestCase { - private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; - private static final int FM_RADIO_MODULE_ID = 0; private static final int DAB_RADIO_MODULE_ID = 1; private static final ArrayList<String> SERVICE_LIST = @@ -140,8 +137,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes createBroadcastRadioService(); ITuner session = mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID, - /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock, - TARGET_SDK_VERSION); + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); assertWithMessage("Session opened in FM radio module") .that(session).isEqualTo(mFmTunerSessionMock); @@ -152,8 +148,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes createBroadcastRadioService(); ITuner session = mBroadcastRadioService.openSession(DAB_RADIO_MODULE_ID + 1, - /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock, - TARGET_SDK_VERSION); + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock); assertWithMessage("Session opened with id not found").that(session).isNull(); } @@ -165,8 +160,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes IllegalStateException thrown = assertThrows(IllegalStateException.class, () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID, - /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock, - TARGET_SDK_VERSION)); + /* legacyConfig= */ null, /* withAudio= */ true, mTunerCallbackMock)); assertWithMessage("Exception for opening session by non-current user") .that(thrown).hasMessageThat().contains("Cannot open session for non-current user"); @@ -178,8 +172,7 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, () -> mBroadcastRadioService.openSession(FM_RADIO_MODULE_ID, - /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock, - TARGET_SDK_VERSION)); + /* legacyConfig= */ null, /* withAudio= */ false, mTunerCallbackMock)); assertWithMessage("Exception for opening session without audio") .that(thrown).hasMessageThat().contains("not supported"); @@ -247,7 +240,6 @@ public final class BroadcastRadioServiceImplTest extends ExtendedRadioMockitoTes return null; }).when(mFmBinderMock).linkToDeath(any(), anyInt()); - when(mFmRadioModuleMock.openSession(eq(mTunerCallbackMock), eq(TARGET_SDK_VERSION))) - .thenReturn(mFmTunerSessionMock); + when(mFmRadioModuleMock.openSession(mTunerCallbackMock)).thenReturn(mFmTunerSessionMock); } } diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java index 5d0e07613a98..ba0579171c43 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java @@ -16,6 +16,9 @@ package com.android.server.broadcastradio.aidl; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; + +import android.app.compat.CompatChanges; import android.hardware.broadcastradio.AmFmBandRange; import android.hardware.broadcastradio.AmFmRegionConfig; import android.hardware.broadcastradio.DabTableEntry; @@ -29,17 +32,23 @@ import android.hardware.radio.Announcement; import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; -import android.os.Build; + +import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder; +import com.android.server.broadcastradio.ExtendedRadioMockitoTestCase; import com.google.common.truth.Expect; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import java.util.Map; import java.util.Set; -public final class ConversionUtilsTest { +public final class ConversionUtilsTest extends ExtendedRadioMockitoTestCase { + + private static final int U_APP_UID = 1001; + private static final int T_APP_UID = 1002; private static final int FM_LOWER_LIMIT = 87_500; private static final int FM_UPPER_LIMIT = 108_000; @@ -118,16 +127,29 @@ public final class ConversionUtilsTest { @Rule public final Expect expect = Expect.create(); + @Override + protected void initializeSession(StaticMockitoSessionBuilder builder) { + builder.spyStatic(CompatChanges.class); + } + + @Before + public void setUp() { + doReturn(true).when(() -> CompatChanges.isChangeEnabled( + ConversionUtils.RADIO_U_VERSION_REQUIRED, U_APP_UID)); + doReturn(false).when(() -> CompatChanges.isChangeEnabled( + ConversionUtils.RADIO_U_VERSION_REQUIRED, T_APP_UID)); + } + @Test public void isAtLeastU_withTSdkVersion_returnsFalse() { expect.withMessage("Target SDK version of T") - .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.TIRAMISU)).isFalse(); + .that(ConversionUtils.isAtLeastU(T_APP_UID)).isFalse(); } @Test public void isAtLeastU_withCurrentSdkVersion_returnsTrue() { expect.withMessage("Target SDK version of U") - .that(ConversionUtils.isAtLeastU(Build.VERSION_CODES.CUR_DEVELOPMENT)).isTrue(); + .that(ConversionUtils.isAtLeastU(U_APP_UID)).isTrue(); } @Test @@ -372,14 +394,14 @@ public final class ConversionUtilsTest { public void programSelectorMeetsSdkVersionRequirement_withLowerVersionId_returnsFalse() { expect.withMessage("Selector %s without required SDK version", TEST_DAB_SELECTOR) .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_DAB_SELECTOR, - Build.VERSION_CODES.TIRAMISU)).isFalse(); + T_APP_UID)).isFalse(); } @Test public void programSelectorMeetsSdkVersionRequirement_withRequiredVersionId_returnsTrue() { expect.withMessage("Selector %s with required SDK version", TEST_FM_SELECTOR) .that(ConversionUtils.programSelectorMeetsSdkVersionRequirement(TEST_FM_SELECTOR, - Build.VERSION_CODES.TIRAMISU)).isTrue(); + T_APP_UID)).isTrue(); } @Test @@ -389,7 +411,7 @@ public final class ConversionUtilsTest { expect.withMessage("Program info %s without required SDK version", dabProgramInfo) .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(dabProgramInfo, - Build.VERSION_CODES.TIRAMISU)).isFalse(); + T_APP_UID)).isFalse(); } @Test @@ -399,7 +421,7 @@ public final class ConversionUtilsTest { expect.withMessage("Program info %s with required SDK version", fmProgramInfo) .that(ConversionUtils.programInfoMeetsSdkVersionRequirement(fmProgramInfo, - Build.VERSION_CODES.TIRAMISU)).isTrue(); + T_APP_UID)).isTrue(); } @Test @@ -413,7 +435,7 @@ public final class ConversionUtilsTest { Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID)); ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk, - Build.VERSION_CODES.TIRAMISU); + T_APP_UID); expect.withMessage( "Purged state of the converted program list chunk with lower SDK version") @@ -441,7 +463,7 @@ public final class ConversionUtilsTest { Set.of(TEST_DAB_SID_EXT_ID, TEST_DAB_ENSEMBLE_ID, TEST_VENDOR_ID)); ProgramList.Chunk convertedChunk = ConversionUtils.convertChunkToTargetSdkVersion(chunk, - Build.VERSION_CODES.CUR_DEVELOPMENT); + U_APP_UID); expect.withMessage("Converted program list chunk with required SDK version") .that(convertedChunk).isEqualTo(chunk); diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java index 464ecb2b50a1..78b5a4ad71de 100644 --- a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java +++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/TunerSessionTest.java @@ -33,6 +33,7 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.compat.CompatChanges; import android.graphics.Bitmap; import android.hardware.broadcastradio.IBroadcastRadio; import android.hardware.broadcastradio.ITunerCallback; @@ -46,7 +47,6 @@ import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.hardware.radio.RadioTuner; -import android.os.Build; import android.os.ParcelableException; import android.os.RemoteException; import android.os.ServiceSpecificException; @@ -73,7 +73,6 @@ import java.util.Set; */ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { - private static final int TARGET_SDK_VERSION = Build.VERSION_CODES.CUR_DEVELOPMENT; private static final VerificationWithTimeout CALLBACK_TIMEOUT = timeout(/* millis= */ 200); private static final int SIGNAL_QUALITY = 90; @@ -125,11 +124,13 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Override protected void initializeSession(StaticMockitoSessionBuilder builder) { - builder.spyStatic(RadioServiceUserController.class); + builder.spyStatic(RadioServiceUserController.class).spyStatic(CompatChanges.class); } @Before public void setup() throws Exception { + doReturn(true).when(() -> CompatChanges.isChangeEnabled( + eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt())); doReturn(true).when(() -> RadioServiceUserController.isCurrentOrSystemUser()); mRadioModule = new RadioModule(mBroadcastRadioMock, @@ -341,7 +342,9 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { @Test public void tune_withLowerSdkVersion() throws Exception { - openAidlClients(/* numClients= */ 1, Build.VERSION_CODES.TIRAMISU); + doReturn(false).when(() -> CompatChanges.isChangeEnabled( + eq(ConversionUtils.RADIO_U_VERSION_REQUIRED), anyInt())); + openAidlClients(/* numClients= */ 1); ProgramSelector initialSel = AidlTestUtils.makeFmSelector(AM_FM_FREQUENCY_LIST[1]); RadioManager.ProgramInfo tuneInfo = AidlTestUtils.makeProgramInfo(initialSel, SIGNAL_QUALITY); @@ -1175,17 +1178,13 @@ public final class TunerSessionTest extends ExtendedRadioMockitoTestCase { .onParametersUpdated(parametersExpected); } } - private void openAidlClients(int numClients) throws Exception { - openAidlClients(numClients, TARGET_SDK_VERSION); - } - private void openAidlClients(int numClients, int targetSdkVersion) throws Exception { + private void openAidlClients(int numClients) throws Exception { mAidlTunerCallbackMocks = new android.hardware.radio.ITunerCallback[numClients]; mTunerSessions = new TunerSession[numClients]; for (int index = 0; index < numClients; index++) { mAidlTunerCallbackMocks[index] = mock(android.hardware.radio.ITunerCallback.class); - mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index], - targetSdkVersion); + mTunerSessions[index] = mRadioModule.openSession(mAidlTunerCallbackMocks[index]); } } diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java index 9b1f0cd9e8da..9a202ae4d176 100644 --- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java +++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java @@ -112,7 +112,7 @@ public class FaceManagerTest { mCaptor.getValue().onAllAuthenticatorsRegistered(mProps); List<FaceSensorPropertiesInternal> actual = mFaceManager.getSensorPropertiesInternal(); - assertThat(actual).isEqualTo(mProps); + assertThat(actual).containsExactlyElementsIn(mProps); verify(mService, never()).getSensorPropertiesInternal(any()); } diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java index f31903a73111..5058065710be 100644 --- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java +++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java @@ -113,7 +113,7 @@ public class FingerprintManagerTest { List<FingerprintSensorPropertiesInternal> actual = mFingerprintManager.getSensorPropertiesInternal(); - assertThat(actual).isEqualTo(mProps); + assertThat(actual).containsExactlyElementsIn(mProps); verify(mService, never()).getSensorPropertiesInternal(any()); } diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index aa1853f50028..1ea20f162680 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -803,51 +803,51 @@ public class DeviceConfigTest { try { // Ensure the device starts in a known state. - DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE); // Assert starting state. assertThat(DeviceConfig.getSyncDisabledMode()) - .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_NONE); + .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE); assertThat(DeviceConfig.setProperties(properties1)).isTrue(); assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE)) .isEqualTo(VALUE); // Test disabled (persistent). Persistence is not actually tested, that would require // a host test. - DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_PERSISTENT); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT); assertThat(DeviceConfig.getSyncDisabledMode()) - .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_PERSISTENT); + .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT); assertThat(DeviceConfig.setProperties(properties2)).isFalse(); assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE)) .isEqualTo(VALUE); // Return to not disabled. - DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE); assertThat(DeviceConfig.getSyncDisabledMode()) - .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_NONE); + .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE); assertThat(DeviceConfig.setProperties(properties2)).isTrue(); assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE)) .isEqualTo(VALUE2); // Test disabled (persistent). Absence of persistence is not actually tested, that would // require a host test. - DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT); assertThat(DeviceConfig.getSyncDisabledMode()) - .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT); + .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT); assertThat(DeviceConfig.setProperties(properties1)).isFalse(); assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE)) .isEqualTo(VALUE2); // Return to not disabled. - DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE); assertThat(DeviceConfig.getSyncDisabledMode()) - .isEqualTo(Settings.Config.SYNC_DISABLED_MODE_NONE); + .isEqualTo(DeviceConfig.SYNC_DISABLED_MODE_NONE); assertThat(DeviceConfig.setProperties(properties1)).isTrue(); assertThat(DeviceConfig.getProperties(NAMESPACE, KEY).getString(KEY, DEFAULT_VALUE)) .isEqualTo(VALUE); } finally { // Try to return to the default sync disabled state in case of failure. - DeviceConfig.setSyncDisabledMode(Settings.Config.SYNC_DISABLED_MODE_NONE); + DeviceConfig.setSyncDisabledMode(DeviceConfig.SYNC_DISABLED_MODE_NONE); // NAMESPACE will be cleared by cleanUp() } diff --git a/data/etc/com.android.systemui.xml b/data/etc/com.android.systemui.xml index fe639ffb1cc6..922dbb5fef38 100644 --- a/data/etc/com.android.systemui.xml +++ b/data/etc/com.android.systemui.xml @@ -51,6 +51,7 @@ <permission name="android.permission.READ_NETWORK_USAGE_HISTORY"/> <permission name="android.permission.READ_PRIVILEGED_PHONE_STATE"/> <permission name="android.permission.READ_PRECISE_PHONE_STATE"/> + <permission name="android.permission.READ_WALLPAPER_INTERNAL"/> <permission name="android.permission.REAL_GET_TASKS"/> <permission name="android.permission.REQUEST_NETWORK_SCORES"/> <permission name="android.permission.RECEIVE_MEDIA_RESOURCE_USAGE"/> diff --git a/data/fonts/fonts.xml b/data/fonts/fonts.xml index e7ddc88537df..0563519fe419 100644 --- a/data/fonts/fonts.xml +++ b/data/fonts/fonts.xml @@ -251,7 +251,7 @@ <alias name="courier new" to="serif-monospace" /> <family name="casual"> - <font weight="400" style="normal">ComingSoon.ttf</font> + <font weight="400" style="normal" postScriptName="ComingSoon-Regular">ComingSoon.ttf</font> </family> <family name="cursive"> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java index 22b841a338c1..913239f74bf2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationRunner.java @@ -75,7 +75,7 @@ class BackAnimationRunner { }; mWaitingAnimation = false; try { - mRunner.onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers, + getRunner().onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers, nonApps, callback); } catch (RemoteException e) { Log.w(TAG, "Failed call onAnimationStart", e); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING new file mode 100644 index 000000000000..837d5ff3b073 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/TEST_MAPPING @@ -0,0 +1,32 @@ +{ + "presubmit": [ + { + "name": "WMShellUnitTests", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "include-filter": "com.android.wm.shell.back" + } + ] + }, + { + "name": "CtsWindowManagerDeviceTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "include-filter": "android.server.wm.BackGestureInvokedTest" + }, + { + "include-filter": "android.server.wm.BackNavigationTests" + }, + { + "include-filter": "android.server.wm.OnBackInvokedCallbackGestureTest" + } + ] + } + ] +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index e5a4362e5bf0..9ccd6ebc51e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -229,6 +229,7 @@ public class BubbleExpandedView extends LinearLayout { options.setLaunchedFromBubble(true); options.setPendingIntentBackgroundActivityStartMode( MODE_BACKGROUND_ACTIVITY_START_ALLOWED); + options.setPendingIntentBackgroundActivityLaunchAllowedByPermission(true); Intent fillInIntent = new Intent(); // Apply flags to make behaviour match documentLaunchMode=always. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java index 2d84d211e30a..318a49a8de31 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizer.java @@ -21,6 +21,8 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE; +import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE; import static android.view.Display.DEFAULT_DISPLAY; import android.app.ActivityManager; @@ -33,6 +35,7 @@ import android.graphics.Rect; import android.os.Binder; import android.os.Handler; import android.os.IBinder; +import android.view.Display; import android.view.InsetsSource; import android.view.InsetsState; import android.view.SurfaceControl; @@ -44,6 +47,7 @@ import android.window.WindowContainerTransaction; import androidx.annotation.NonNull; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.common.DisplayController; @@ -80,6 +84,12 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { private final DisplayController mDisplayController; private final DisplayInsetsController mDisplayInsetsController; + /** + * The value of the {@link R.bool.config_reverseDefaultRotation} property which defines how + * {@link Display#getRotation} values are mapped to screen orientations + */ + private final boolean mReverseDefaultRotationEnabled; + @VisibleForTesting ActivityManager.RunningTaskInfo mLaunchRootTask; @VisibleForTesting @@ -188,6 +198,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { mDisplayInsetsController = displayInsetsController; mKidsModeSettingsObserver = kidsModeSettingsObserver; shellInit.addInitCallback(this::onInit, this); + mReverseDefaultRotationEnabled = context.getResources().getBoolean( + R.bool.config_reverseDefaultRotation); } public KidsModeTaskOrganizer( @@ -211,6 +223,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { mDisplayController = displayController; mDisplayInsetsController = displayInsetsController; shellInit.addInitCallback(this::onInit, this); + mReverseDefaultRotationEnabled = context.getResources().getBoolean( + R.bool.config_reverseDefaultRotation); } /** @@ -294,7 +308,14 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { // Needed since many Kids apps aren't optimised to support both orientations and it will be // hard for kids to understand the app compat mode. // TODO(229961548): Remove ignoreOrientationRequest exception for Kids Mode once possible. - setIsIgnoreOrientationRequestDisabled(true); + if (mReverseDefaultRotationEnabled) { + setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true, + /* fromOrientations */ new int[]{SCREEN_ORIENTATION_REVERSE_LANDSCAPE}, + /* toOrientations */ new int[]{SCREEN_ORIENTATION_LANDSCAPE}); + } else { + setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ true, + /* fromOrientations */ null, /* toOrientations */ null); + } final DisplayLayout displayLayout = mDisplayController.getDisplayLayout(DEFAULT_DISPLAY); if (displayLayout != null) { mDisplayWidth = displayLayout.width(); @@ -315,7 +336,8 @@ public class KidsModeTaskOrganizer extends ShellTaskOrganizer { @VisibleForTesting void disable() { - setIsIgnoreOrientationRequestDisabled(false); + setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled */ false, + /* fromOrientations */ null, /* toOrientations */ null); mDisplayInsetsController.removeInsetsChangedListener(DEFAULT_DISPLAY, mOnInsetsChangedListener); mDisplayController.removeDisplayWindowListener(mOnDisplaysChangedListener); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl index d961d8658b98..78de5f3e7a1f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/IPip.aidl @@ -75,4 +75,9 @@ interface IPip { * Sets the height and visibility of the Launcher keep clear area. */ oneway void setLauncherKeepClearAreaHeight(boolean visible, int height) = 6; + + /** + * Sets the app icon size in pixel used by Launcher + */ + oneway void setLauncherAppIconSize(int iconSizePx) = 7; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index fe8ede67c415..1187126f5bf1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -371,10 +371,11 @@ public class PipAnimationController { new PipContentOverlay.PipSnapshotOverlay(snapshot, sourceRectHint)); } - void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo) { + void setAppIconContentOverlay(Context context, Rect bounds, ActivityInfo activityInfo, + int appIconSizePx) { reattachContentOverlay( new PipContentOverlay.PipAppIconOverlay(context, bounds, - () -> new IconProvider(context).getIcon(activityInfo))); + new IconProvider(context).getIcon(activityInfo), appIconSizePx)); } private void reattachContentOverlay(PipContentOverlay overlay) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index f08742db8ebf..9a775dff1f69 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -86,6 +86,7 @@ public class PipBoundsState { private int mStashedState = STASH_TYPE_NONE; private int mStashOffset; private @Nullable PipReentryState mPipReentryState; + private final LauncherState mLauncherState = new LauncherState(); private final @Nullable PipSizeSpecHandler mPipSizeSpecHandler; private @Nullable ComponentName mLastPipComponentName; private final @NonNull MotionBoundsState mMotionBoundsState = new MotionBoundsState(); @@ -482,6 +483,10 @@ public class PipBoundsState { mOnPipExclusionBoundsChangeCallbacks.remove(onPipExclusionBoundsChangeCallback); } + public LauncherState getLauncherState() { + return mLauncherState; + } + /** Source of truth for the current bounds of PIP that may be in motion. */ public static class MotionBoundsState { /** The bounds used when PIP is in motion (e.g. during a drag or animation) */ @@ -534,6 +539,25 @@ public class PipBoundsState { } } + /** Data class for Launcher state. */ + public static final class LauncherState { + private int mAppIconSizePx; + + public void setAppIconSizePx(int appIconSizePx) { + mAppIconSizePx = appIconSizePx; + } + + public int getAppIconSizePx() { + return mAppIconSizePx; + } + + void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + LauncherState.class.getSimpleName()); + pw.println(innerPrefix + "getAppIconSizePx=" + getAppIconSizePx()); + } + } + static final class PipReentryState { private static final String TAG = PipReentryState.class.getSimpleName(); @@ -587,6 +611,7 @@ public class PipBoundsState { } else { mPipReentryState.dump(pw, innerPrefix); } + mLauncherState.dump(pw, innerPrefix); mMotionBoundsState.dump(pw, innerPrefix); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index d228dfbb7705..9fa57cacb11f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -32,8 +32,6 @@ import android.view.SurfaceControl; import android.view.SurfaceSession; import android.window.TaskSnapshot; -import java.util.function.Supplier; - /** * Represents the content overlay used during the entering PiP animation. */ @@ -176,9 +174,8 @@ public abstract class PipContentOverlay { /** A {@link PipContentOverlay} shows app icon on solid color background. */ public static final class PipAppIconOverlay extends PipContentOverlay { private static final String TAG = PipAppIconOverlay.class.getSimpleName(); - // Align with the practical / reasonable launcher:iconImageSize as in - // vendor/unbundled_google/packages/NexusLauncher/res/xml/device_profiles.xml - private static final int APP_ICON_SIZE_DP = 66; + // The maximum size for app icon in pixel. + private static final int MAX_APP_ICON_SIZE_DP = 72; private final Context mContext; private final int mAppIconSizePx; @@ -188,14 +185,16 @@ public abstract class PipContentOverlay { private Bitmap mBitmap; - public PipAppIconOverlay(Context context, Rect appBounds, Supplier<Drawable> iconSupplier) { + public PipAppIconOverlay(Context context, Rect appBounds, + Drawable appIcon, int appIconSizePx) { mContext = context; - mAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, APP_ICON_SIZE_DP, - context.getResources().getDisplayMetrics()); + final int maxAppIconSizePx = (int) TypedValue.applyDimension(COMPLEX_UNIT_DIP, + MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics()); + mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx); mAppBounds = new Rect(appBounds); mBitmap = Bitmap.createBitmap(appBounds.width(), appBounds.height(), Bitmap.Config.ARGB_8888); - prepareAppIconOverlay(iconSupplier); + prepareAppIconOverlay(appIcon); mLeash = new SurfaceControl.Builder(new SurfaceSession()) .setCallsite(TAG) .setName(LAYER_NAME) @@ -238,7 +237,7 @@ public abstract class PipContentOverlay { } } - private void prepareAppIconOverlay(Supplier<Drawable> iconSupplier) { + private void prepareAppIconOverlay(Drawable appIcon) { final Canvas canvas = new Canvas(); canvas.setBitmap(mBitmap); final TypedArray ta = mContext.obtainStyledAttributes(new int[] { @@ -252,7 +251,6 @@ public abstract class PipContentOverlay { } finally { ta.recycle(); } - final Drawable appIcon = iconSupplier.get(); final Rect appIconBounds = new Rect( mAppBounds.centerX() - mAppIconSizePx / 2, mAppBounds.centerY() - mAppIconSizePx / 2, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index d5b9c5e8d8ff..52f5a8cfd8e0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -1608,7 +1608,8 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, if (SystemProperties.getBoolean( "persist.wm.debug.enable_pip_app_icon_overlay", true)) { animator.setAppIconContentOverlay( - mContext, currentBounds, mTaskInfo.topActivityInfo); + mContext, currentBounds, mTaskInfo.topActivityInfo, + mPipBoundsState.getLauncherState().getAppIconSizePx()); } else { animator.setColorContentOverlay(mContext); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index 45bb73bdc0d2..49a27c57dc73 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -808,7 +808,8 @@ public class PipTransition extends PipTransitionController { "persist.wm.debug.enable_pip_app_icon_overlay", true) && hasTopActivityInfo) { animator.setAppIconContentOverlay( - mContext, currentBounds, taskInfo.topActivityInfo); + mContext, currentBounds, taskInfo.topActivityInfo, + mPipBoundsState.getLauncherState().getAppIconSizePx()); } else { animator.setColorContentOverlay(mContext); } 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 ea2559af6142..9ee4b65b5836 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 @@ -193,7 +193,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb Rect destBounds = mPipKeepClearAlgorithm.adjust(mPipBoundsState, mPipBoundsAlgorithm); // only move if the bounds are actually different - if (destBounds != mPipBoundsState.getBounds()) { + if (!destBounds.equals(mPipBoundsState.getBounds())) { if (mPipTransitionState.hasEnteredPip()) { // if already in PiP, schedule separate animation mPipTaskOrganizer.scheduleAnimateResizePip(destBounds, @@ -942,6 +942,10 @@ public class PipController implements PipTransitionController.PipTransitionCallb } } + private void setLauncherAppIconSize(int iconSizePx) { + mPipBoundsState.getLauncherState().setAppIconSizePx(iconSizePx); + } + private void setOnIsInPipStateChangedListener(Consumer<Boolean> callback) { mOnIsInPipStateChangedListener = callback; if (mOnIsInPipStateChangedListener != null) { @@ -1286,26 +1290,26 @@ public class PipController implements PipTransitionController.PipTransitionCallb overlay.setUnreleasedWarningCallSite("PipController.stopSwipePipToHome"); } executeRemoteCallWithTaskPermission(mController, "stopSwipePipToHome", - (controller) -> { - controller.stopSwipePipToHome(taskId, componentName, destinationBounds, - overlay); - }); + (controller) -> controller.stopSwipePipToHome( + taskId, componentName, destinationBounds, overlay)); } @Override public void setShelfHeight(boolean visible, int height) { executeRemoteCallWithTaskPermission(mController, "setShelfHeight", - (controller) -> { - controller.setShelfHeight(visible, height); - }); + (controller) -> controller.setShelfHeight(visible, height)); } @Override public void setLauncherKeepClearAreaHeight(boolean visible, int height) { executeRemoteCallWithTaskPermission(mController, "setLauncherKeepClearAreaHeight", - (controller) -> { - controller.setLauncherKeepClearAreaHeight(visible, height); - }); + (controller) -> controller.setLauncherKeepClearAreaHeight(visible, height)); + } + + @Override + public void setLauncherAppIconSize(int iconSizePx) { + executeRemoteCallWithTaskPermission(mController, "setLauncherAppIconSize", + (controller) -> controller.setLauncherAppIconSize(iconSizePx)); } @Override @@ -1323,9 +1327,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Override public void setPipAnimationTypeToAlpha() { executeRemoteCallWithTaskPermission(mController, "setPipAnimationTypeToAlpha", - (controller) -> { - controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA); - }); + (controller) -> controller.setPinnedStackAnimationType(ANIM_TYPE_ALPHA)); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java index 0a9c3310a883..8cb575cc96e3 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeInputListener.java @@ -103,6 +103,7 @@ class DragResizeInputListener implements AutoCloseable { null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, + 0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, mFocusGrantToken, @@ -208,6 +209,7 @@ class DragResizeInputListener implements AutoCloseable { mDecorationSurface, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, + 0 /* inputFeatures */, touchRegion); } catch (RemoteException e) { e.rethrowFromSystemServer(); diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt index 11bb0cc1306e..f52e877ec2b1 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt @@ -74,10 +74,18 @@ open class EnterPipOnUserLeaveHintTest(flicker: FlickerTest) : EnterPipTransitio @Presubmit @Test - override fun pipAppLayerOrOverlayAlwaysVisible() { + override fun pipAppLayerAlwaysVisible() { // pip layer in gesture nav will disappear during transition Assume.assumeFalse(flicker.scenario.isGesturalNavigation) - super.pipAppLayerOrOverlayAlwaysVisible() + super.pipAppLayerAlwaysVisible() + } + + @Presubmit + @Test + override fun pipOverlayLayerAppearThenDisappear() { + // no overlay in gesture nav for non-auto enter PiP transition + Assume.assumeFalse(flicker.scenario.isGesturalNavigation) + super.pipOverlayLayerAppearThenDisappear() } @Presubmit diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt index 327225421580..e40e5eaad9e2 100644 --- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt +++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt @@ -43,13 +43,23 @@ abstract class EnterPipTransition(flicker: FlickerTest) : PipTransition(flicker) /** Checks [pipApp] layer remains visible throughout the animation */ @Presubmit @Test - open fun pipAppLayerOrOverlayAlwaysVisible() { + open fun pipAppLayerAlwaysVisible() { flicker.assertLayers { this.isVisible(pipApp) + } + } + + /** Checks the content overlay appears then disappears during the animation */ + @Presubmit + @Test + open fun pipOverlayLayerAppearThenDisappear() { + val overlay = ComponentNameMatcher.PIP_CONTENT_OVERLAY + flicker.assertLayers { + this.notContains(overlay) .then() - .isVisible(ComponentNameMatcher.PIP_CONTENT_OVERLAY) + .contains(overlay) .then() - .isVisible(pipApp) + .notContains(overlay) } } diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp index 2ac1dc0c4838..57a698128d77 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -69,6 +69,8 @@ android_test { enabled: false, }, + test_suites: ["device-tests"], + platform_apis: true, certificate: "platform", diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java index 6dae479ae7a7..169b9bd4dea7 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java @@ -411,6 +411,53 @@ public class BackAnimationControllerTest extends ShellTestCase { verify(mAnimatorCallback, never()).onBackInvoked(); } + @Test + public void testBackToActivity() throws RemoteException { + final CrossActivityAnimation animation = new CrossActivityAnimation(mContext, + mAnimationBackground); + verifySystemBackBehavior( + BackNavigationInfo.TYPE_CROSS_ACTIVITY, animation.mBackAnimationRunner); + } + + @Test + public void testBackToTask() throws RemoteException { + final CrossTaskBackAnimation animation = new CrossTaskBackAnimation(mContext, + mAnimationBackground); + verifySystemBackBehavior( + BackNavigationInfo.TYPE_CROSS_TASK, animation.mBackAnimationRunner); + } + + private void verifySystemBackBehavior(int type, BackAnimationRunner animation) + throws RemoteException { + final BackAnimationRunner animationRunner = spy(animation); + final IRemoteAnimationRunner runner = spy(animationRunner.getRunner()); + final IOnBackInvokedCallback callback = spy(animationRunner.getCallback()); + + // Set up the monitoring objects. + doNothing().when(runner).onAnimationStart(anyInt(), any(), any(), any(), any()); + doReturn(runner).when(animationRunner).getRunner(); + doReturn(callback).when(animationRunner).getCallback(); + + mController.registerAnimation(type, animationRunner); + + createNavigationInfo(type, true); + + doMotionEvent(MotionEvent.ACTION_DOWN, 0); + + // Check that back start and progress is dispatched when first move. + doMotionEvent(MotionEvent.ACTION_MOVE, 100); + + simulateRemoteAnimationStart(type); + + verify(callback).onBackStarted(any(BackMotionEvent.class)); + verify(animationRunner).startAnimation(any(), any(), any(), any()); + + // Check that back invocation is dispatched. + mController.setTriggerBack(true); // Fake trigger back + doMotionEvent(MotionEvent.ACTION_UP, 0); + verify(callback).onBackInvoked(); + } + private void doMotionEvent(int actionDown, int coordinate) { mController.onMotionEvent( coordinate, coordinate, diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java index ecfb427dbced..58e91cb50c7a 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/kidsmode/KidsModeTaskOrganizerTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.verify; import android.app.ActivityManager; import android.content.Context; import android.content.pm.ParceledListSlice; +import android.content.res.Resources; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; @@ -77,6 +78,7 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase { @Mock private ShellInit mShellInit; @Mock private ShellCommandHandler mShellCommandHandler; @Mock private DisplayInsetsController mDisplayInsetsController; + @Mock private Resources mResources; KidsModeTaskOrganizer mOrganizer; @@ -89,10 +91,12 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase { } catch (RemoteException e) { } // NOTE: KidsModeTaskOrganizer should have a null CompatUIController. - mOrganizer = spy(new KidsModeTaskOrganizer(mContext, mShellInit, mShellCommandHandler, - mTaskOrganizerController, mSyncTransactionQueue, mDisplayController, - mDisplayInsetsController, Optional.empty(), Optional.empty(), mObserver, - mTestExecutor, mHandler)); + doReturn(mResources).when(mContext).getResources(); + final KidsModeTaskOrganizer kidsModeTaskOrganizer = new KidsModeTaskOrganizer(mContext, + mShellInit, mShellCommandHandler, mTaskOrganizerController, mSyncTransactionQueue, + mDisplayController, mDisplayInsetsController, Optional.empty(), Optional.empty(), + mObserver, mTestExecutor, mHandler); + mOrganizer = spy(kidsModeTaskOrganizer); doReturn(mTransaction).when(mOrganizer).getWindowContainerTransaction(); doReturn(new InsetsState()).when(mDisplayController).getInsetsState(DEFAULT_DISPLAY); } @@ -112,6 +116,8 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase { verify(mOrganizer, times(1)).registerOrganizer(); verify(mOrganizer, times(1)).createRootTask( eq(DEFAULT_DISPLAY), eq(WINDOWING_MODE_FULLSCREEN), eq(mOrganizer.mCookie)); + verify(mOrganizer, times(1)) + .setOrientationRequestPolicy(eq(true), any(), any()); final ActivityManager.RunningTaskInfo rootTask = createTaskInfo(12, WINDOWING_MODE_FULLSCREEN, mOrganizer.mCookie); @@ -132,10 +138,11 @@ public class KidsModeTaskOrganizerTest extends ShellTestCase { doReturn(false).when(mObserver).isEnabled(); mOrganizer.updateKidsModeState(); - verify(mOrganizer, times(1)).disable(); verify(mOrganizer, times(1)).unregisterOrganizer(); verify(mOrganizer, times(1)).deleteRootTask(rootTask.token); + verify(mOrganizer, times(1)) + .setOrientationRequestPolicy(eq(false), any(), any()); assertThat(mOrganizer.mLaunchRootLeash).isNull(); assertThat(mOrganizer.mLaunchRootTask).isNull(); } diff --git a/libs/input/MouseCursorController.cpp b/libs/input/MouseCursorController.cpp index c3984055a278..c3ad7670d473 100644 --- a/libs/input/MouseCursorController.cpp +++ b/libs/input/MouseCursorController.cpp @@ -53,8 +53,6 @@ MouseCursorController::MouseCursorController(PointerControllerContext& context) mLocked.resolvedPointerType = PointerIconStyle::TYPE_NOT_SPECIFIED; mLocked.resourcesLoaded = false; - - mLocked.buttonState = 0; } MouseCursorController::~MouseCursorController() { @@ -95,22 +93,6 @@ void MouseCursorController::move(float deltaX, float deltaY) { setPositionLocked(mLocked.pointerX + deltaX, mLocked.pointerY + deltaY); } -void MouseCursorController::setButtonState(int32_t buttonState) { -#if DEBUG_MOUSE_CURSOR_UPDATES - ALOGD("Set button state 0x%08x", buttonState); -#endif - std::scoped_lock lock(mLock); - - if (mLocked.buttonState != buttonState) { - mLocked.buttonState = buttonState; - } -} - -int32_t MouseCursorController::getButtonState() const { - std::scoped_lock lock(mLock); - return mLocked.buttonState; -} - void MouseCursorController::setPosition(float x, float y) { #if DEBUG_MOUSE_CURSOR_UPDATES ALOGD("Set pointer position to x=%0.3f, y=%0.3f", x, y); diff --git a/libs/input/MouseCursorController.h b/libs/input/MouseCursorController.h index 26be2a858c4e..00dc0854440e 100644 --- a/libs/input/MouseCursorController.h +++ b/libs/input/MouseCursorController.h @@ -45,8 +45,6 @@ public: std::optional<FloatRect> getBounds() const; void move(float deltaX, float deltaY); - void setButtonState(int32_t buttonState); - int32_t getButtonState() const; void setPosition(float x, float y); FloatPoint getPosition() const; int32_t getDisplayId() const; @@ -96,8 +94,6 @@ private: PointerIconStyle requestedPointerType; PointerIconStyle resolvedPointerType; - int32_t buttonState; - bool animating{false}; } mLocked GUARDED_BY(mLock); diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 544edc2a716f..88e351963148 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -136,14 +136,6 @@ void PointerController::move(float deltaX, float deltaY) { mCursorController.move(transformed.x, transformed.y); } -void PointerController::setButtonState(int32_t buttonState) { - mCursorController.setButtonState(buttonState); -} - -int32_t PointerController::getButtonState() const { - return mCursorController.getButtonState(); -} - void PointerController::setPosition(float x, float y) { const int32_t displayId = mCursorController.getDisplayId(); vec2 transformed; diff --git a/libs/input/PointerController.h b/libs/input/PointerController.h index 6d3557c89cc7..ca14b6e9bfdc 100644 --- a/libs/input/PointerController.h +++ b/libs/input/PointerController.h @@ -50,21 +50,19 @@ public: ~PointerController() override; - virtual std::optional<FloatRect> getBounds() const; - virtual void move(float deltaX, float deltaY); - virtual void setButtonState(int32_t buttonState); - virtual int32_t getButtonState() const; - virtual void setPosition(float x, float y); - virtual FloatPoint getPosition() const; - virtual int32_t getDisplayId() const; - virtual void fade(Transition transition); - virtual void unfade(Transition transition); - virtual void setDisplayViewport(const DisplayViewport& viewport); - - virtual void setPresentation(Presentation presentation); - virtual void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, - BitSet32 spotIdBits, int32_t displayId); - virtual void clearSpots(); + std::optional<FloatRect> getBounds() const override; + void move(float deltaX, float deltaY) override; + void setPosition(float x, float y) override; + FloatPoint getPosition() const override; + int32_t getDisplayId() const override; + void fade(Transition transition) override; + void unfade(Transition transition) override; + void setDisplayViewport(const DisplayViewport& viewport) override; + + void setPresentation(Presentation presentation) override; + void setSpots(const PointerCoords* spotCoords, const uint32_t* spotIdToIndex, + BitSet32 spotIdBits, int32_t displayId) override; + void clearSpots() override; void updatePointerIcon(PointerIconStyle iconId); void setCustomPointerIcon(const SpriteIcon& icon); diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java index f9b6ce0a199c..8ab715965082 100644 --- a/media/java/android/media/AudioManager.java +++ b/media/java/android/media/AudioManager.java @@ -6251,7 +6251,6 @@ public class AudioManager { * Volume behavior for an audio device where no software attenuation is applied, and * the volume is kept synchronized between the host and the device itself through a * device-specific protocol such as BT AVRCP. - * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) */ @SystemApi public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE = 3; @@ -6262,7 +6261,6 @@ public class AudioManager { * device-specific protocol (such as for hearing aids), based on the audio mode (e.g. * normal vs in phone call). * @see #setMode(int) - * @see #setDeviceVolumeBehavior(AudioDeviceAttributes, int) */ @SystemApi public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE = 4; @@ -6272,6 +6270,11 @@ public class AudioManager { * A variant of {@link #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE} where the host cannot reliably set * the volume percentage of the audio device. Specifically, {@link #setStreamVolume} will have * no effect, or an unreliable effect. + * + * {@link #DEVICE_VOLUME_BEHAVIOR_FULL} will be returned instead by + * {@link #getDeviceVolumeBehavior} for target SDK versions before U. + * + * @see #RETURN_DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY */ @SystemApi public static final int DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY = 5; @@ -6313,18 +6316,27 @@ public class AudioManager { public @interface AbsoluteDeviceVolumeBehavior {} /** + * Volume behaviors that can be set with {@link #setDeviceVolumeBehavior}. + * @hide + */ + @IntDef({ + DEVICE_VOLUME_BEHAVIOR_VARIABLE, + DEVICE_VOLUME_BEHAVIOR_FULL, + DEVICE_VOLUME_BEHAVIOR_FIXED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface SettableDeviceVolumeBehavior {} + + /** * @hide - * Throws IAE on an invalid volume behavior value + * Throws IAE on a non-settable volume behavior value * @param volumeBehavior behavior value to check */ - public static void enforceValidVolumeBehavior(int volumeBehavior) { + public static void enforceSettableVolumeBehavior(int volumeBehavior) { switch (volumeBehavior) { case DEVICE_VOLUME_BEHAVIOR_VARIABLE: case DEVICE_VOLUME_BEHAVIOR_FULL: case DEVICE_VOLUME_BEHAVIOR_FIXED: - case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE: - case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE: - case DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_ADJUST_ONLY: return; default: throw new IllegalArgumentException("Illegal volume behavior " + volumeBehavior); @@ -6334,11 +6346,8 @@ public class AudioManager { /** * @hide * Sets the volume behavior for an audio output device. - * @see #DEVICE_VOLUME_BEHAVIOR_VARIABLE - * @see #DEVICE_VOLUME_BEHAVIOR_FULL - * @see #DEVICE_VOLUME_BEHAVIOR_FIXED - * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE - * @see #DEVICE_VOLUME_BEHAVIOR_ABSOLUTE_MULTI_MODE + * + * @see SettableDeviceVolumeBehavior * @param device the device to be affected * @param deviceVolumeBehavior one of the device behaviors */ @@ -6348,10 +6357,10 @@ public class AudioManager { Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED }) public void setDeviceVolumeBehavior(@NonNull AudioDeviceAttributes device, - @DeviceVolumeBehavior int deviceVolumeBehavior) { + @SettableDeviceVolumeBehavior int deviceVolumeBehavior) { // verify arguments (validity of device type is enforced in server) Objects.requireNonNull(device); - enforceValidVolumeBehavior(deviceVolumeBehavior); + enforceSettableVolumeBehavior(deviceVolumeBehavior); // communicate with service final IAudioService service = getService(); try { @@ -6752,7 +6761,7 @@ public class AudioManager { /** * @hide - * Lower media volume to RS1 + * Lower media volume to RS1 interval */ public void lowerVolumeToRs1() { try { @@ -6764,13 +6773,13 @@ public class AudioManager { /** * @hide - * @return the RS2 value used for momentary exposure warnings + * @return the RS2 upper bound used for momentary exposure warnings */ @TestApi @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public float getRs2Value() { try { - return getService().getRs2Value(); + return getService().getOutputRs2UpperBound(); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -6778,13 +6787,13 @@ public class AudioManager { /** * @hide - * Sets the RS2 value used for momentary exposure warnings + * Sets the RS2 upper bound used for momentary exposure warnings */ @TestApi @RequiresPermission(Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) public void setRs2Value(float rs2Value) { try { - getService().setRs2Value(rs2Value); + getService().setOutputRs2UpperBound(rs2Value); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/media/java/android/media/IAudioService.aidl b/media/java/android/media/IAudioService.aidl index cebdc820cd02..f9d4efea57e2 100644 --- a/media/java/android/media/IAudioService.aidl +++ b/media/java/android/media/IAudioService.aidl @@ -296,10 +296,10 @@ interface IAudioService { void lowerVolumeToRs1(String callingPackage); @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") - float getRs2Value(); + float getOutputRs2UpperBound(); @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") - oneway void setRs2Value(float rs2Value); + oneway void setOutputRs2UpperBound(float rs2Value); @EnforcePermission("MODIFY_AUDIO_SETTINGS_PRIVILEGED") float getCsd(); diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl index 14c020e94c15..901ea46ba38b 100644 --- a/media/java/android/media/tv/ITvInputManager.aidl +++ b/media/java/android/media/tv/ITvInputManager.aidl @@ -120,6 +120,7 @@ interface ITvInputManager { // For TV Message void notifyTvMessage(in IBinder sessionToken, int type, in Bundle data, int userId); + void setTvMessageEnabled(in IBinder sessionToken, int type, boolean enabled, int userId); // For TV input hardware binding List<TvInputHardwareInfo> getHardwareList(); diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl index a7bd8d313ac3..5246f5c4dc1e 100644 --- a/media/java/android/media/tv/ITvInputSession.aidl +++ b/media/java/android/media/tv/ITvInputSession.aidl @@ -79,4 +79,5 @@ oneway interface ITvInputSession { // For TV messages void notifyTvMessage(int type, in Bundle data); + void setTvMessageEnabled(int type, boolean enabled); } diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java index f939f523d3fa..3a990b3dd6a9 100644 --- a/media/java/android/media/tv/ITvInputSessionWrapper.java +++ b/media/java/android/media/tv/ITvInputSessionWrapper.java @@ -268,6 +268,7 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand case DO_SET_TV_MESSAGE_ENABLED: { SomeArgs args = (SomeArgs) msg.obj; mTvInputSessionImpl.setTvMessageEnabled((Integer) args.arg1, (Boolean) args.arg2); + args.recycle(); break; } case DO_REQUEST_AD: { @@ -474,6 +475,12 @@ public class ITvInputSessionWrapper extends ITvInputSession.Stub implements Hand mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_NOTIFY_TV_MESSAGE, type, data)); } + @Override + public void setTvMessageEnabled(int type, boolean enabled) { + mCaller.executeOrSendMessage(mCaller.obtainMessageOO(DO_SET_TV_MESSAGE_ENABLED, type, + enabled)); + } + private final class TvInputEventReceiver extends InputEventReceiver { TvInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); diff --git a/media/java/android/media/tv/OWNERS b/media/java/android/media/tv/OWNERS index fa0429350a25..0b022e514662 100644 --- a/media/java/android/media/tv/OWNERS +++ b/media/java/android/media/tv/OWNERS @@ -1,6 +1,7 @@ quxiangfang@google.com shubang@google.com hgchen@google.com +qingxun@google.com # For android remote service per-file ITvRemoteServiceInput.aidl = file:/media/lib/tvremote/OWNERS diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java index ef2b5a5b6e50..f344fd3e797b 100644 --- a/media/java/android/media/tv/TvInputManager.java +++ b/media/java/android/media/tv/TvInputManager.java @@ -156,20 +156,51 @@ public final class TvInputManager { "android.media.tv.TvInputManager.stream_id"; /** + * This value for {@link #TV_MESSAGE_KEY_GROUP_ID} denotes that the message doesn't + * belong to any group. + */ + public static final long TV_MESSAGE_GROUP_ID_NONE = -1; + + /** + * This constant is used as a {@link Bundle} key for TV messages. This is used to + * optionally identify messages that belong together, such as headers and bodies + * of the same event. For messages that do not have a group, this value + * should be {@link #TV_MESSAGE_GROUP_ID_NONE}. + * + * <p> As -1 is a reserved value, -1 should not be used as a valid groupId. + * + * <p> Type: long + */ + public static final String TV_MESSAGE_KEY_GROUP_ID = + "android.media.tv.TvInputManager.group_id"; + + /** + * This is a subtype for TV messages that can be potentially found as a value + * at {@link #TV_MESSAGE_KEY_SUBTYPE}. It identifies the subtype of the message + * as the watermarking format ATSC A/335. + */ + public static final String TV_MESSAGE_SUBTYPE_WATERMARKING_A335 = "ATSC A/335"; + + /** + * This is a subtype for TV messages that can be potentially found as a value + * at {@link #TV_MESSAGE_KEY_SUBTYPE}. It identifies the subtype of the message + * as the CC format CTA 608-E. + */ + public static final String TV_MESSAGE_SUBTYPE_CC_608E = "CTA 608-E"; + + /** * This constant is used as a {@link Bundle} key for TV messages. The value of the key * identifies the subtype of the data, such as the format of the CC data. The format * found at this key can then be used to identify how to parse the data at * {@link #TV_MESSAGE_KEY_RAW_DATA}. * - * To parse the raw data bsed on the subtype, please refer to the official documentation of the - * concerning subtype. For example, for the subtype "ATSC A/335" for watermarking, the - * document for A/335 from the ATSC standard details how this data is formatted. - * - * Some other examples of common formats include: - * <ul> - * <li>Watermarking - ATSC A/336</li> - * <li>Closed Captioning - CTA 608-E</li> - * </ul> + * <p> To parse the raw data based on the subtype, please refer to the official + * documentation of the concerning subtype. For example, for the subtype + * {@link #TV_MESSAGE_SUBTYPE_WATERMARKING_A335}, the document for A/335 from the ATSC + * standard details how this data is formatted. Similarly, the subtype + * {@link #TV_MESSAGE_SUBTYPE_CC_608E} is documented in the ANSI/CTA standard for + * 608-E. These subtypes are examples of common formats for their respective uses + * and other subtypes may exist. * * <p> Type: String */ @@ -178,7 +209,7 @@ public final class TvInputManager { /** * This constant is used as a {@link Bundle} key for TV messages. The value of the key - * stores the raw data contained in this TV Message. The format of this data is determined + * stores the raw data contained in this TV message. The format of this data is determined * by the format defined by the subtype, found using the key at * {@link #TV_MESSAGE_KEY_SUBTYPE}. See {@link #TV_MESSAGE_KEY_SUBTYPE} for more * information on how to parse this data. @@ -839,6 +870,7 @@ public final class TvInputManager { * @param type The type of message received, such as {@link #TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message. The bundle keys are: * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID}, + * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID}, * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE}, * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}. * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on @@ -3273,7 +3305,7 @@ public final class TvInputManager { /** * Sends TV messages to the service for testing purposes */ - public void notifyTvMessage(@NonNull @TvMessageType int type, @NonNull Bundle data) { + public void notifyTvMessage(int type, Bundle data) { try { mService.notifyTvMessage(mToken, type, data, mUserId); } catch (RemoteException e) { @@ -3282,6 +3314,17 @@ public final class TvInputManager { } /** + * Sets whether the TV message of the specific type should be enabled. + */ + public void setTvMessageEnabled(int type, boolean enabled) { + try { + mService.setTvMessageEnabled(mToken, type, enabled, mUserId); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Starts TV program recording in the current recording session. * * @param programUri The URI for the TV program to record as a hint, built by diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java index 0d283fa87c19..85b02ad54718 100644 --- a/media/java/android/media/tv/TvInputService.java +++ b/media/java/android/media/tv/TvInputService.java @@ -1032,6 +1032,7 @@ public abstract class TvInputService extends Service { * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message. The bundle keys are: * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID}, + * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID}, * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE}, * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}. * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on @@ -1507,6 +1508,7 @@ public abstract class TvInputService extends Service { * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message. The bundle keys are: * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID}, + * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID}, * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE}, * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}. * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java index 8a886832e88e..a4e5fb6a6165 100644 --- a/media/java/android/media/tv/TvView.java +++ b/media/java/android/media/tv/TvView.java @@ -745,6 +745,9 @@ public class TvView extends ViewGroup { */ public void setTvMessageEnabled(@TvInputManager.TvMessageType int type, boolean enabled) { + if (mSession != null) { + mSession.setTvMessageEnabled(type, enabled); + } } @Override @@ -1256,6 +1259,7 @@ public class TvView extends ViewGroup { * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message. The bundle keys are: * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID}, + * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID}, * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE}, * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}. * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppService.java b/media/java/android/media/tv/interactive/TvInteractiveAppService.java index 69ff9c63b32b..06dfe4fffefe 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppService.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppService.java @@ -921,6 +921,7 @@ public abstract class TvInteractiveAppService extends Service { * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message. The bundle keys are: * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID}, + * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID}, * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE}, * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}. * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on diff --git a/media/java/android/media/tv/interactive/TvInteractiveAppView.java b/media/java/android/media/tv/interactive/TvInteractiveAppView.java index 80a14354ef7a..cbaf5e482faa 100755 --- a/media/java/android/media/tv/interactive/TvInteractiveAppView.java +++ b/media/java/android/media/tv/interactive/TvInteractiveAppView.java @@ -946,6 +946,7 @@ public class TvInteractiveAppView extends ViewGroup { * {@link TvInputManager#TV_MESSAGE_TYPE_WATERMARK} * @param data The raw data of the message. The bundle keys are: * {@link TvInputManager#TV_MESSAGE_KEY_STREAM_ID}, + * {@link TvInputManager#TV_MESSAGE_KEY_GROUP_ID}, * {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE}, * {@link TvInputManager#TV_MESSAGE_KEY_RAW_DATA}. * See {@link TvInputManager#TV_MESSAGE_KEY_SUBTYPE} for more information on diff --git a/native/android/surface_control.cpp b/native/android/surface_control.cpp index 904fa7484027..4b63fbf14d4c 100644 --- a/native/android/surface_control.cpp +++ b/native/android/surface_control.cpp @@ -628,14 +628,15 @@ void ASurfaceTransaction_setExtendedRangeBrightness(ASurfaceTransaction* aSurfac CHECK_NOT_NULL(aSurfaceControl); if (!isfinite(currentBufferRatio) || currentBufferRatio < 1.0f) { - ALOGE("Ignore setExtendedRangeBrightness, currentBufferRatio %f isn't finite or >= 1.0f", - currentBufferRatio); + LOG_ALWAYS_FATAL("setExtendedRangeBrightness, currentBufferRatio %f isn't finite or >= " + "1.0f", + currentBufferRatio); return; } if (!isfinite(desiredRatio) || desiredRatio < 1.0f) { - ALOGE("Ignore setExtendedRangeBrightness, desiredRatio %f isn't finite or >= 1.0f", - desiredRatio); + LOG_ALWAYS_FATAL("setExtendedRangeBrightness, desiredRatio %f isn't finite or >= 1.0f", + desiredRatio); return; } diff --git a/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml b/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml index ebe16a7a14e5..e6ac209e7add 100644 --- a/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml +++ b/packages/CompanionDeviceManager/res/drawable/btn_negative_multiple_devices.xml @@ -18,8 +18,7 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@android:color/transparent" /> - <corners android:topLeftRadius="16dp" android:topRightRadius="16dp" - android:bottomLeftRadius="16dp" android:bottomRightRadius="16dp"/> + <corners android:radius="24dp" /> <stroke android:width="1dp" android:color="@android:color/system_accent1_600" /> diff --git a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml index 22805f62c449..d1d2c70134c6 100644 --- a/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml +++ b/packages/CompanionDeviceManager/res/layout/activity_confirmation.xml @@ -123,21 +123,30 @@ <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" + android:orientation="horizontal" android:gravity="bottom|end" - android:orientation="vertical" android:layout_marginEnd="16dp" android:layout_marginBottom="16dp"> <!-- Do NOT change the IDs of the buttons: they are referenced in CTS tests. --> + <LinearLayout + android:id="@+id/negative_multiple_devices_layout" + android:layout_width="wrap_content" + android:layout_height="48dp" + android:gravity="center" + android:visibility="gone"> + + <Button + android:id="@+id/btn_negative_multiple_devices" + style="@style/NegativeButtonMultipleDevices" + android:textColor="?android:textColorPrimary" + android:visibility="gone" + android:duplicateParentState="true" + android:clickable="false" + android:text="@string/consent_no" /> + + </LinearLayout> - <Button - android:id="@+id/btn_negative_multiple_devices" - style="@style/NegativeButtonMultipleDevices" - android:textColor="?android:textColorPrimary" - android:visibility="gone" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:text="@string/consent_no" /> </LinearLayout> </LinearLayout> diff --git a/packages/CompanionDeviceManager/res/values/styles.xml b/packages/CompanionDeviceManager/res/values/styles.xml index 3c75cd5b8668..b167377eabf7 100644 --- a/packages/CompanionDeviceManager/res/values/styles.xml +++ b/packages/CompanionDeviceManager/res/values/styles.xml @@ -94,12 +94,12 @@ <style name="NegativeButtonMultipleDevices" parent="@android:style/Widget.Material.Button.Colored"> - <item name="android:layout_width">100dp</item> + <item name="android:layout_width">wrap_content</item> <item name="android:layout_height">36dp</item> <item name="android:textAllCaps">false</item> <item name="android:textSize">14sp</item> - <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> <item name="android:background">@drawable/btn_negative_multiple_devices</item> + <item name="android:textAppearance">@android:style/TextAppearance.DeviceDefault.Medium</item> </style> <style name="DeviceListBorder"> diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java index 8316f9df323f..99b776cb939f 100644 --- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java +++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java @@ -156,6 +156,9 @@ public class CompanionDeviceActivity extends FragmentActivity implements private ConstraintLayout mConstraintList; // Only present for self-managed association requests. private RelativeLayout mVendorHeader; + // A linearLayout for mButtonNotAllowMultipleDevices, user will press this layout instead + // of the button for accessibility. + private LinearLayout mNotAllowMultipleDevicesLayout; // The recycler view is only shown for multiple-device regular association request, after // at least one matching device is found. @@ -327,10 +330,11 @@ public class CompanionDeviceActivity extends FragmentActivity implements mButtonAllow = findViewById(R.id.btn_positive); mButtonNotAllow = findViewById(R.id.btn_negative); mButtonNotAllowMultipleDevices = findViewById(R.id.btn_negative_multiple_devices); + mNotAllowMultipleDevicesLayout = findViewById(R.id.negative_multiple_devices_layout); mButtonAllow.setOnClickListener(this::onPositiveButtonClick); mButtonNotAllow.setOnClickListener(this::onNegativeButtonClick); - mButtonNotAllowMultipleDevices.setOnClickListener(this::onNegativeButtonClick); + mNotAllowMultipleDevicesLayout.setOnClickListener(this::onNegativeButtonClick); mVendorHeaderButton.setOnClickListener(this::onShowHelperDialog); @@ -617,6 +621,7 @@ public class CompanionDeviceActivity extends FragmentActivity implements mButtonNotAllow.setVisibility(View.GONE); mDeviceListRecyclerView.setVisibility(View.VISIBLE); mButtonNotAllowMultipleDevices.setVisibility(View.VISIBLE); + mNotAllowMultipleDevicesLayout.setVisibility(View.VISIBLE); mConstraintList.setVisibility(View.VISIBLE); mMultipleDeviceSpinner.setVisibility(View.VISIBLE); } diff --git a/packages/SettingsLib/DeviceStateRotationLock/Android.bp b/packages/SettingsLib/DeviceStateRotationLock/Android.bp index c642bd14ed79..103309a43bb8 100644 --- a/packages/SettingsLib/DeviceStateRotationLock/Android.bp +++ b/packages/SettingsLib/DeviceStateRotationLock/Android.bp @@ -10,7 +10,10 @@ package { android_library { name: "SettingsLibDeviceStateRotationLock", - srcs: ["src/**/*.java"], + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], min_sdk_version: "21", } diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java index 10b004e1b243..76e1df1459e3 100644 --- a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/DeviceStateRotationLockSettingsManager.java @@ -57,17 +57,19 @@ public final class DeviceStateRotationLockSettingsManager { private final Handler mMainHandler = new Handler(Looper.getMainLooper()); private final Set<DeviceStateRotationLockSettingsListener> mListeners = new HashSet<>(); private final SecureSettings mSecureSettings; - private String[] mDeviceStateRotationLockDefaults; - private SparseIntArray mDeviceStateRotationLockSettings; - private SparseIntArray mDeviceStateDefaultRotationLockSettings; - private SparseIntArray mDeviceStateRotationLockFallbackSettings; + private final PosturesHelper mPosturesHelper; + private String[] mPostureRotationLockDefaults; + private SparseIntArray mPostureRotationLockSettings; + private SparseIntArray mPostureDefaultRotationLockSettings; + private SparseIntArray mPostureRotationLockFallbackSettings; private String mLastSettingValue; private List<SettableDeviceState> mSettableDeviceStates; @VisibleForTesting DeviceStateRotationLockSettingsManager(Context context, SecureSettings secureSettings) { - this.mSecureSettings = secureSettings; - mDeviceStateRotationLockDefaults = + mSecureSettings = secureSettings; + mPosturesHelper = new PosturesHelper(context); + mPostureRotationLockDefaults = context.getResources() .getStringArray(R.array.config_perDeviceStateRotationLockDefaults); loadDefaults(); @@ -134,13 +136,14 @@ public final class DeviceStateRotationLockSettingsManager { /** Updates the rotation lock setting for a specified device state. */ public void updateSetting(int deviceState, boolean rotationLocked) { - if (mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState) >= 0) { - // The setting for this device state is IGNORED, and has a fallback device state. - // The setting for that fallback device state should be the changed in this case. - deviceState = mDeviceStateRotationLockFallbackSettings.get(deviceState); + int posture = mPosturesHelper.deviceStateToPosture(deviceState); + if (mPostureRotationLockFallbackSettings.indexOfKey(posture) >= 0) { + // The setting for this device posture is IGNORED, and has a fallback posture. + // The setting for that fallback posture should be the changed in this case. + posture = mPostureRotationLockFallbackSettings.get(posture); } - mDeviceStateRotationLockSettings.put( - deviceState, + mPostureRotationLockSettings.put( + posture, rotationLocked ? DEVICE_STATE_ROTATION_LOCK_LOCKED : DEVICE_STATE_ROTATION_LOCK_UNLOCKED); @@ -159,22 +162,23 @@ public final class DeviceStateRotationLockSettingsManager { */ @Settings.Secure.DeviceStateRotationLockSetting public int getRotationLockSetting(int deviceState) { - int rotationLockSetting = mDeviceStateRotationLockSettings.get( - deviceState, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); + int devicePosture = mPosturesHelper.deviceStateToPosture(deviceState); + int rotationLockSetting = mPostureRotationLockSettings.get( + devicePosture, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { - rotationLockSetting = getFallbackRotationLockSetting(deviceState); + rotationLockSetting = getFallbackRotationLockSetting(devicePosture); } return rotationLockSetting; } - private int getFallbackRotationLockSetting(int deviceState) { - int indexOfFallbackState = mDeviceStateRotationLockFallbackSettings.indexOfKey(deviceState); - if (indexOfFallbackState < 0) { + private int getFallbackRotationLockSetting(int devicePosture) { + int indexOfFallback = mPostureRotationLockFallbackSettings.indexOfKey(devicePosture); + if (indexOfFallback < 0) { Log.w(TAG, "Setting is ignored, but no fallback was specified."); return DEVICE_STATE_ROTATION_LOCK_IGNORED; } - int fallbackState = mDeviceStateRotationLockFallbackSettings.valueAt(indexOfFallbackState); - return mDeviceStateRotationLockSettings.get(fallbackState, + int fallbackPosture = mPostureRotationLockFallbackSettings.valueAt(indexOfFallback); + return mPostureRotationLockSettings.get(fallbackPosture, /* valueIfKeyNotFound= */ DEVICE_STATE_ROTATION_LOCK_IGNORED); } @@ -189,8 +193,8 @@ public final class DeviceStateRotationLockSettingsManager { * DEVICE_STATE_ROTATION_LOCK_UNLOCKED}. */ public boolean isRotationLockedForAllStates() { - for (int i = 0; i < mDeviceStateRotationLockSettings.size(); i++) { - if (mDeviceStateRotationLockSettings.valueAt(i) + for (int i = 0; i < mPostureRotationLockSettings.size(); i++) { + if (mPostureRotationLockSettings.valueAt(i) == DEVICE_STATE_ROTATION_LOCK_UNLOCKED) { return false; } @@ -221,7 +225,7 @@ public final class DeviceStateRotationLockSettingsManager { fallbackOnDefaults(); return; } - mDeviceStateRotationLockSettings = new SparseIntArray(values.length / 2); + mPostureRotationLockSettings = new SparseIntArray(values.length / 2); int key; int value; @@ -230,7 +234,7 @@ public final class DeviceStateRotationLockSettingsManager { key = Integer.parseInt(values[i++]); value = Integer.parseInt(values[i++]); boolean isPersistedValueIgnored = value == DEVICE_STATE_ROTATION_LOCK_IGNORED; - boolean isDefaultValueIgnored = mDeviceStateDefaultRotationLockSettings.get(key) + boolean isDefaultValueIgnored = mPostureDefaultRotationLockSettings.get(key) == DEVICE_STATE_ROTATION_LOCK_IGNORED; if (isPersistedValueIgnored != isDefaultValueIgnored) { Log.w(TAG, "Conflict for ignored device state " + key @@ -238,7 +242,7 @@ public final class DeviceStateRotationLockSettingsManager { fallbackOnDefaults(); return; } - mDeviceStateRotationLockSettings.put(key, value); + mPostureRotationLockSettings.put(key, value); } catch (NumberFormatException e) { Log.wtf(TAG, "Error deserializing one of the saved settings", e); fallbackOnDefaults(); @@ -253,7 +257,7 @@ public final class DeviceStateRotationLockSettingsManager { */ @VisibleForTesting public void resetStateForTesting(Resources resources) { - mDeviceStateRotationLockDefaults = + mPostureRotationLockDefaults = resources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults); fallbackOnDefaults(); } @@ -264,23 +268,23 @@ public final class DeviceStateRotationLockSettingsManager { } private void persistSettings() { - if (mDeviceStateRotationLockSettings.size() == 0) { + if (mPostureRotationLockSettings.size() == 0) { persistSettingIfChanged(/* newSettingValue= */ ""); return; } StringBuilder stringBuilder = new StringBuilder(); stringBuilder - .append(mDeviceStateRotationLockSettings.keyAt(0)) + .append(mPostureRotationLockSettings.keyAt(0)) .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.valueAt(0)); + .append(mPostureRotationLockSettings.valueAt(0)); - for (int i = 1; i < mDeviceStateRotationLockSettings.size(); i++) { + for (int i = 1; i < mPostureRotationLockSettings.size(); i++) { stringBuilder .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.keyAt(i)) + .append(mPostureRotationLockSettings.keyAt(i)) .append(SEPARATOR_REGEX) - .append(mDeviceStateRotationLockSettings.valueAt(i)); + .append(mPostureRotationLockSettings.valueAt(i)); } persistSettingIfChanged(stringBuilder.toString()); } @@ -300,22 +304,20 @@ public final class DeviceStateRotationLockSettingsManager { } private void loadDefaults() { - mSettableDeviceStates = new ArrayList<>(mDeviceStateRotationLockDefaults.length); - mDeviceStateDefaultRotationLockSettings = new SparseIntArray( - mDeviceStateRotationLockDefaults.length); - mDeviceStateRotationLockSettings = new SparseIntArray( - mDeviceStateRotationLockDefaults.length); - mDeviceStateRotationLockFallbackSettings = new SparseIntArray(1); - for (String entry : mDeviceStateRotationLockDefaults) { + mSettableDeviceStates = new ArrayList<>(mPostureRotationLockDefaults.length); + mPostureDefaultRotationLockSettings = new SparseIntArray( + mPostureRotationLockDefaults.length); + mPostureRotationLockSettings = new SparseIntArray(mPostureRotationLockDefaults.length); + mPostureRotationLockFallbackSettings = new SparseIntArray(1); + for (String entry : mPostureRotationLockDefaults) { String[] values = entry.split(SEPARATOR_REGEX); try { - int deviceState = Integer.parseInt(values[0]); + int posture = Integer.parseInt(values[0]); int rotationLockSetting = Integer.parseInt(values[1]); if (rotationLockSetting == DEVICE_STATE_ROTATION_LOCK_IGNORED) { if (values.length == 3) { - int fallbackDeviceState = Integer.parseInt(values[2]); - mDeviceStateRotationLockFallbackSettings.put(deviceState, - fallbackDeviceState); + int fallbackPosture = Integer.parseInt(values[2]); + mPostureRotationLockFallbackSettings.put(posture, fallbackPosture); } else { Log.w(TAG, "Rotation lock setting is IGNORED, but values have unexpected " @@ -324,9 +326,14 @@ public final class DeviceStateRotationLockSettingsManager { } } boolean isSettable = rotationLockSetting != DEVICE_STATE_ROTATION_LOCK_IGNORED; - mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable)); - mDeviceStateRotationLockSettings.put(deviceState, rotationLockSetting); - mDeviceStateDefaultRotationLockSettings.put(deviceState, rotationLockSetting); + Integer deviceState = mPosturesHelper.postureToDeviceState(posture); + if (deviceState != null) { + mSettableDeviceStates.add(new SettableDeviceState(deviceState, isSettable)); + } else { + Log.wtf(TAG, "No matching device state for posture: " + posture); + } + mPostureRotationLockSettings.put(posture, rotationLockSetting); + mPostureDefaultRotationLockSettings.put(posture, rotationLockSetting); } catch (NumberFormatException e) { Log.wtf(TAG, "Error parsing settings entry. Entry was: " + entry, e); return; @@ -338,13 +345,11 @@ public final class DeviceStateRotationLockSettingsManager { public void dump(IndentingPrintWriter pw) { pw.println("DeviceStateRotationLockSettingsManager"); pw.increaseIndent(); - pw.println("mDeviceStateRotationLockDefaults: " + Arrays.toString( - mDeviceStateRotationLockDefaults)); - pw.println("mDeviceStateDefaultRotationLockSettings: " - + mDeviceStateDefaultRotationLockSettings); - pw.println("mDeviceStateRotationLockSettings: " + mDeviceStateRotationLockSettings); - pw.println("mDeviceStateRotationLockFallbackSettings: " - + mDeviceStateRotationLockFallbackSettings); + pw.println("mPostureRotationLockDefaults: " + + Arrays.toString(mPostureRotationLockDefaults)); + pw.println("mPostureDefaultRotationLockSettings: " + mPostureDefaultRotationLockSettings); + pw.println("mDeviceStateRotationLockSettings: " + mPostureRotationLockSettings); + pw.println("mPostureRotationLockFallbackSettings: " + mPostureRotationLockFallbackSettings); pw.println("mSettableDeviceStates: " + mSettableDeviceStates); pw.println("mLastSettingValue: " + mLastSettingValue); pw.decreaseIndent(); diff --git a/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/PosturesHelper.kt b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/PosturesHelper.kt new file mode 100644 index 000000000000..9c70be9c1f66 --- /dev/null +++ b/packages/SettingsLib/DeviceStateRotationLock/src/com.android.settingslib.devicestate/PosturesHelper.kt @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.settingslib.devicestate + +import android.content.Context +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_FOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_HALF_FOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNFOLDED +import android.provider.Settings.Secure.DEVICE_STATE_ROTATION_KEY_UNKNOWN +import android.provider.Settings.Secure.DeviceStateRotationLockKey +import com.android.internal.R + +/** Helps to convert between device state and posture. */ +class PosturesHelper(context: Context) { + + private val foldedDeviceStates = + context.resources.getIntArray(R.array.config_foldedDeviceStates) + private val halfFoldedDeviceStates = + context.resources.getIntArray(R.array.config_halfFoldedDeviceStates) + private val unfoldedDeviceStates = + context.resources.getIntArray(R.array.config_openDeviceStates) + + @DeviceStateRotationLockKey + fun deviceStateToPosture(deviceState: Int): Int { + return when (deviceState) { + in foldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_FOLDED + in halfFoldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_HALF_FOLDED + in unfoldedDeviceStates -> DEVICE_STATE_ROTATION_KEY_UNFOLDED + else -> DEVICE_STATE_ROTATION_KEY_UNKNOWN + } + } + + fun postureToDeviceState(@DeviceStateRotationLockKey posture: Int): Int? { + return when (posture) { + DEVICE_STATE_ROTATION_KEY_FOLDED -> foldedDeviceStates.firstOrNull() + DEVICE_STATE_ROTATION_KEY_HALF_FOLDED -> halfFoldedDeviceStates.firstOrNull() + DEVICE_STATE_ROTATION_KEY_UNFOLDED -> unfoldedDeviceStates.firstOrNull() + else -> null + } + } +} diff --git a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java index a45e853a3255..60ec91508930 100644 --- a/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java +++ b/packages/SettingsLib/EmergencyNumber/src/com/android/settingslib/emergencynumber/EmergencyNumberUtils.java @@ -55,11 +55,15 @@ public class EmergencyNumberUtils { public static final String METHOD_NAME_SET_EMERGENCY_NUMBER_OVERRIDE = "SET_EMERGENCY_NUMBER_OVERRIDE"; public static final String METHOD_NAME_SET_EMERGENCY_GESTURE = "SET_EMERGENCY_GESTURE"; + public static final String METHOD_NAME_SET_EMERGENCY_GESTURE_UI_SHOWING = + "SET_EMERGENCY_GESTURE_UI_SHOWING"; public static final String METHOD_NAME_SET_EMERGENCY_SOUND = "SET_EMERGENCY_SOUND"; public static final String METHOD_NAME_GET_EMERGENCY_GESTURE_ENABLED = "GET_EMERGENCY_GESTURE"; public static final String METHOD_NAME_GET_EMERGENCY_GESTURE_SOUND_ENABLED = "GET_EMERGENCY_SOUND"; public static final String EMERGENCY_GESTURE_CALL_NUMBER = "emergency_gesture_call_number"; + public static final String EMERGENCY_GESTURE_UI_SHOWING_VALUE = + "emergency_gesture_ui_showing_value"; public static final String EMERGENCY_SETTING_VALUE = "emergency_setting_value"; public static final int EMERGENCY_SETTING_ON = 1; public static final int EMERGENCY_SETTING_OFF = 0; diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java index c61ebc032fa5..0630a2e515e0 100644 --- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java +++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastMetadata.java @@ -261,19 +261,21 @@ public class LocalBluetoothLeBroadcastMetadata { Pattern pattern = Pattern.compile(PATTERN_BT_BROADCAST_METADATA); Matcher match = pattern.matcher(qrCodeString); if (match.find()) { - mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE)); - mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( + try { + mSourceAddressType = Integer.parseInt(match.group(MATCH_INDEX_ADDRESS_TYPE)); + mSourceDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice( match.group(MATCH_INDEX_DEVICE)); - mSourceAdvertisingSid = Integer.parseInt(match.group(MATCH_INDEX_ADVERTISING_SID)); - mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID)); - mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL)); - mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED)); - mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes(); - mPresentationDelayMicros = - Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY)); - - if (DEBUG) { - Log.d(TAG, "Converted qrCodeString result: " + mSourceAdvertisingSid = Integer.parseInt( + match.group(MATCH_INDEX_ADVERTISING_SID)); + mBroadcastId = Integer.parseInt(match.group(MATCH_INDEX_BROADCAST_ID)); + mPaSyncInterval = Integer.parseInt(match.group(MATCH_INDEX_SYNC_INTERVAL)); + mIsEncrypted = Boolean.valueOf(match.group(MATCH_INDEX_IS_ENCRYPTED)); + mBroadcastCode = match.group(MATCH_INDEX_BROADCAST_CODE).getBytes(); + mPresentationDelayMicros = + Integer.parseInt(match.group(MATCH_INDEX_PRESENTATION_DELAY)); + + if (DEBUG) { + Log.d(TAG, "Converted qrCodeString result: " + " ,Type = " + mSourceAddressType + " ,Device = " + mSourceDevice + " ,AdSid = " + mSourceAdvertisingSid @@ -282,11 +284,11 @@ public class LocalBluetoothLeBroadcastMetadata { + " ,encrypted = " + mIsEncrypted + " ,BroadcastCode = " + Arrays.toString(mBroadcastCode) + " ,delay = " + mPresentationDelayMicros); - } + } - mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS)); + mSubgroup = convertToSubgroup(match.group(MATCH_INDEX_SUBGROUPS)); - return new BluetoothLeBroadcastMetadata.Builder() + return new BluetoothLeBroadcastMetadata.Builder() .setSourceDevice(mSourceDevice, mSourceAddressType) .setSourceAdvertisingSid(mSourceAdvertisingSid) .setBroadcastId(mBroadcastId) @@ -296,10 +298,13 @@ public class LocalBluetoothLeBroadcastMetadata { .setPresentationDelayMicros(mPresentationDelayMicros) .addSubgroup(mSubgroup) .build(); + } catch (IllegalArgumentException e) { + Log.d(TAG, "IllegalArgumentException when convert : " + e); + return null; + } } else { if (DEBUG) { - Log.d(TAG, - "The match fail, can not convert it to BluetoothLeBroadcastMetadata."); + Log.d(TAG, "The match fail, can not convert it to BluetoothLeBroadcastMetadata."); } return null; } diff --git a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java index 0fa15eb6bc0c..fdefcde3a170 100644 --- a/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java +++ b/packages/SettingsLib/tests/integ/src/com/android/settingslib/devicestate/DeviceStateRotationLockSettingsManagerTest.java @@ -70,12 +70,20 @@ public class DeviceStateRotationLockSettingsManagerTest { when(mMockContext.getApplicationContext()).thenReturn(mMockContext); when(mMockContext.getResources()).thenReturn(mMockResources); when(mMockContext.getContentResolver()).thenReturn(context.getContentResolver()); + when(mMockResources.getStringArray(R.array.config_perDeviceStateRotationLockDefaults)) + .thenReturn(new String[]{"0:1", "1:0:2", "2:2"}); + when(mMockResources.getIntArray(R.array.config_foldedDeviceStates)) + .thenReturn(new int[]{0}); + when(mMockResources.getIntArray(R.array.config_halfFoldedDeviceStates)) + .thenReturn(new int[]{1}); + when(mMockResources.getIntArray(R.array.config_openDeviceStates)) + .thenReturn(new int[]{2}); mFakeSecureSettings.registerContentObserver( Settings.Secure.DEVICE_STATE_ROTATION_LOCK, /* notifyForDescendents= */ false, //NOTYPO mContentObserver, UserHandle.USER_CURRENT); - mManager = new DeviceStateRotationLockSettingsManager(context, mFakeSecureSettings); + mManager = new DeviceStateRotationLockSettingsManager(mMockContext, mFakeSecureSettings); } @Test @@ -109,7 +117,7 @@ public class DeviceStateRotationLockSettingsManagerTest { public void getSettableDeviceStates_returnsExpectedValuesInOriginalOrder() { when(mMockResources.getStringArray( R.array.config_perDeviceStateRotationLockDefaults)).thenReturn( - new String[]{"2:2", "4:0", "1:1", "0:0"}); + new String[]{"2:1", "1:0:1", "0:2"}); List<SettableDeviceState> settableDeviceStates = DeviceStateRotationLockSettingsManager.getInstance( @@ -117,9 +125,8 @@ public class DeviceStateRotationLockSettingsManagerTest { assertThat(settableDeviceStates).containsExactly( new SettableDeviceState(/* deviceState= */ 2, /* isSettable= */ true), - new SettableDeviceState(/* deviceState= */ 4, /* isSettable= */ false), - new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ true), - new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ false) + new SettableDeviceState(/* deviceState= */ 1, /* isSettable= */ false), + new SettableDeviceState(/* deviceState= */ 0, /* isSettable= */ true) ).inOrder(); } diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java index 8d07fb67c14f..d5386c1d75ac 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/GlobalSettingsValidators.java @@ -126,6 +126,8 @@ public class GlobalSettingsValidators { NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Global.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS, NON_NEGATIVE_INTEGER_VALIDATOR); + VALIDATORS.put(Global.EMERGENCY_GESTURE_STICKY_UI_MAX_DURATION_MILLIS, + NON_NEGATIVE_INTEGER_VALIDATOR); VALIDATORS.put(Global.CALL_AUTO_RETRY, BOOLEAN_VALIDATOR); VALIDATORS.put(Global.DOCK_AUDIO_MEDIA_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java index abd2c7511567..f6c2f6918739 100644 --- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java +++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java @@ -314,6 +314,9 @@ public class SecureSettingsValidators { VALIDATORS.put(Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.EMERGENCY_GESTURE_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put(Secure.EMERGENCY_GESTURE_SOUND_ENABLED, BOOLEAN_VALIDATOR); + VALIDATORS.put(Secure.EMERGENCY_GESTURE_UI_SHOWING, BOOLEAN_VALIDATOR); + VALIDATORS.put( + Secure.EMERGENCY_GESTURE_UI_LAST_STARTED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR); VALIDATORS.put(Secure.ADAPTIVE_CONNECTIVITY_ENABLED, BOOLEAN_VALIDATOR); VALIDATORS.put( Secure.ASSIST_HANDLES_LEARNING_TIME_ELAPSED_MILLIS, NONE_NEGATIVE_LONG_VALIDATOR); diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java index 7607909dc40b..7e89bfce7255 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java @@ -19,9 +19,9 @@ package com.android.providers.settings; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; import static android.os.Process.SYSTEM_UID; -import static android.provider.Settings.Config.SYNC_DISABLED_MODE_NONE; -import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT; -import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT; +import static android.provider.DeviceConfig.SYNC_DISABLED_MODE_NONE; +import static android.provider.DeviceConfig.SYNC_DISABLED_MODE_PERSISTENT; +import static android.provider.DeviceConfig.SYNC_DISABLED_MODE_UNTIL_REBOOT; import static android.provider.Settings.SET_ALL_RESULT_DISABLED; import static android.provider.Settings.SET_ALL_RESULT_FAILURE; import static android.provider.Settings.SET_ALL_RESULT_SUCCESS; diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 1f14723f466f..47abb35d01ab 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -257,6 +257,7 @@ public class SettingsBackupTest { Settings.Global.EMERGENCY_AFFORDANCE_NEEDED, Settings.Global.EMERGENCY_GESTURE_POWER_BUTTON_COOLDOWN_PERIOD_MS, Settings.Global.EMERGENCY_GESTURE_TAP_DETECTION_MIN_TIME_MS, + Settings.Global.EMERGENCY_GESTURE_STICKY_UI_MAX_DURATION_MILLIS, Settings.Global.EMULATE_DISPLAY_CUTOUT, Settings.Global.ENABLE_ACCESSIBILITY_GLOBAL_GESTURE_ENABLED, Settings.Global.ENABLE_CACHE_QUOTA_CALCULATION, @@ -718,6 +719,8 @@ public class SettingsBackupTest { Settings.Secure.DOCKED_CLOCK_FACE, Settings.Secure.DOZE_PULSE_ON_LONG_PRESS, Settings.Secure.EMERGENCY_ASSISTANCE_APPLICATION, + Settings.Secure.EMERGENCY_GESTURE_UI_SHOWING, + Settings.Secure.EMERGENCY_GESTURE_UI_LAST_STARTED_MILLIS, Settings.Secure.ENABLED_INPUT_METHODS, // Intentionally removed in P Settings.Secure.ENABLED_NOTIFICATION_ASSISTANT, Settings.Secure.ENABLED_NOTIFICATION_LISTENERS, diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index b236ac5af104..8b38debef942 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -368,6 +368,7 @@ android_library { plugins: ["dagger2-compiler"], lint: { test: true, + extra_check_modules: ["SystemUILintChecker"], }, } diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml index 4290ca0d0982..650d5fabeb85 100644 --- a/packages/SystemUI/AndroidManifest.xml +++ b/packages/SystemUI/AndroidManifest.xml @@ -27,6 +27,7 @@ <!-- Used to read wallpaper --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> + <uses-permission android:name="android.permission.READ_WALLPAPER_INTERNAL" /> <!-- Used to read storage for all users --> <uses-permission android:name="android.permission.WRITE_MEDIA_STORAGE" /> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml index 0747ef089a8d..81fa8e6bba21 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/values/strings.xml @@ -49,7 +49,6 @@ <!-- Short summary of app that appears as subtext on the service preference in Settings --> <string name="accessibility_menu_summary">Control device via large menu</string> - <!-- TODO(b/113371047): string need to be reviewed --> <!-- String defining the settings name --> <string name="accessibility_menu_settings_name">Accessibility Menu Settings</string> @@ -57,8 +56,6 @@ <string name="accessibility_menu_large_buttons_title">Large buttons</string> <!-- String defining the summary of Large button setting --> <string name="accessibility_menu_large_buttons_summary">Increase size of Accessibility Menu Buttons</string> - <!-- String defining the title of the preference to show help and feedback menu [CHAR LIMIT=40] --> - <string name="pref_help_and_feedback_title">Help & feedback</string> <!-- String defining the title of the preference to show help menu [CHAR LIMIT=40] --> <string name="pref_help_title">Help</string> @@ -67,7 +64,4 @@ <!-- The percentage of the music volume, and double "%" is required to represent the symbol "%" --> <string name="music_volume_percentage_label">Music volume <xliff:g id="percentage">%1$s</xliff:g> %%</string> - <!-- The label of a settings item that displays legal information about the licenses used in this app. [CHAR LIMIT=NONE] --> - <string name="pref_item_licenses">Open Source Licenses</string> - </resources> diff --git a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml index 3b79287ad202..e42c3cd5a368 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml +++ b/packages/SystemUI/accessibility/accessibilitymenu/res/xml/accessibilitymenu_preferences.xml @@ -28,6 +28,6 @@ <Preference android:key="@string/pref_help" - android:title="@string/pref_help_and_feedback_title"/> + android:title="@string/pref_help_title"/> </androidx.preference.PreferenceScreen>
\ No newline at end of file diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java index 8f2934882ede..4b6f9a430390 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java @@ -71,8 +71,6 @@ public class A11yMenuSettingsActivity extends FragmentActivity { private void initializeHelpAndFeedbackPreference() { final Preference prefHelp = findPreference(getString(R.string.pref_help)); if (prefHelp != null) { - prefHelp.setTitle(R.string.pref_help_title); - // Do not allow access to web during setup. if (Settings.Secure.getInt( getContext().getContentResolver(), diff --git a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java index 0e89dcdaf142..7277392f1841 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java +++ b/packages/SystemUI/accessibility/accessibilitymenu/tests/src/com/android/systemui/accessibility/accessibilitymenu/tests/AccessibilityMenuServiceTest.java @@ -29,6 +29,8 @@ import static com.android.systemui.accessibility.accessibilitymenu.Accessibility import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.INTENT_TOGGLE_MENU; import static com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService.PACKAGE_NAME; +import static com.google.common.truth.Truth.assertThat; + import android.accessibilityservice.AccessibilityServiceInfo; import android.app.Instrumentation; import android.app.UiAutomation; @@ -39,6 +41,7 @@ import android.content.IntentFilter; import android.hardware.display.BrightnessInfo; import android.hardware.display.DisplayManager; import android.media.AudioManager; +import android.os.PowerManager; import android.provider.Settings; import android.util.Log; import android.view.accessibility.AccessibilityManager; @@ -375,4 +378,26 @@ public class AccessibilityMenuServiceTest { () -> sLastGlobalAction.compareAndSet( GLOBAL_ACTION_TAKE_SCREENSHOT, NO_GLOBAL_ACTION)); } + + @Test + public void testOnScreenLock_closesMenu() throws Throwable { + openMenu(); + Context context = sInstrumentation.getTargetContext(); + PowerManager powerManager = context.getSystemService(PowerManager.class); + + assertThat(powerManager).isNotNull(); + assertThat(powerManager.isInteractive()).isTrue(); + + sUiAutomation.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN); + TestUtils.waitUntil("Screen did not become locked", + TIMEOUT_UI_CHANGE_S, + () -> !powerManager.isInteractive()); + + sUiAutomation.executeShellCommand("input keyevent KEYCODE_WAKEUP"); + TestUtils.waitUntil("Screen did not wake up", + TIMEOUT_UI_CHANGE_S, + () -> powerManager.isInteractive()); + + assertThat(isMenuVisible()).isFalse(); + } } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt index 280e7ed9ea2e..23fcb691ddb4 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/shaderutil/ShaderUtilLibrary.kt @@ -15,163 +15,166 @@ */ package com.android.systemui.surfaceeffects.shaderutil -/** A common utility functions that are used for computing shaders. */ -class ShaderUtilLibrary { +/** Common utility functions that are used for computing shaders. */ +object ShaderUtilLibrary { // language=AGSL - companion object { - const val SHADER_LIB = - """ - float triangleNoise(vec2 n) { - n = fract(n * vec2(5.3987, 5.4421)); - n += dot(n.yx, n.xy + vec2(21.5351, 14.3137)); - float xy = n.x * n.y; - // compute in [0..2[ and remap to [-1.0..1.0[ - return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0; + const val SHADER_LIB = + """ + float triangleNoise(vec2 n) { + n = fract(n * vec2(5.3987, 5.4421)); + n += dot(n.yx, n.xy + vec2(21.5351, 14.3137)); + float xy = n.x * n.y; + // compute in [0..2[ and remap to [-1.0..1.0[ + return fract(xy * 95.4307) + fract(xy * 75.04961) - 1.0; + } + + const float PI = 3.1415926535897932384626; + + float sparkles(vec2 uv, float t) { + float n = triangleNoise(uv); + float s = 0.0; + for (float i = 0; i < 4; i += 1) { + float l = i * 0.01; + float h = l + 0.1; + float o = smoothstep(n - l, h, n); + o *= abs(sin(PI * o * (t + 0.55 * i))); + s += o; } - - const float PI = 3.1415926535897932384626; - - float sparkles(vec2 uv, float t) { - float n = triangleNoise(uv); - float s = 0.0; - for (float i = 0; i < 4; i += 1) { - float l = i * 0.01; - float h = l + 0.1; - float o = smoothstep(n - l, h, n); - o *= abs(sin(PI * o * (t + 0.55 * i))); - s += o; - } - return s; - } - - vec2 distort(vec2 p, float time, float distort_amount_radial, - float distort_amount_xy) { - float angle = atan(p.y, p.x); - return p + vec2(sin(angle * 8 + time * 0.003 + 1.641), - cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial - + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123), - cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy; - } - - // Perceived luminosity (L′), not absolute luminosity. - half getLuminosity(vec3 c) { - return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b; - } - - // Creates a luminosity mask and clamp to the legal range. - vec3 maskLuminosity(vec3 dest, float lum) { - dest.rgb *= vec3(lum); - // Clip back into the legal range - dest = clamp(dest, vec3(0.), vec3(1.0)); - return dest; - } - - // Return range [-1, 1]. - vec3 hash(vec3 p) { - p = fract(p * vec3(.3456, .1234, .9876)); - p += dot(p, p.yxz + 43.21); - p = (p.xxy + p.yxx) * p.zyx; - return (fract(sin(p) * 4567.1234567) - .5) * 2.; - } - - // Skew factors (non-uniform). - const half SKEW = 0.3333333; // 1/3 - const half UNSKEW = 0.1666667; // 1/6 - - // Return range roughly [-1,1]. - // It's because the hash function (that returns a random gradient vector) returns - // different magnitude of vectors. Noise doesn't have to be in the precise range thus - // skipped normalize. - half simplex3d(vec3 p) { - // Skew the input coordinate, so that we get squashed cubical grid - vec3 s = floor(p + (p.x + p.y + p.z) * SKEW); - - // Unskew back - vec3 u = s - (s.x + s.y + s.z) * UNSKEW; - - // Unskewed coordinate that is relative to p, to compute the noise contribution - // based on the distance. - vec3 c0 = p - u; - - // We have six simplices (in this case tetrahedron, since we are in 3D) that we - // could possibly in. - // Here, we are finding the correct tetrahedron (simplex shape), and traverse its - // four vertices (c0..3) when computing noise contribution. - // The way we find them is by comparing c0's x,y,z values. - // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in - // by comparing x and y values. i.e. x>y lower, x<y, upper triangle. - // Same applies in 3D. - // - // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0) - // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1) - // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1) - // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1) - // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1) - // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1) - // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1) - // - // The rule is: - // * For offset1, set 1 at the max component, otherwise 0. - // * For offset2, set 0 at the min component, otherwise 1. - // * For offset3, set 1 for all. - // - // Encode x0-y0, y0-z0, z0-x0 in a vec3 - vec3 en = c0 - c0.yzx; - // Each represents whether x0>y0, y0>z0, z0>x0 - en = step(vec3(0.), en); - // en.zxy encodes z0>x0, x0>y0, y0>x0 - vec3 offset1 = en * (1. - en.zxy); // find max - vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min) - vec3 offset3 = vec3(1.); - - vec3 c1 = c0 - offset1 + UNSKEW; - vec3 c2 = c0 - offset2 + UNSKEW * 2.; - vec3 c3 = c0 - offset3 + UNSKEW * 3.; - - // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution) - // - // First compute d^2, squared distance to the point. - vec4 w; // w = max(0, r^2 - d^2)) - w.x = dot(c0, c0); - w.y = dot(c1, c1); - w.z = dot(c2, c2); - w.w = dot(c3, c3); - - // Noise contribution should decay to zero before they cross the simplex boundary. - // Usually r^2 is 0.5 or 0.6; - // 0.5 ensures continuity but 0.6 increases the visual quality for the application - // where discontinuity isn't noticeable. - w = max(0.6 - w, 0.); - - // Noise contribution from each point. - vec4 nc; - nc.x = dot(hash(s), c0); - nc.y = dot(hash(s + offset1), c1); - nc.z = dot(hash(s + offset2), c2); - nc.w = dot(hash(s + offset3), c3); - - nc *= w*w*w*w; - - // Add all the noise contributions. - // Should multiply by the possible max contribution to adjust the range in [-1,1]. - return dot(vec4(32.), nc); - } - - // Random rotations. - // The way you create fractal noise is layering simplex noise with some rotation. - // To make random cloud looking noise, the rotations should not align. (Otherwise it - // creates patterned noise). - // Below rotations only rotate in one axis. - const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15); - const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95); - const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44); - - // Octave = 4 - // Divide each coefficient by 3 to produce more grainy noise. - half simplex3d_fractal(vec3 mat) { - return 0.675 * simplex3d(mat * rot1) + 0.225 * simplex3d(2.0 * mat * rot2) - + 0.075 * simplex3d(4.0 * mat * rot3) + 0.025 * simplex3d(8.0 * mat); - } - """ - } + return s; + } + + vec2 distort(vec2 p, float time, float distort_amount_radial, + float distort_amount_xy) { + float angle = atan(p.y, p.x); + return p + vec2(sin(angle * 8 + time * 0.003 + 1.641), + cos(angle * 5 + 2.14 + time * 0.00412)) * distort_amount_radial + + vec2(sin(p.x * 0.01 + time * 0.00215 + 0.8123), + cos(p.y * 0.01 + time * 0.005931)) * distort_amount_xy; + } + + // Perceived luminosity (L′), not absolute luminosity. + half getLuminosity(vec3 c) { + return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b; + } + + // Creates a luminosity mask and clamp to the legal range. + vec3 maskLuminosity(vec3 dest, float lum) { + dest.rgb *= vec3(lum); + // Clip back into the legal range + dest = clamp(dest, vec3(0.), vec3(1.0)); + return dest; + } + + // Return range [-1, 1]. + vec3 hash(vec3 p) { + p = fract(p * vec3(.3456, .1234, .9876)); + p += dot(p, p.yxz + 43.21); + p = (p.xxy + p.yxx) * p.zyx; + return (fract(sin(p) * 4567.1234567) - .5) * 2.; + } + + // Skew factors (non-uniform). + const half SKEW = 0.3333333; // 1/3 + const half UNSKEW = 0.1666667; // 1/6 + + // Return range roughly [-1,1]. + // It's because the hash function (that returns a random gradient vector) returns + // different magnitude of vectors. Noise doesn't have to be in the precise range thus + // skipped normalize. + half simplex3d(vec3 p) { + // Skew the input coordinate, so that we get squashed cubical grid + vec3 s = floor(p + (p.x + p.y + p.z) * SKEW); + + // Unskew back + vec3 u = s - (s.x + s.y + s.z) * UNSKEW; + + // Unskewed coordinate that is relative to p, to compute the noise contribution + // based on the distance. + vec3 c0 = p - u; + + // We have six simplices (in this case tetrahedron, since we are in 3D) that we + // could possibly in. + // Here, we are finding the correct tetrahedron (simplex shape), and traverse its + // four vertices (c0..3) when computing noise contribution. + // The way we find them is by comparing c0's x,y,z values. + // For example in 2D, we can find the triangle (simplex shape in 2D) that we are in + // by comparing x and y values. i.e. x>y lower, x<y, upper triangle. + // Same applies in 3D. + // + // Below indicates the offsets (or offset directions) when c0=(x0,y0,z0) + // x0>y0>z0: (1,0,0), (1,1,0), (1,1,1) + // x0>z0>y0: (1,0,0), (1,0,1), (1,1,1) + // z0>x0>y0: (0,0,1), (1,0,1), (1,1,1) + // z0>y0>x0: (0,0,1), (0,1,1), (1,1,1) + // y0>z0>x0: (0,1,0), (0,1,1), (1,1,1) + // y0>x0>z0: (0,1,0), (1,1,0), (1,1,1) + // + // The rule is: + // * For offset1, set 1 at the max component, otherwise 0. + // * For offset2, set 0 at the min component, otherwise 1. + // * For offset3, set 1 for all. + // + // Encode x0-y0, y0-z0, z0-x0 in a vec3 + vec3 en = c0 - c0.yzx; + // Each represents whether x0>y0, y0>z0, z0>x0 + en = step(vec3(0.), en); + // en.zxy encodes z0>x0, x0>y0, y0>x0 + vec3 offset1 = en * (1. - en.zxy); // find max + vec3 offset2 = 1. - en.zxy * (1. - en); // 1-(find min) + vec3 offset3 = vec3(1.); + + vec3 c1 = c0 - offset1 + UNSKEW; + vec3 c2 = c0 - offset2 + UNSKEW * 2.; + vec3 c3 = c0 - offset3 + UNSKEW * 3.; + + // Kernel summation: dot(max(0, r^2-d^2))^4, noise contribution) + // + // First compute d^2, squared distance to the point. + vec4 w; // w = max(0, r^2 - d^2)) + w.x = dot(c0, c0); + w.y = dot(c1, c1); + w.z = dot(c2, c2); + w.w = dot(c3, c3); + + // Noise contribution should decay to zero before they cross the simplex boundary. + // Usually r^2 is 0.5 or 0.6; + // 0.5 ensures continuity but 0.6 increases the visual quality for the application + // where discontinuity isn't noticeable. + w = max(0.6 - w, 0.); + + // Noise contribution from each point. + vec4 nc; + nc.x = dot(hash(s), c0); + nc.y = dot(hash(s + offset1), c1); + nc.z = dot(hash(s + offset2), c2); + nc.w = dot(hash(s + offset3), c3); + + nc *= w*w*w*w; + + // Add all the noise contributions. + // Should multiply by the possible max contribution to adjust the range in [-1,1]. + return dot(vec4(32.), nc); + } + + // Random rotations. + // The way you create fractal noise is layering simplex noise with some rotation. + // To make random cloud looking noise, the rotations should not align. (Otherwise it + // creates patterned noise). + // Below rotations only rotate in one axis. + const mat3 rot1 = mat3(1.0, 0. ,0., 0., 0.15, -0.98, 0., 0.98, 0.15); + const mat3 rot2 = mat3(-0.95, 0. ,-0.3, 0., 1., 0., 0.3, 0., -0.95); + const mat3 rot3 = mat3(1.0, 0. ,0., 0., -0.44, -0.89, 0., 0.89, -0.44); + + // Octave = 4 + // Divide each coefficient by 3 to produce more grainy noise. + half simplex3d_fractal(vec3 p) { + return 0.675 * simplex3d(p * rot1) + 0.225 * simplex3d(2.0 * p * rot2) + + 0.075 * simplex3d(4.0 * p * rot3) + 0.025 * simplex3d(8.0 * p); + } + + // Screen blend + vec3 screen(vec3 dest, vec3 src) { + return dest + src - dest * src; + } + """ } diff --git a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt index 0e2266734274..d1ba7c4de35c 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/surfaceeffects/turbulencenoise/TurbulenceNoiseShader.kt @@ -36,6 +36,7 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : uniform float in_aspectRatio; uniform float in_opacity; uniform float in_pixelDensity; + uniform float in_inverseLuma; layout(color) uniform vec4 in_color; layout(color) uniform vec4 in_backgroundColor; """ @@ -47,7 +48,7 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - float luma = simplex3d(noiseP) * in_opacity; + float luma = abs(in_inverseLuma - simplex3d(noiseP)) * in_opacity; vec3 mask = maskLuminosity(in_color.rgb, luma); vec3 color = in_backgroundColor.rgb + mask * 0.6; @@ -69,7 +70,7 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : uv.x *= in_aspectRatio; vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum; - float luma = simplex3d_fractal(noiseP) * in_opacity; + float luma = abs(in_inverseLuma - simplex3d_fractal(noiseP)) * in_opacity; vec3 mask = maskLuminosity(in_color.rgb, luma); vec3 color = in_backgroundColor.rgb + mask * 0.6; @@ -123,6 +124,17 @@ class TurbulenceNoiseShader(useFractal: Boolean = false) : setFloatUniform("in_aspectRatio", width / max(height, 0.001f)) } + /** + * Sets whether to inverse the luminosity of the noise. + * + * By default noise will be used as a luma matte as is. This means that you will see color in + * the brighter area. If you want to invert it, meaning blend color onto the darker side, set to + * true. + */ + fun setInverseNoiseLuminosity(inverse: Boolean) { + setFloatUniform("in_inverseLuma", if (inverse) 1f else 0f) + } + /** Current noise movements in x, y, and z axes. */ var noiseOffsetX: Float = 0f private set diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt index 0d880759bd09..f9e8aafcfa47 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/customization/data/content/CustomizationProviderContract.kt @@ -184,6 +184,9 @@ object CustomizationProviderContract { /** Flag denoting whether the Monochromatic Theme is enabled. */ const val FLAG_NAME_MONOCHROMATIC_THEME = "is_monochromatic_theme_enabled" + /** Flag denoting AI Wallpapers are enabled in wallpaper picker. */ + const val FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP = "wallpaper_picker_ui_for_aiwp" + object Columns { /** String. Unique ID for the flag. */ const val NAME = "name" diff --git a/packages/SystemUI/res/layout/activity_rear_display_education.xml b/packages/SystemUI/res/layout/activity_rear_display_education.xml index 094807e0fad4..c295cfe7a2e0 100644 --- a/packages/SystemUI/res/layout/activity_rear_display_education.xml +++ b/packages/SystemUI/res/layout/activity_rear_display_education.xml @@ -30,6 +30,7 @@ <com.airbnb.lottie.LottieAnimationView android:id="@+id/rear_display_folded_animation" + android:importantForAccessibility="no" android:layout_width="@dimen/rear_display_animation_width" android:layout_height="@dimen/rear_display_animation_height" android:layout_gravity="center" diff --git a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml index e970bc573b01..c12bfcca4eff 100644 --- a/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml +++ b/packages/SystemUI/res/layout/activity_rear_display_education_opened.xml @@ -31,6 +31,7 @@ <com.airbnb.lottie.LottieAnimationView android:id="@+id/rear_display_folded_animation" + android:importantForAccessibility="no" android:layout_width="@dimen/rear_display_animation_width" android:layout_height="@dimen/rear_display_animation_height" android:layout_gravity="center" diff --git a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml index 5b2ec483f50e..0cd062383570 100644 --- a/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml +++ b/packages/SystemUI/res/layout/dream_overlay_home_controls_chip.xml @@ -14,22 +14,15 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<FrameLayout +<com.android.systemui.animation.view.LaunchableImageView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:paddingVertical="@dimen/dream_overlay_complication_home_controls_padding"> - - <com.android.systemui.animation.view.LaunchableImageView - android:id="@+id/home_controls_chip" - android:layout_height="@dimen/dream_overlay_bottom_affordance_height" - android:layout_width="@dimen/dream_overlay_bottom_affordance_width" - android:layout_gravity="bottom|start" - android:padding="@dimen/dream_overlay_bottom_affordance_padding" - android:background="@drawable/dream_overlay_bottom_affordance_bg" - android:scaleType="fitCenter" - android:tint="?android:attr/textColorPrimary" - android:src="@drawable/controls_icon" - android:contentDescription="@string/quick_controls_title" /> - -</FrameLayout> + android:id="@+id/home_controls_chip" + android:layout_height="@dimen/dream_overlay_bottom_affordance_height" + android:layout_width="@dimen/dream_overlay_bottom_affordance_width" + android:layout_gravity="bottom|start" + android:padding="@dimen/dream_overlay_bottom_affordance_padding" + android:background="@drawable/dream_overlay_bottom_affordance_bg" + android:scaleType="fitCenter" + android:tint="?android:attr/textColorPrimary" + android:src="@drawable/controls_icon" + android:contentDescription="@string/quick_controls_title" /> diff --git a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml index 50f3ffcaf968..b75c638904d8 100644 --- a/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml +++ b/packages/SystemUI/res/layout/dream_overlay_media_entry_chip.xml @@ -17,13 +17,12 @@ <ImageView xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/media_entry_chip" - android:layout_height="@dimen/keyguard_affordance_fixed_height" - android:layout_width="@dimen/keyguard_affordance_fixed_width" + android:layout_height="@dimen/dream_overlay_bottom_affordance_height" + android:layout_width="@dimen/dream_overlay_bottom_affordance_width" android:layout_gravity="bottom|start" - android:scaleType="center" + android:scaleType="fitCenter" + android:padding="@dimen/dream_overlay_bottom_affordance_padding" android:tint="?android:attr/textColorPrimary" android:src="@drawable/ic_music_note" - android:background="@drawable/keyguard_bottom_affordance_bg" - android:layout_marginStart="@dimen/keyguard_affordance_horizontal_offset" - android:layout_marginBottom="@dimen/keyguard_affordance_vertical_offset" + android:background="@drawable/dream_overlay_bottom_affordance_bg" android:contentDescription="@string/controls_media_title" /> diff --git a/packages/SystemUI/res/layout/notification_conversation_info.xml b/packages/SystemUI/res/layout/notification_conversation_info.xml index 9b2f0ac364da..79948da978d7 100644 --- a/packages/SystemUI/res/layout/notification_conversation_info.xml +++ b/packages/SystemUI/res/layout/notification_conversation_info.xml @@ -311,7 +311,7 @@ android:clickable="false" android:focusable="false" android:ellipsize="end" - android:maxLines="3" + android:maxLines="4" android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> @@ -364,7 +364,7 @@ android:clickable="false" android:focusable="false" android:ellipsize="end" - android:maxLines="3" + android:maxLines="4" android:textAppearance="@style/TextAppearance.NotificationImportanceDetail"/> </com.android.systemui.statusbar.notification.row.ButtonLinearLayout> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index e5cd0c55027b..082f3855affc 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -99,6 +99,7 @@ <item>accessibility_display_daltonizer_enabled:color_correction</item> <item>accessibility_display_inversion_enabled:inversion</item> <item>one_handed_mode_enabled:onehanded</item> + <item>accessibility_font_scaling_has_been_changed:font_scaling</item> </string-array> <!-- Use collapsed layout for media player in landscape QQS --> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 4d38541e2cc9..ee9e07a1e908 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -43,8 +43,6 @@ <dimen name="navigation_edge_panel_height">268dp</dimen> <!-- The threshold to drag to trigger the edge action --> <dimen name="navigation_edge_action_drag_threshold">16dp</dimen> - <!-- The drag distance to consider evaluating gesture --> - <dimen name="navigation_edge_action_min_distance_to_start_animation">24dp</dimen> <!-- The threshold to progress back animation for edge swipe --> <dimen name="navigation_edge_action_progress_threshold">412dp</dimen> <!-- The minimum display position of the arrow on the screen --> @@ -58,8 +56,6 @@ <!-- The thickness of the arrow --> <dimen name="navigation_edge_arrow_thickness">4dp</dimen> - <!-- The minimum delta needed to change direction / stop triggering back --> - <dimen name="navigation_edge_minimum_x_delta_for_switch">32dp</dimen> <!-- entry state --> <item name="navigation_edge_entry_scale" format="float" type="dimen">0.98</item> @@ -1636,7 +1632,6 @@ <dimen name="dream_overlay_bottom_affordance_padding">14dp</dimen> <dimen name="dream_overlay_complication_clock_time_text_size">86dp</dimen> <dimen name="dream_overlay_complication_clock_time_translation_y">28dp</dimen> - <dimen name="dream_overlay_complication_home_controls_padding">28dp</dimen> <dimen name="dream_overlay_complication_clock_subtitle_text_size">24sp</dimen> <dimen name="dream_overlay_complication_preview_text_size">36sp</dimen> <dimen name="dream_overlay_complication_preview_icon_padding">28dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index ebf0f8ecd48c..2aa912ce250d 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2877,7 +2877,7 @@ <string name="manage_users">Manage users</string> <!-- Toast shown when a notification does not support dragging to split [CHAR LIMIT=NONE] --> - <string name="drag_split_not_supported">This notification does not support dragging to Splitscreen.</string> + <string name="drag_split_not_supported">This notification does not support dragging to split screen</string> <!-- Content description for the Wi-Fi off icon in the dream overlay status bar [CHAR LIMIT=NONE] --> <string name="dream_overlay_status_bar_wifi_off">Wi\u2011Fi unavailable</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java index 0d36d046392e..13513148b248 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java @@ -177,42 +177,96 @@ public class QuickStepContract { public static String getSystemUiStateString(int flags) { StringJoiner str = new StringJoiner("|"); - str.add((flags & SYSUI_STATE_SCREEN_PINNING) != 0 ? "screen_pinned" : ""); - str.add((flags & SYSUI_STATE_OVERVIEW_DISABLED) != 0 ? "overview_disabled" : ""); - str.add((flags & SYSUI_STATE_HOME_DISABLED) != 0 ? "home_disabled" : ""); - str.add((flags & SYSUI_STATE_SEARCH_DISABLED) != 0 ? "search_disabled" : ""); - str.add((flags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0 ? "navbar_hidden" : ""); - str.add((flags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0 ? "notif_visible" : ""); - str.add((flags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) != 0 ? "qs_visible" : ""); - str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) != 0 ? "keygrd_visible" : ""); - str.add((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0 - ? "keygrd_occluded" : ""); - str.add((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0 ? "bouncer_visible" : ""); - str.add((flags & SYSUI_STATE_DIALOG_SHOWING) != 0 ? "dialog_showing" : ""); - str.add((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0 ? "a11y_click" : ""); - str.add((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0 ? "a11y_long_click" : ""); - str.add((flags & SYSUI_STATE_TRACING_ENABLED) != 0 ? "tracing" : ""); - str.add((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0 - ? "asst_gesture_constrain" : ""); - str.add((flags & SYSUI_STATE_BUBBLES_EXPANDED) != 0 ? "bubbles_expanded" : ""); - str.add((flags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0 ? "one_handed_active" : ""); - str.add((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0 - ? "allow_gesture" : ""); - str.add((flags & SYSUI_STATE_IME_SHOWING) != 0 ? "ime_visible" : ""); - str.add((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0 ? "magnification_overlap" : ""); - str.add((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0 ? "ime_switcher_showing" : ""); - str.add((flags & SYSUI_STATE_DEVICE_DOZING) != 0 ? "device_dozing" : ""); - str.add((flags & SYSUI_STATE_BACK_DISABLED) != 0 ? "back_disabled" : ""); - str.add((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0 - ? "bubbles_mange_menu_expanded" : ""); - str.add((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0 ? "immersive_mode" : ""); - str.add((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0 ? "vis_win_showing" : ""); - str.add((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0 - ? "freeform_active_in_desktop_mode" : ""); - str.add((flags & SYSUI_STATE_DEVICE_DREAMING) != 0 ? "device_dreaming" : ""); - str.add("screen_" - + ((flags & SYSUI_STATE_SCREEN_TRANSITION) != 0 ? "turning_" : "") - + ((flags & SYSUI_STATE_SCREEN_ON) != 0 ? "on" : "off")); + if ((flags & SYSUI_STATE_SCREEN_PINNING) != 0) { + str.add("screen_pinned"); + } + if ((flags & SYSUI_STATE_OVERVIEW_DISABLED) != 0) { + str.add("overview_disabled"); + } + if ((flags & SYSUI_STATE_HOME_DISABLED) != 0) { + str.add("home_disabled"); + } + if ((flags & SYSUI_STATE_SEARCH_DISABLED) != 0) { + str.add("search_disabled"); + } + if ((flags & SYSUI_STATE_NAV_BAR_HIDDEN) != 0) { + str.add("navbar_hidden"); + } + if ((flags & SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED) != 0) { + str.add("notif_visible"); + } + if ((flags & SYSUI_STATE_QUICK_SETTINGS_EXPANDED) != 0) { + str.add("qs_visible"); + } + if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING) != 0) { + str.add("keygrd_visible"); + } + if ((flags & SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED) != 0) { + str.add("keygrd_occluded"); + } + if ((flags & SYSUI_STATE_BOUNCER_SHOWING) != 0) { + str.add("bouncer_visible"); + } + if ((flags & SYSUI_STATE_DIALOG_SHOWING) != 0) { + str.add("dialog_showing"); + } + if ((flags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0) { + str.add("a11y_click"); + } + if ((flags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0) { + str.add("a11y_long_click"); + } + if ((flags & SYSUI_STATE_TRACING_ENABLED) != 0) { + str.add("tracing"); + } + if ((flags & SYSUI_STATE_ASSIST_GESTURE_CONSTRAINED) != 0) { + str.add("asst_gesture_constrain"); + } + if ((flags & SYSUI_STATE_BUBBLES_EXPANDED) != 0) { + str.add("bubbles_expanded"); + } + if ((flags & SYSUI_STATE_ONE_HANDED_ACTIVE) != 0) { + str.add("one_handed_active"); + } + if ((flags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) != 0) { + str.add("allow_gesture"); + } + if ((flags & SYSUI_STATE_IME_SHOWING) != 0) { + str.add("ime_visible"); + } + if ((flags & SYSUI_STATE_MAGNIFICATION_OVERLAP) != 0) { + str.add("magnification_overlap"); + } + if ((flags & SYSUI_STATE_IME_SWITCHER_SHOWING) != 0) { + str.add("ime_switcher_showing"); + } + if ((flags & SYSUI_STATE_DEVICE_DOZING) != 0) { + str.add("device_dozing"); + } + if ((flags & SYSUI_STATE_BACK_DISABLED) != 0) { + str.add("back_disabled"); + } + if ((flags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0) { + str.add("bubbles_mange_menu_expanded"); + } + if ((flags & SYSUI_STATE_IMMERSIVE_MODE) != 0) { + str.add("immersive_mode"); + } + if ((flags & SYSUI_STATE_VOICE_INTERACTION_WINDOW_SHOWING) != 0) { + str.add("vis_win_showing"); + } + if ((flags & SYSUI_STATE_FREEFORM_ACTIVE_IN_DESKTOP_MODE) != 0) { + str.add("freeform_active_in_desktop_mode"); + } + if ((flags & SYSUI_STATE_DEVICE_DREAMING) != 0) { + str.add("device_dreaming"); + } + if ((flags & SYSUI_STATE_SCREEN_TRANSITION) != 0) { + str.add("screen_transition"); + } + if ((flags & SYSUI_STATE_SCREEN_ON) != 0) { + str.add("screen_on"); + } return str.toString(); } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index b3a641cbe93a..0e2f8f07cb01 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -4125,6 +4125,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab KeyguardFingerprintListenModel.TABLE_HEADERS, mFingerprintListenBuffer.toList() ).printTableData(pw); + } else if (mFpm != null && mFingerprintSensorProperties.isEmpty()) { + final int userId = mUserTracker.getUserId(); + pw.println(" Fingerprint state (user=" + userId + ")"); + pw.println(" mFingerprintSensorProperties.isEmpty=" + + mFingerprintSensorProperties.isEmpty()); + pw.println(" mFpm.isHardwareDetected=" + + mFpm.isHardwareDetected()); + + new DumpsysTableLogger( + "KeyguardFingerprintListen", + KeyguardFingerprintListenModel.TABLE_HEADERS, + mFingerprintListenBuffer.toList() + ).printTableData(pw); } if (mFaceManager != null && !mFaceSensorProperties.isEmpty()) { final int userId = mUserTracker.getUserId(); @@ -4155,6 +4168,19 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab KeyguardFaceListenModel.TABLE_HEADERS, mFaceListenBuffer.toList() ).printTableData(pw); + } else if (mFaceManager != null && mFaceSensorProperties.isEmpty()) { + final int userId = mUserTracker.getUserId(); + pw.println(" Face state (user=" + userId + ")"); + pw.println(" mFaceSensorProperties.isEmpty=" + + mFaceSensorProperties.isEmpty()); + pw.println(" mFaceManager.isHardwareDetected=" + + mFaceManager.isHardwareDetected()); + + new DumpsysTableLogger( + "KeyguardFaceListen", + KeyguardFingerprintListenModel.TABLE_HEADERS, + mFingerprintListenBuffer.toList() + ).printTableData(pw); } new DumpsysTableLogger( diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt index 53a421d9eccc..1836ce857783 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/fontscaling/FontScalingDialog.kt @@ -15,6 +15,7 @@ */ package com.android.systemui.accessibility.fontscaling +import android.annotation.WorkerThread import android.content.Context import android.content.pm.ActivityInfo import android.content.res.Configuration @@ -27,18 +28,26 @@ import android.widget.SeekBar.OnSeekBarChangeListener import android.widget.TextView import com.android.systemui.R import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SystemSettings +import java.util.concurrent.Executor import kotlin.math.roundToInt /** The Dialog that contains a seekbar for changing the font size. */ -class FontScalingDialog(context: Context, private val systemSettings: SystemSettings) : - SystemUIDialog(context) { +class FontScalingDialog( + context: Context, + private val systemSettings: SystemSettings, + private val secureSettings: SecureSettings, + @Background private val backgroundExecutor: Executor +) : SystemUIDialog(context) { private val strEntryValues: Array<String> = context.resources.getStringArray(com.android.settingslib.R.array.entryvalues_font_size) private lateinit var title: TextView private lateinit var doneButton: Button private lateinit var seekBarWithIconButtonsView: SeekBarWithIconButtonsView + private var lastProgress: Int = -1 private val configuration: Configuration = Configuration(context.getResources().getConfiguration()) @@ -70,12 +79,22 @@ class FontScalingDialog(context: Context, private val systemSettings: SystemSett seekBarWithIconButtonsView.setMax((strEntryValues).size - 1) val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, 1.0f) - seekBarWithIconButtonsView.setProgress(fontSizeValueToIndex(currentScale)) + lastProgress = fontSizeValueToIndex(currentScale) + seekBarWithIconButtonsView.setProgress(lastProgress) seekBarWithIconButtonsView.setOnSeekBarChangeListener( object : OnSeekBarChangeListener { override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { - systemSettings.putString(Settings.System.FONT_SCALE, strEntryValues[progress]) + if (progress != lastProgress) { + if (!fontSizeHasBeenChangedFromTile) { + backgroundExecutor.execute { updateSecureSettingsIfNeeded() } + fontSizeHasBeenChangedFromTile = true + } + + backgroundExecutor.execute { updateFontScale(strEntryValues[progress]) } + + lastProgress = progress + } } override fun onStartTrackingTouch(seekBar: SeekBar) { @@ -115,4 +134,28 @@ class FontScalingDialog(context: Context, private val systemSettings: SystemSett } } } + + @WorkerThread + fun updateFontScale(newScale: String) { + systemSettings.putString(Settings.System.FONT_SCALE, newScale) + } + + @WorkerThread + fun updateSecureSettingsIfNeeded() { + if ( + secureSettings.getString(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED) != + ON + ) { + secureSettings.putString( + Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, + ON + ) + } + } + + companion object { + private const val ON = "1" + private const val OFF = "0" + private var fontSizeHasBeenChangedFromTile = false + } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java index a7b6e6ae6d40..13bb6d345dbd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java @@ -658,7 +658,9 @@ public abstract class AuthBiometricView extends LinearLayout { protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mIconController.onConfigurationChanged(newConfig); - updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE)); + if (mSavedState != null) { + updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE)); + } } @Override diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java index 4aa985b50967..705fc8c1a8fd 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java @@ -823,7 +823,7 @@ public class AuthController implements CoreStartable, CommandQueue.Callbacks, final Rect overlayBounds = new Rect( 0, /* left */ - mCachedDisplayInfo.getNaturalHeight() / 2, /* top */ + 0, /* top */ mCachedDisplayInfo.getNaturalWidth(), /* right */ mCachedDisplayInfo.getNaturalHeight() /* botom */); diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index bb35355ba03a..e7ec3eb7e81a 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -42,6 +42,7 @@ import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; +import android.hardware.input.InputManager; import android.os.Build; import android.os.Handler; import android.os.PowerManager; @@ -169,6 +170,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final AlternateBouncerInteractor mAlternateBouncerInteractor; @NonNull private final SecureSettings mSecureSettings; @NonNull private final UdfpsUtils mUdfpsUtils; + @NonNull private final InputManager mInputManager; // 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. @@ -576,6 +578,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { data.getTime(), data.getGestureStart(), mStatusBarStateController.isDozing()); + + // Pilfer if valid overlap, don't allow following events to reach keyguard + mInputManager.pilferPointers( + mOverlay.getOverlayView().getViewRootImpl().getInputToken()); break; case UP: @@ -599,9 +605,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { break; case UNCHANGED: - if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getX(), event.getY(), + if (!isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true) && mActivePointerId == MotionEvent.INVALID_POINTER_ID - && event.getActionMasked() == MotionEvent.ACTION_DOWN && mAlternateBouncerInteractor.isVisibleState()) { // No pointer on sensor, forward to keyguard if alternateBouncer is visible mKeyguardViewManager.onTouch(event); @@ -612,6 +617,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { } logBiometricTouch(processedTouch.getEvent(), data); + // Always pilfer pointers that are within sensor area + if (isWithinSensorArea(mOverlay.getOverlayView(), event.getRawX(), event.getRawY(), true)) { + Log.d("Austin", "pilferTouch invalid overlap"); + mInputManager.pilferPointers( + mOverlay.getOverlayView().getViewRootImpl().getInputToken()); + } + return processedTouch.getTouchData().isWithinSensor(mOverlayParams.getNativeSensorBounds()); } @@ -798,6 +810,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull SessionTracker sessionTracker, @NonNull AlternateBouncerInteractor alternateBouncerInteractor, @NonNull SecureSettings secureSettings, + @NonNull InputManager inputManager, @NonNull UdfpsUtils udfpsUtils) { mContext = context; mExecution = execution; @@ -841,6 +854,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { mAlternateBouncerInteractor = alternateBouncerInteractor; mSecureSettings = secureSettings; mUdfpsUtils = udfpsUtils; + mInputManager = inputManager; mTouchProcessor = mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION) ? singlePointerTouchProcessor : null; diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt index 92a7094c22bf..9a0792ee7923 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt @@ -37,12 +37,12 @@ class UdfpsOverlayInteractor @Inject constructor(private val authController: AuthController, @Application scope: CoroutineScope) { - /** Whether a touch should be intercepted or allowed to pass to the UdfpsOverlay */ - fun canInterceptTouchInUdfpsBounds(ev: MotionEvent): Boolean { + /** Whether a touch is within the under-display fingerprint sensor area */ + fun isTouchWithinUdfpsArea(ev: MotionEvent): Boolean { val isUdfpsEnrolled = authController.isUdfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser()) - val isWithinUdfpsOverlayBounds = + val isWithinOverlayBounds = udfpsOverlayParams.value.overlayBounds.contains(ev.rawX.toInt(), ev.rawY.toInt()) - return !isUdfpsEnrolled || !isWithinUdfpsOverlayBounds + return isUdfpsEnrolled && isWithinOverlayBounds } /** Returns the current udfpsOverlayParams */ diff --git a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java index e8c83b1e49dd..012385788952 100644 --- a/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java +++ b/packages/SystemUI/src/com/android/systemui/classifier/ProximityClassifier.java @@ -81,19 +81,19 @@ class ProximityClassifier extends FalsingClassifier { int action = motionEvent.getActionMasked(); if (action == MotionEvent.ACTION_DOWN) { - mGestureStartTimeNs = motionEvent.getEventTimeNano(); + mGestureStartTimeNs = motionEvent.getEventTimeNanos(); if (mPrevNearTimeNs > 0) { // We only care about if the proximity sensor is triggered while a move event is // happening. - mPrevNearTimeNs = motionEvent.getEventTimeNano(); + mPrevNearTimeNs = motionEvent.getEventTimeNanos(); } logDebug("Gesture start time: " + mGestureStartTimeNs); mNearDurationNs = 0; } if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { - update(mNear, motionEvent.getEventTimeNano()); - long duration = motionEvent.getEventTimeNano() - mGestureStartTimeNs; + update(mNear, motionEvent.getEventTimeNanos()); + long duration = motionEvent.getEventTimeNanos() - mGestureStartTimeNs; logDebug("Gesture duration, Proximity duration: " + duration + ", " + mNearDurationNs); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java index 7f395d863c3f..82a885892b75 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/DreamHomeControlsComplication.java @@ -33,7 +33,6 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; import com.android.systemui.CoreStartable; -import com.android.systemui.R; import com.android.systemui.animation.ActivityLaunchAnimator; import com.android.systemui.controls.ControlsServiceInfo; import com.android.systemui.controls.dagger.ControlsComponent; @@ -157,14 +156,14 @@ public class DreamHomeControlsComplication implements Complication { * Contains values/logic associated with the dream complication view. */ public static class DreamHomeControlsChipViewHolder implements ViewHolder { - private final View mView; + private final ImageView mView; private final ComplicationLayoutParams mLayoutParams; private final DreamHomeControlsChipViewController mViewController; @Inject DreamHomeControlsChipViewHolder( DreamHomeControlsChipViewController dreamHomeControlsChipViewController, - @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view, + @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, @Named(DREAM_HOME_CONTROLS_CHIP_LAYOUT_PARAMS) ComplicationLayoutParams layoutParams ) { mView = view; @@ -174,7 +173,7 @@ public class DreamHomeControlsComplication implements Complication { } @Override - public View getView() { + public ImageView getView() { return mView; } @@ -187,7 +186,7 @@ public class DreamHomeControlsComplication implements Complication { /** * Controls behavior of the dream complication. */ - static class DreamHomeControlsChipViewController extends ViewController<View> { + static class DreamHomeControlsChipViewController extends ViewController<ImageView> { private static final String TAG = "DreamHomeControlsCtrl"; private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); @@ -216,7 +215,7 @@ public class DreamHomeControlsComplication implements Complication { @Inject DreamHomeControlsChipViewController( - @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) View view, + @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view, ActivityStarter activityStarter, Context context, ControlsComponent controlsComponent, @@ -231,10 +230,9 @@ public class DreamHomeControlsComplication implements Complication { @Override protected void onViewAttached() { - final ImageView chip = mView.findViewById(R.id.home_controls_chip); - chip.setImageResource(mControlsComponent.getTileImageId()); - chip.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId())); - chip.setOnClickListener(this::onClickHomeControls); + mView.setImageResource(mControlsComponent.getTileImageId()); + mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId())); + mView.setOnClickListener(this::onClickHomeControls); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java index a7aa97f74e31..cf05d2d9cda0 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/DreamHomeControlsComplicationComponent.java @@ -19,7 +19,7 @@ package com.android.systemui.dreams.complication.dagger; import static java.lang.annotation.RetentionPolicy.RUNTIME; import android.view.LayoutInflater; -import android.view.View; +import android.widget.ImageView; import com.android.systemui.R; import com.android.systemui.dreams.complication.DreamHomeControlsComplication; @@ -74,8 +74,8 @@ public interface DreamHomeControlsComplicationComponent { @Provides @DreamHomeControlsComplicationScope @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) - static View provideHomeControlsChipView(LayoutInflater layoutInflater) { - return layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip, + static ImageView provideHomeControlsChipView(LayoutInflater layoutInflater) { + return (ImageView) layoutInflater.inflate(R.layout.dream_overlay_home_controls_chip, null, false); } } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java index 616bd81abe4d..3be42cb58f11 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/dagger/RegisteredComplicationsModule.java @@ -101,8 +101,8 @@ public interface RegisteredComplicationsModule { @Named(DREAM_MEDIA_ENTRY_LAYOUT_PARAMS) static ComplicationLayoutParams provideMediaEntryLayoutParams(@Main Resources res) { return new ComplicationLayoutParams( - res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_width), - res.getDimensionPixelSize(R.dimen.keyguard_affordance_fixed_height), + ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT, ComplicationLayoutParams.POSITION_BOTTOM | ComplicationLayoutParams.POSITION_START, ComplicationLayoutParams.DIRECTION_END, diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index c0e11336b46f..b08822d97b71 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -214,6 +214,15 @@ object Flags { "lock_screen_long_press_enabled" ) + /** Enables UI updates for AI wallpapers in the wallpaper picker. */ + // TODO(b/267722622): Tracking Bug + @JvmField + val WALLPAPER_PICKER_UI_FOR_AIWP = + releasedFlag( + 229, + "wallpaper_picker_ui_for_aiwp" + ) + /** Whether to inflate the bouncer view on a background thread. */ // TODO(b/272091103): Tracking Bug @JvmField @@ -236,12 +245,21 @@ object Flags { // TODO(b/270223352): Tracking Bug @JvmField - val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = unreleasedFlag(404, "hide_smartspace_on_dream_overlay") + val HIDE_SMARTSPACE_ON_DREAM_OVERLAY = + unreleasedFlag( + 404, + "hide_smartspace_on_dream_overlay", + teamfood = true + ) // TODO(b/271460958): Tracking Bug @JvmField - val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = unreleasedFlag(405, - "show_weather_complication_on_dream_overlay") + val SHOW_WEATHER_COMPLICATION_ON_DREAM_OVERLAY = + unreleasedFlag( + 405, + "show_weather_complication_on_dream_overlay", + teamfood = true + ) // 500 - quick settings @@ -522,7 +540,7 @@ object Flags { // TODO(b/270987164): Tracking Bug @JvmField - val TRACKPAD_GESTURE_BACK = unreleasedFlag(1205, "trackpad_gesture_back", teamfood = true) + val TRACKPAD_GESTURE_FEATURES = releasedFlag(1205, "trackpad_gesture_features") // TODO(b/263826204): Tracking Bug @JvmField @@ -544,6 +562,10 @@ object Flags { val WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM = unreleasedFlag(1209, "persist.wm.debug.predictive_back_qs_dialog_anim", teamfood = true) + // TODO(b/273800936): Tracking Bug + @JvmField + val TRACKPAD_GESTURE_COMMON = releasedFlag(1210, "trackpad_gesture_common") + // 1300 - screenshots // TODO(b/254513155): Tracking Bug @JvmField @@ -661,8 +683,7 @@ object Flags { // 2600 - keyboard // TODO(b/259352579): Tracking Bug - @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = - unreleasedFlag(2600, "shortcut_list_search_layout", teamfood = true) + @JvmField val SHORTCUT_LIST_SEARCH_LAYOUT = releasedFlag(2600, "shortcut_list_search_layout") // TODO(b/259428678): Tracking Bug @JvmField diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt index eae40d61cdb6..d745a19e8549 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AlternateBouncerInteractor.kt @@ -46,6 +46,7 @@ constructor( var legacyAlternateBouncer: LegacyAlternateBouncer? = null var legacyAlternateBouncerVisibleTime: Long = NOT_VISIBLE + var receivedDownTouch = false val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible /** @@ -79,6 +80,7 @@ constructor( * @return true if the alternate bouncer was newly hidden, else false. */ fun hide(): Boolean { + receivedDownTouch = false return if (isModernAlternateBouncerEnabled) { val wasAlternateBouncerVisible = isVisibleState() bouncerRepository.setAlternateVisible(false) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index 70648276365e..8448b809445f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -414,6 +414,10 @@ constructor( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_MONOCHROMATIC_THEME, value = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEME) + ), + KeyguardPickerFlag( + name = Contract.FlagsTable.FLAG_NAME_WALLPAPER_PICKER_UI_FOR_AIWP, + value = featureFlags.isEnabled(Flags.WALLPAPER_PICKER_UI_FOR_AIWP) ) ) } diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java index 00e5aacdedae..a3bf652aba18 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java @@ -151,7 +151,7 @@ public class MediaControlPanel { private static final float REC_MEDIA_COVER_SCALE_FACTOR = 1.25f; private static final float MEDIA_SCRIM_START_ALPHA = 0.25f; private static final float MEDIA_REC_SCRIM_START_ALPHA = 0.15f; - private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 0.9f; + private static final float MEDIA_PLAYER_SCRIM_END_ALPHA = 1.0f; private static final float MEDIA_REC_SCRIM_END_ALPHA = 1.0f; private static final Intent SETTINGS_INTENT = new Intent(ACTION_MEDIA_CONTROLS_SETTINGS); diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt index b4724ddebb9a..1a85a0f47749 100644 --- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt +++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaViewController.kt @@ -89,6 +89,9 @@ constructor( R.id.turbulence_noise_view, R.id.touch_ripple_view, ) + + // Sizing view id for recommendation card view. + val recSizingViewId = R.id.sizing_view } /** A listener when the current dimensions of the player change */ @@ -176,7 +179,21 @@ constructor( // Layout dimensions are possibly changing, so we need to update them. (at // least on large screen devices) lastOrientation = newOrientation - loadLayoutForType(type) + // Update the height of media controls for the expanded layout. it is needed + // for large screen devices. + if (type == TYPE.PLAYER) { + backgroundIds.forEach { id -> + expandedLayout.getConstraint(id).layout.mHeight = + context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_height_expanded + ) + } + } else { + expandedLayout.getConstraint(recSizingViewId).layout.mHeight = + context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_height_expanded + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt index 80ed08c901af..e7bb6dcab30f 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/BackPanelController.kt @@ -53,11 +53,14 @@ private const val ENABLE_FAILSAFE = true private const val PX_PER_SEC = 1000 private const val PX_PER_MS = 1 -internal const val MIN_DURATION_ACTIVE_ANIMATION = 300L +internal const val MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION = 300L +private const val MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION = 130L private const val MIN_DURATION_CANCELLED_ANIMATION = 200L private const val MIN_DURATION_COMMITTED_ANIMATION = 120L private const val MIN_DURATION_INACTIVE_BEFORE_FLUNG_ANIMATION = 50L -private const val MIN_DURATION_CONSIDERED_AS_FLING = 100L + +private const val MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING = 100L +private const val MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING = 400L private const val FAILSAFE_DELAY_MS = 350L private const val POP_ON_FLING_DELAY = 140L @@ -145,12 +148,12 @@ class BackPanelController internal constructor( private var startY = 0f private var startIsLeft: Boolean? = null - private var gestureSinceActionDown = 0L private var gestureEntryTime = 0L + private var gestureInactiveTime = 0L private var gestureActiveTime = 0L - private val elapsedTimeSinceActionDown - get() = SystemClock.uptimeMillis() - gestureSinceActionDown + private val elapsedTimeSinceInactive + get() = SystemClock.uptimeMillis() - gestureInactiveTime private val elapsedTimeSinceEntry get() = SystemClock.uptimeMillis() - gestureEntryTime @@ -158,6 +161,9 @@ class BackPanelController internal constructor( // so that we can unambiguously start showing the ENTRY animation private var hasPassedDragSlop = false + // Distance in pixels a drag can be considered for a fling event + private var minFlingDistance = 0 + private val failsafeRunnable = Runnable { onFailsafe() } internal enum class GestureState { @@ -235,6 +241,7 @@ class BackPanelController internal constructor( private fun updateConfiguration() { params.update(resources) mView.updateArrowPaint(params.arrowThickness) + minFlingDistance = ViewConfiguration.get(context).scaledTouchSlop * 3 } private val configurationListener = object : ConfigurationController.ConfigurationListener { @@ -268,7 +275,6 @@ class BackPanelController internal constructor( velocityTracker!!.addMovement(event) when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { - gestureSinceActionDown = SystemClock.uptimeMillis() cancelAllPendingAnimations() startX = event.x startY = event.y @@ -307,8 +313,22 @@ class BackPanelController internal constructor( } } GestureState.ACTIVE -> { - if (elapsedTimeSinceEntry < MIN_DURATION_CONSIDERED_AS_FLING) { + if (previousState == GestureState.ENTRY && + elapsedTimeSinceEntry + < MIN_DURATION_ENTRY_TO_ACTIVE_CONSIDERED_AS_FLING + ) { updateArrowState(GestureState.FLUNG) + } else if (previousState == GestureState.INACTIVE && + elapsedTimeSinceInactive + < MIN_DURATION_INACTIVE_TO_ACTIVE_CONSIDERED_AS_FLING + ) { + // A delay is added to allow the background to transition back to ACTIVE + // since it was briefly in INACTIVE. Without this delay, setting it + // immediately to COMMITTED would result in the committed animation + // appearing like it was playing in INACTIVE. + mainHandler.postDelayed(MIN_DURATION_ACTIVE_AFTER_INACTIVE_ANIMATION) { + updateArrowState(GestureState.COMMITTED) + } } else { updateArrowState(GestureState.COMMITTED) } @@ -376,7 +396,7 @@ class BackPanelController internal constructor( val isPastDynamicDeactivationThreshold = totalTouchDelta <= params.deactivationSwipeTriggerThreshold val isMinDurationElapsed = - elapsedTimeSinceActionDown > MIN_DURATION_ACTIVE_ANIMATION + elapsedTimeSinceEntry > MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION if (isMinDurationElapsed && (!isWithinYActivationThreshold || isPastDynamicDeactivationThreshold) @@ -470,8 +490,15 @@ class BackPanelController internal constructor( GestureState.GONE -> 0f } + val indicator = when (currentState) { + GestureState.ENTRY -> params.entryIndicator + GestureState.INACTIVE -> params.preThresholdIndicator + GestureState.ACTIVE -> params.activeIndicator + else -> params.preThresholdIndicator + } + strokeAlphaProgress?.let { progress -> - params.arrowStrokeAlphaSpring.get(progress).takeIf { it.isNewState }?.let { + indicator.arrowDimens.alphaSpring?.get(progress)?.takeIf { it.isNewState }?.let { mView.popArrowAlpha(0f, it.value) } } @@ -537,7 +564,8 @@ class BackPanelController internal constructor( backgroundHeightStretchAmount = params.heightInterpolator .getInterpolation(progress), backgroundAlphaStretchAmount = 1f, - arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value, + arrowAlphaStretchAmount = params.entryIndicator.arrowDimens + .alphaInterpolator?.get(progress)?.value ?: 0f, edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress), farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress), fullyStretchedDimens = params.preThresholdIndicator @@ -567,7 +595,8 @@ class BackPanelController internal constructor( backgroundHeightStretchAmount = params.heightInterpolator .getInterpolation(progress), backgroundAlphaStretchAmount = 1f, - arrowAlphaStretchAmount = params.arrowStrokeAlphaInterpolator.get(progress).value, + arrowAlphaStretchAmount = params.preThresholdIndicator.arrowDimens + .alphaInterpolator?.get(progress)?.value ?: 0f, edgeCornerStretchAmount = params.edgeCornerInterpolator.getInterpolation(progress), farCornerStretchAmount = params.farCornerInterpolator.getInterpolation(progress), fullyStretchedDimens = params.preThresholdIndicator @@ -599,19 +628,15 @@ class BackPanelController internal constructor( windowManager.addView(mView, layoutParams) } - private fun isDragAwayFromEdge(velocityPxPerSecThreshold: Int = 0) = velocityTracker!!.run { - computeCurrentVelocity(PX_PER_SEC) - val velocity = xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1) - velocity > velocityPxPerSecThreshold - } - private fun isFlungAwayFromEdge(endX: Float, startX: Float = touchDeltaStartX): Boolean { - val minDistanceConsideredForFling = ViewConfiguration.get(context).scaledTouchSlop val flingDistance = if (mView.isLeftPanel) endX - startX else startX - endX - val isPastFlingVelocity = isDragAwayFromEdge( - velocityPxPerSecThreshold = - ViewConfiguration.get(context).scaledMinimumFlingVelocity) - return flingDistance > minDistanceConsideredForFling && isPastFlingVelocity + val flingVelocity = velocityTracker?.run { + computeCurrentVelocity(PX_PER_SEC) + xVelocity.takeIf { mView.isLeftPanel } ?: (xVelocity * -1) + } ?: 0f + val isPastFlingVelocityThreshold = + flingVelocity > ViewConfiguration.get(context).scaledMinimumFlingVelocity + return flingDistance > minFlingDistance && isPastFlingVelocityThreshold } private fun playHorizontalAnimationThen(onEnd: DelayedOnAnimationEndListener) { @@ -664,7 +689,6 @@ class BackPanelController internal constructor( mView.setSpring( arrowLength = params.entryIndicator.arrowDimens.lengthSpring, arrowHeight = params.entryIndicator.arrowDimens.heightSpring, - arrowAlpha = params.entryIndicator.arrowDimens.alphaSpring, scale = params.entryIndicator.scaleSpring, verticalTranslation = params.entryIndicator.verticalTranslationSpring, horizontalTranslation = params.entryIndicator.horizontalTranslationSpring, @@ -725,6 +749,7 @@ class BackPanelController internal constructor( arrowLength = params.committedIndicator.arrowDimens.lengthSpring, arrowHeight = params.committedIndicator.arrowDimens.heightSpring, scale = params.committedIndicator.scaleSpring, + backgroundAlpha = params.committedIndicator.backgroundDimens.alphaSpring, backgroundWidth = params.committedIndicator.backgroundDimens.widthSpring, backgroundHeight = params.committedIndicator.backgroundDimens.heightSpring, backgroundEdgeCornerRadius = params.committedIndicator.backgroundDimens @@ -733,6 +758,10 @@ class BackPanelController internal constructor( .farCornerRadiusSpring, ) } + GestureState.CANCELLED -> { + mView.setSpring( + backgroundAlpha = params.cancelledIndicator.backgroundDimens.alphaSpring) + } else -> {} } @@ -864,6 +893,7 @@ class BackPanelController internal constructor( } GestureState.INACTIVE -> { + gestureInactiveTime = SystemClock.uptimeMillis() // Typically entering INACTIVE means // totalTouchDelta <= deactivationSwipeTriggerThreshold @@ -900,9 +930,9 @@ class BackPanelController internal constructor( val delay = max(0, MIN_DURATION_CANCELLED_ANIMATION - elapsedTimeSinceEntry) playWithBackgroundWidthAnimation(onEndSetGoneStateListener, delay) - params.arrowStrokeAlphaSpring.get(0f).takeIf { it.isNewState }?.let { - mView.popArrowAlpha(0f, it.value) - } + val springForceOnCancelled = params.cancelledIndicator + .arrowDimens.alphaSpring?.get(0f)?.value + mView.popArrowAlpha(0f, springForceOnCancelled) mainHandler.postDelayed(10L) { vibratorHelper.cancel() } } } diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java index b454c23ef07f..cfcc6713eca9 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java @@ -67,7 +67,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.model.SysUiState; -import com.android.systemui.navigationbar.NavigationBarView; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.plugins.FalsingManager; import com.android.systemui.plugins.NavigationEdgeBackPlugin; @@ -591,7 +590,8 @@ public class EdgeBackGestureHandler implements PluginListener<NavigationEdgeBack // Add a nav bar panel window mIsNewBackAffordanceEnabled = mFeatureFlags.isEnabled(Flags.NEW_BACK_AFFORDANCE); - mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK); + mIsTrackpadGestureBackEnabled = mFeatureFlags.isEnabled( + Flags.TRACKPAD_GESTURE_FEATURES); resetEdgeBackPlugin(); mPluginManager.addPluginListener( this, NavigationEdgeBackPlugin.class, /*allowMultiple=*/ false); diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt index d46333a50c8d..3dc6d2f8464d 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgePanelParams.kt @@ -12,9 +12,10 @@ data class EdgePanelParams(private var resources: Resources) { val length: Float? = 0f, val height: Float? = 0f, val alpha: Float = 0f, - var alphaSpring: SpringForce? = null, val heightSpring: SpringForce? = null, val lengthSpring: SpringForce? = null, + var alphaSpring: Step<SpringForce>? = null, + var alphaInterpolator: Step<Float>? = null ) data class BackgroundDimens( @@ -61,11 +62,6 @@ data class EdgePanelParams(private var resources: Resources) { private set var arrowThickness: Float = 0f private set - lateinit var arrowStrokeAlphaSpring: Step<SpringForce> - private set - lateinit var arrowStrokeAlphaInterpolator: Step<Float> - private set - // The closest to y var minArrowYPosition: Int = 0 private set @@ -81,13 +77,6 @@ data class EdgePanelParams(private var resources: Resources) { var swipeProgressThreshold: Float = 0f private set - // The minimum delta needed to change direction / stop triggering back - var minDeltaForSwitch: Int = 0 - private set - - var minDragToStartAnimation: Float = 0f - private set - lateinit var entryWidthInterpolator: PathInterpolator private set lateinit var entryWidthTowardsEdgeInterpolator: PathInterpolator @@ -133,23 +122,17 @@ data class EdgePanelParams(private var resources: Resources) { deactivationSwipeTriggerThreshold = getDimen(R.dimen.navigation_edge_action_deactivation_drag_threshold) swipeProgressThreshold = getDimen(R.dimen.navigation_edge_action_progress_threshold) - minDeltaForSwitch = getPx(R.dimen.navigation_edge_minimum_x_delta_for_switch) - minDragToStartAnimation = - getDimen(R.dimen.navigation_edge_action_min_distance_to_start_animation) entryWidthInterpolator = PathInterpolator(.19f, 1.27f, .71f, .86f) entryWidthTowardsEdgeInterpolator = PathInterpolator(1f, -3f, 1f, 1.2f) - activeWidthInterpolator = PathInterpolator(.32f, 0f, .16f, .94f) + activeWidthInterpolator = PathInterpolator(.7f, .06f, .34f, .97f) arrowAngleInterpolator = entryWidthInterpolator translationInterpolator = PathInterpolator(0.2f, 1.0f, 1.0f, 1.0f) farCornerInterpolator = PathInterpolator(.03f, .19f, .14f, 1.09f) edgeCornerInterpolator = PathInterpolator(0f, 1.11f, .85f, .84f) heightInterpolator = PathInterpolator(1f, .05f, .9f, -0.29f) - val showArrowOnProgressValue = .23f - val showArrowOnProgressValueFactor = 1.05f - - val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.8f) + val entryActiveHorizontalTranslationSpring = createSpring(800f, 0.76f) val activeCommittedArrowLengthSpring = createSpring(1500f, 0.29f) val activeCommittedArrowHeightSpring = createSpring(1500f, 0.29f) val flungCommittedEdgeCornerSpring = createSpring(10000f, 1f) @@ -157,6 +140,8 @@ data class EdgePanelParams(private var resources: Resources) { val flungCommittedWidthSpring = createSpring(10000f, 1f) val flungCommittedHeightSpring = createSpring(10000f, 1f) + val entryIndicatorAlphaThreshold = .23f + val entryIndicatorAlphaFactor = 1.05f entryIndicator = BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_entry_margin), scale = getDimenFloat(R.dimen.navigation_edge_entry_scale), @@ -168,9 +153,20 @@ data class EdgePanelParams(private var resources: Resources) { length = getDimen(R.dimen.navigation_edge_entry_arrow_length), height = getDimen(R.dimen.navigation_edge_entry_arrow_height), alpha = 0f, - alphaSpring = createSpring(200f, 1f), lengthSpring = createSpring(600f, 0.4f), heightSpring = createSpring(600f, 0.4f), + alphaSpring = Step( + threshold = entryIndicatorAlphaThreshold, + factor = entryIndicatorAlphaFactor, + postThreshold = createSpring(200f, 1f), + preThreshold = createSpring(2000f, 0.6f) + ), + alphaInterpolator = Step( + threshold = entryIndicatorAlphaThreshold, + factor = entryIndicatorAlphaFactor, + postThreshold = 1f, + preThreshold = 0f + ) ), backgroundDimens = BackgroundDimens( alpha = 1f, @@ -186,6 +182,20 @@ data class EdgePanelParams(private var resources: Resources) { ) ) + val preThresholdAndActiveIndicatorAlphaThreshold = .355f + val preThresholdAndActiveIndicatorAlphaFactor = 1.05f + val preThresholdAndActiveAlphaSpring = Step( + threshold = preThresholdAndActiveIndicatorAlphaThreshold, + factor = preThresholdAndActiveIndicatorAlphaFactor, + postThreshold = createSpring(180f, 0.9f), + preThreshold = createSpring(2000f, 0.6f) + ) + val preThresholdAndActiveAlphaSpringInterpolator = Step( + threshold = preThresholdAndActiveIndicatorAlphaThreshold, + factor = preThresholdAndActiveIndicatorAlphaFactor, + postThreshold = 1f, + preThreshold = 0f + ) activeIndicator = BackIndicatorDimens( horizontalTranslation = getDimen(R.dimen.navigation_edge_active_margin), scale = getDimenFloat(R.dimen.navigation_edge_active_scale), @@ -197,6 +207,8 @@ data class EdgePanelParams(private var resources: Resources) { alpha = 1f, lengthSpring = activeCommittedArrowLengthSpring, heightSpring = activeCommittedArrowHeightSpring, + alphaSpring = preThresholdAndActiveAlphaSpring, + alphaInterpolator = preThresholdAndActiveAlphaSpringInterpolator ), backgroundDimens = BackgroundDimens( alpha = 1f, @@ -204,7 +216,7 @@ data class EdgePanelParams(private var resources: Resources) { height = getDimen(R.dimen.navigation_edge_active_background_height), edgeCornerRadius = getDimen(R.dimen.navigation_edge_active_edge_corners), farCornerRadius = getDimen(R.dimen.navigation_edge_active_far_corners), - widthSpring = createSpring(375f, 0.675f), + widthSpring = createSpring(650f, 0.75f), heightSpring = createSpring(10000f, 1f), edgeCornerRadiusSpring = createSpring(600f, 0.36f), farCornerRadiusSpring = createSpring(2500f, 0.855f), @@ -223,6 +235,8 @@ data class EdgePanelParams(private var resources: Resources) { alpha = 1f, lengthSpring = createSpring(100f, 0.6f), heightSpring = createSpring(100f, 0.6f), + alphaSpring = preThresholdAndActiveAlphaSpring, + alphaInterpolator = preThresholdAndActiveAlphaSpringInterpolator ), backgroundDimens = BackgroundDimens( alpha = 1f, @@ -255,6 +269,7 @@ data class EdgePanelParams(private var resources: Resources) { heightSpring = flungCommittedHeightSpring, edgeCornerRadiusSpring = flungCommittedEdgeCornerSpring, farCornerRadiusSpring = flungCommittedFarCornerSpring, + alphaSpring = createSpring(1100f, 1f), ), scale = 0.85f, scaleSpring = createSpring(1150f, 1f), @@ -276,7 +291,11 @@ data class EdgePanelParams(private var resources: Resources) { ) cancelledIndicator = entryIndicator.copy( - backgroundDimens = entryIndicator.backgroundDimens.copy(width = 0f) + backgroundDimens = entryIndicator.backgroundDimens.copy( + width = 0f, + alpha = 0f, + alphaSpring = createSpring(450f, 1f) + ) ) fullyStretchedIndicator = BackIndicatorDimens( @@ -306,22 +325,6 @@ data class EdgePanelParams(private var resources: Resources) { farCornerRadiusSpring = null, ) ) - - arrowStrokeAlphaInterpolator = Step( - threshold = showArrowOnProgressValue, - factor = showArrowOnProgressValueFactor, - postThreshold = 1f, - preThreshold = 0f - ) - - entryIndicator.arrowDimens.alphaSpring?.let { alphaSpring -> - arrowStrokeAlphaSpring = Step( - threshold = showArrowOnProgressValue, - factor = showArrowOnProgressValueFactor, - postThreshold = alphaSpring, - preThreshold = SpringForce().setStiffness(2000f).setDampingRatio(1f) - ) - } } } diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 779f1d8011e3..e74d78d2b5d9 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -27,6 +27,7 @@ import android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT import android.content.Intent.FLAG_ACTIVITY_NEW_TASK import android.content.pm.PackageManager import android.os.Build +import android.os.UserHandle import android.os.UserManager import android.util.Log import androidx.annotation.VisibleForTesting @@ -100,6 +101,14 @@ constructor( fun showNoteTask( entryPoint: NoteTaskEntryPoint, ) { + showNoteTaskAsUser(entryPoint, userTracker.userHandle) + } + + /** A variant of [showNoteTask] which launches note task in the given [user]. */ + fun showNoteTaskAsUser( + entryPoint: NoteTaskEntryPoint, + user: UserHandle, + ) { if (!isEnabled) return val bubbles = optionalBubbles.getOrNull() ?: return @@ -113,7 +122,7 @@ constructor( // note task when the screen is locked. if ( isKeyguardLocked && - devicePolicyManager.areKeyguardShortcutsDisabled(userId = userTracker.userId) + devicePolicyManager.areKeyguardShortcutsDisabled(userId = user.identifier) ) { logDebug { "Enterprise policy disallows launching note app when the screen is locked." } return @@ -126,7 +135,7 @@ constructor( // TODO(b/266686199): We should handle when app not available. For now, we log. val intent = createNoteIntent(info) try { - logDebug { "onShowNoteTask - start: $info" } + logDebug { "onShowNoteTask - start: $info on user#${user.identifier}" } when (info.launchMode) { is NoteTaskLaunchMode.AppBubble -> { bubbles.showOrHideAppBubble(intent, userTracker.userHandle) @@ -134,7 +143,7 @@ constructor( logDebug { "onShowNoteTask - opened as app bubble: $info" } } is NoteTaskLaunchMode.Activity -> { - context.startActivityAsUser(intent, userTracker.userHandle) + context.startActivityAsUser(intent, user) eventLogger.logNoteTaskOpened(info) logDebug { "onShowNoteTask - opened as activity: $info" } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java index a71e6ddb6abd..9ece72d2ca7f 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSHost.java @@ -26,6 +26,7 @@ import com.android.systemui.R; import com.android.systemui.plugins.qs.QSFactory; import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.qs.QSTileView; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.util.leak.GarbageMonitor; import java.util.ArrayList; @@ -33,7 +34,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; -public interface QSHost { +public interface QSHost extends PanelInteractor { String TILES_SETTING = Settings.Secure.QS_TILES; int POSITION_AT_END = -1; @@ -57,9 +58,6 @@ public interface QSHost { } void warn(String message, Throwable t); - void collapsePanels(); - void forceCollapsePanels(); - void openPanels(); Context getContext(); Context getUserContext(); int getUserId(); diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt new file mode 100644 index 000000000000..958fa71b1fd8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2023 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.systemui.qs.dagger + +import com.android.systemui.flags.FeatureFlags +import com.android.systemui.flags.Flags +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QSTileHost +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl +import dagger.Binds +import dagger.Module +import dagger.Provides + +@Module +interface QSHostModule { + + @Binds fun provideQsHost(controllerImpl: QSTileHost): QSHost + + @Module + companion object { + @Provides + @JvmStatic + fun providePanelInteractor( + featureFlags: FeatureFlags, + qsHost: QSHost, + panelInteractorImpl: PanelInteractorImpl + ): PanelInteractor { + return if (featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_HOST)) { + panelInteractorImpl + } else { + qsHost + } + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java index 431d6e847207..cfe93132c044 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java +++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java @@ -27,7 +27,6 @@ import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.media.dagger.MediaModule; import com.android.systemui.qs.AutoAddTracker; import com.android.systemui.qs.QSHost; -import com.android.systemui.qs.QSTileHost; import com.android.systemui.qs.ReduceBrightColorsController; import com.android.systemui.qs.external.QSExternalModule; import com.android.systemui.qs.tileimpl.QSTileImpl; @@ -45,7 +44,6 @@ import java.util.Map; import javax.inject.Named; -import dagger.Binds; import dagger.Module; import dagger.Provides; import dagger.multibindings.Multibinds; @@ -54,7 +52,13 @@ import dagger.multibindings.Multibinds; * Module for QS dependencies */ @Module(subcomponents = {QSFragmentComponent.class}, - includes = {MediaModule.class, QSExternalModule.class, QSFlagsModule.class}) + includes = { + MediaModule.class, + QSExternalModule.class, + QSFlagsModule.class, + QSHostModule.class + } +) public interface QSModule { /** A map of internal QS tiles. Ensures that this can be injected even if @@ -100,8 +104,4 @@ public interface QSModule { manager.init(); return manager; } - - /** */ - @Binds - QSHost provideQsHost(QSTileHost controllerImpl); } diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java index adc71657e680..2083cc7b167e 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java +++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileServices.java @@ -40,6 +40,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -74,6 +75,7 @@ public class TileServices extends IQSService.Stub { private final CommandQueue mCommandQueue; private final UserTracker mUserTracker; private final StatusBarIconController mStatusBarIconController; + private final PanelInteractor mPanelInteractor; private int mMaxBound = DEFAULT_MAX_BOUND; @@ -85,7 +87,8 @@ public class TileServices extends IQSService.Stub { UserTracker userTracker, KeyguardStateController keyguardStateController, CommandQueue commandQueue, - StatusBarIconController statusBarIconController) { + StatusBarIconController statusBarIconController, + PanelInteractor panelInteractor) { mHost = host; mKeyguardStateController = keyguardStateController; mContext = mHost.getContext(); @@ -96,6 +99,7 @@ public class TileServices extends IQSService.Stub { mCommandQueue = commandQueue; mStatusBarIconController = statusBarIconController; mCommandQueue.addCallback(mRequestListeningCallback); + mPanelInteractor = panelInteractor; } public Context getContext() { @@ -255,7 +259,7 @@ public class TileServices extends IQSService.Stub { if (customTile != null) { verifyCaller(customTile); customTile.onDialogShown(); - mHost.forceCollapsePanels(); + mPanelInteractor.forceCollapsePanels(); Objects.requireNonNull(mServices.get(customTile)).setShowingDialog(true); } } @@ -275,7 +279,7 @@ public class TileServices extends IQSService.Stub { CustomTile customTile = getTileForToken(token); if (customTile != null) { verifyCaller(customTile); - mHost.forceCollapsePanels(); + mPanelInteractor.forceCollapsePanels(); } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt new file mode 100644 index 000000000000..260caa767a5e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractor.kt @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 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.systemui.qs.pipeline.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.phone.CentralSurfaces +import java.util.Optional +import javax.inject.Inject + +/** Encapsulates business logic for interacting with the QS panel. */ +interface PanelInteractor { + + /** Collapse the shade */ + fun collapsePanels() + + /** Collapse the shade forcefully, skipping some animations. */ + fun forceCollapsePanels() + + /** Open the Quick Settings panel */ + fun openPanels() +} + +@SysUISingleton +class PanelInteractorImpl +@Inject +constructor( + private val centralSurfaces: Optional<CentralSurfaces>, +) : PanelInteractor { + override fun collapsePanels() { + centralSurfaces.ifPresent { it.postAnimateCollapsePanels() } + } + + override fun forceCollapsePanels() { + centralSurfaces.ifPresent { it.postAnimateForceCollapsePanels() } + } + + override fun openPanels() { + centralSurfaces.ifPresent { it.postAnimateOpenPanels() } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt index a915ddba4f1c..3f514344cee1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/FontScalingTile.kt @@ -17,6 +17,7 @@ package com.android.systemui.qs.tiles import android.content.Intent import android.os.Handler +import android.os.HandlerExecutor import android.os.Looper import android.provider.Settings import android.view.View @@ -38,6 +39,7 @@ import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SystemSettings import javax.inject.Inject @@ -54,6 +56,7 @@ constructor( qsLogger: QSLogger, private val dialogLaunchAnimator: DialogLaunchAnimator, private val systemSettings: SystemSettings, + private val secureSettings: SecureSettings, private val featureFlags: FeatureFlags ) : QSTileImpl<QSTile.State?>( @@ -78,7 +81,13 @@ constructor( override fun handleClick(view: View?) { mUiHandler.post { - val dialog: SystemUIDialog = FontScalingDialog(mContext, systemSettings) + val dialog: SystemUIDialog = + FontScalingDialog( + mContext, + systemSettings, + secureSettings, + HandlerExecutor(mHandler) + ) if (view != null) { dialogLaunchAnimator.showFromView( dialog, diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java index 89d402a3af5a..27f58269722a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/LocationTile.java @@ -38,6 +38,7 @@ import com.android.systemui.plugins.qs.QSTile.BooleanState; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.statusbar.policy.KeyguardStateController; import com.android.systemui.statusbar.policy.LocationController; @@ -52,6 +53,7 @@ public class LocationTile extends QSTileImpl<BooleanState> { private final LocationController mController; private final KeyguardStateController mKeyguard; + private final PanelInteractor mPanelInteractor; private final Callback mCallback = new Callback(); @Inject @@ -65,12 +67,14 @@ public class LocationTile extends QSTileImpl<BooleanState> { ActivityStarter activityStarter, QSLogger qsLogger, LocationController locationController, - KeyguardStateController keyguardStateController + KeyguardStateController keyguardStateController, + PanelInteractor panelInteractor ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); mController = locationController; mKeyguard = keyguardStateController; + mPanelInteractor = panelInteractor; mController.observe(this, mCallback); mKeyguard.observe(this, mCallback); } @@ -90,7 +94,7 @@ public class LocationTile extends QSTileImpl<BooleanState> { if (mKeyguard.isMethodSecure() && mKeyguard.isShowing()) { mActivityStarter.postQSRunnableDismissingKeyguard(() -> { final boolean wasEnabled = mState.value; - mHost.openPanels(); + mPanelInteractor.openPanels(); mController.setLocationEnabled(!wasEnabled); }); return; diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java index 07b50c9a66f6..65592a717565 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ScreenRecordTile.java @@ -42,6 +42,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; @@ -66,6 +67,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> private final Callback mCallback = new Callback(); private final DialogLaunchAnimator mDialogLaunchAnimator; private final FeatureFlags mFlags; + private final PanelInteractor mPanelInteractor; private long mMillisUntilFinished = 0; @@ -83,7 +85,8 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> RecordingController controller, KeyguardDismissUtil keyguardDismissUtil, KeyguardStateController keyguardStateController, - DialogLaunchAnimator dialogLaunchAnimator + DialogLaunchAnimator dialogLaunchAnimator, + PanelInteractor panelInteractor ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); @@ -93,6 +96,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> mKeyguardDismissUtil = keyguardDismissUtil; mKeyguardStateController = keyguardStateController; mDialogLaunchAnimator = dialogLaunchAnimator; + mPanelInteractor = panelInteractor; } @Override @@ -171,7 +175,7 @@ public class ScreenRecordTile extends QSTileImpl<QSTile.BooleanState> // disable the exit animation which looks weird when it happens at the same time as the // shade collapsing. mDialogLaunchAnimator.disableAllCurrentDialogsExitAnimations(); - getHost().collapsePanels(); + mPanelInteractor.collapsePanels(); }; final Dialog dialog = mController.createScreenRecordDialog(mContext, mFlags, diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 30865aa0c45d..1c3e0112967e 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -849,7 +849,7 @@ public final class NotificationPanelViewController implements Dumpable { mLayoutInflater = layoutInflater; mFeatureFlags = featureFlags; mAnimateBack = mFeatureFlags.isEnabled(Flags.WM_SHADE_ANIMATE_BACK_GESTURE); - mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_BACK); + mTrackpadGestureBack = mFeatureFlags.isEnabled(Flags.TRACKPAD_GESTURE_FEATURES); mFalsingCollector = falsingCollector; mPowerManager = powerManager; mWakeUpCoordinator = coordinator; @@ -2805,6 +2805,7 @@ public final class NotificationPanelViewController implements Dumpable { public void setIsLaunchAnimationRunning(boolean running) { boolean wasRunning = mIsLaunchAnimationRunning; mIsLaunchAnimationRunning = running; + mCentralSurfaces.updateIsKeyguard(); if (wasRunning != mIsLaunchAnimationRunning) { mShadeExpansionStateManager.notifyLaunchingActivityChanged(running); } @@ -3862,6 +3863,10 @@ public final class NotificationPanelViewController implements Dumpable { return mClosing || mIsLaunchAnimationRunning; } + public boolean isLaunchAnimationRunning() { + return mIsLaunchAnimationRunning; + } + public boolean isTracking() { return mTracking; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index 156e4fd1889f..e7759df6e81b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -301,9 +301,11 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW } private void applyKeyguardFlags(NotificationShadeWindowState state) { - final boolean keyguardOrAod = state.keyguardShowing + // Keyguard is visible if it's showing or if it's fading away (in which case we're animating + // it out, but the wallpaper should remain visible as a backdrop for the animation); + final boolean keyguardOrAodVisible = (state.keyguardShowing || state.keyguardFadingAway) || (state.dozing && mDozeParameters.getAlwaysOn()); - if ((keyguardOrAod && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque) + if ((keyguardOrAodVisible && !state.mediaBackdropShowing && !state.lightRevealScrimOpaque) || mKeyguardViewMediator.isAnimatingBetweenKeyguardAndSurfaceBehind()) { // Show the wallpaper if we're on keyguard/AOD and the wallpaper is not occluded by a // solid backdrop. Also, show it if we are currently animating between the diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index a716a6ea55fb..5f6f158277d7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -16,7 +16,7 @@ package com.android.systemui.shade; -import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_BACK; +import static com.android.systemui.flags.Flags.TRACKPAD_GESTURE_COMMON; import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; import android.app.StatusBarManager; @@ -37,14 +37,12 @@ import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.LockIconViewController; import com.android.keyguard.dagger.KeyguardBouncerComponent; import com.android.systemui.R; -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.compose.ComposeFacade; import com.android.systemui.dock.DockManager; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; import com.android.systemui.keyguard.KeyguardUnlockAnimationController; -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.TransitionState; import com.android.systemui.keyguard.shared.model.TransitionStep; @@ -94,9 +92,7 @@ public class NotificationShadeWindowViewController { private final AmbientState mAmbientState; private final PulsingGestureListener mPulsingGestureListener; private final NotificationInsetsController mNotificationInsetsController; - private final AlternateBouncerInteractor mAlternateBouncerInteractor; - private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; - private final boolean mIsTrackpadGestureBackEnabled; + private final boolean mIsTrackpadCommonEnabled; private GestureDetector mPulsingWakeupGestureHandler; private View mBrightnessMirror; private boolean mTouchActive; @@ -145,8 +141,6 @@ public class NotificationShadeWindowViewController { PulsingGestureListener pulsingGestureListener, KeyguardBouncerViewModel keyguardBouncerViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory, - AlternateBouncerInteractor alternateBouncerInteractor, - UdfpsOverlayInteractor udfpsOverlayInteractor, KeyguardTransitionInteractor keyguardTransitionInteractor, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, FeatureFlags featureFlags, @@ -170,9 +164,7 @@ public class NotificationShadeWindowViewController { mAmbientState = ambientState; mPulsingGestureListener = pulsingGestureListener; mNotificationInsetsController = notificationInsetsController; - mAlternateBouncerInteractor = alternateBouncerInteractor; - mUdfpsOverlayInteractor = udfpsOverlayInteractor; - mIsTrackpadGestureBackEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_BACK); + mIsTrackpadCommonEnabled = featureFlags.isEnabled(TRACKPAD_GESTURE_COMMON); // This view is not part of the newly inflated expanded status bar. mBrightnessMirror = mView.findViewById(R.id.brightness_mirror_container); @@ -269,6 +261,9 @@ public class NotificationShadeWindowViewController { mFalsingCollector.onTouchEvent(ev); mPulsingWakeupGestureHandler.onTouchEvent(ev); + if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) { + return true; + } if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { // Disallow new pointers while the brightness mirror is visible. This is so that @@ -343,9 +338,10 @@ public class NotificationShadeWindowViewController { return true; } - if (mAlternateBouncerInteractor.isVisibleState()) { - // If using UDFPS, don't intercept touches that are within its overlay bounds - return mUdfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(ev); + if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) { + // Don't allow touches to proceed to underlying views if alternate + // bouncer is showing + return true; } if (mLockIconViewController.onInterceptTouchEvent(ev)) { @@ -381,10 +377,8 @@ public class NotificationShadeWindowViewController { handled = !mService.isPulsing(); } - if (mAlternateBouncerInteractor.isVisibleState()) { - // eat the touch - mStatusBarKeyguardViewManager.onTouch(ev); - handled = true; + if (mStatusBarKeyguardViewManager.onTouch(ev)) { + return true; } if ((mDragDownHelper.isDragDownEnabled() && !handled) @@ -474,7 +468,7 @@ public class NotificationShadeWindowViewController { if (mTouchActive) { final long now = mClock.uptimeMillis(); final MotionEvent event; - if (mIsTrackpadGestureBackEnabled) { + if (mIsTrackpadCommonEnabled) { event = MotionEvent.obtain(mDownEvent); event.setDownTime(now); event.setAction(MotionEvent.ACTION_CANCEL); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt index 82b1268e4797..02bf3b3be537 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/GroupCountCoordinator.kt @@ -1,3 +1,19 @@ +/* + * Copyright 2023 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.systemui.statusbar.notification.collection.coordinator import android.util.ArrayMap diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 33cbf0699bca..a529da54fc4e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -68,6 +68,7 @@ import androidx.annotation.Nullable; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.ContrastColorUtil; import com.android.internal.widget.CachingIconView; import com.android.internal.widget.CallLayout; @@ -1671,7 +1672,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView MetricsLogger metricsLogger, SmartReplyConstants smartReplyConstants, SmartReplyController smartReplyController, - FeatureFlags featureFlags) { + FeatureFlags featureFlags, + IStatusBarService statusBarService) { mEntry = entry; mAppName = appName; if (mMenuRow == null) { @@ -1699,7 +1701,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView mPeopleNotificationIdentifier, rivSubcomponentFactory, smartReplyConstants, - smartReplyController); + smartReplyController, + statusBarService); } mOnUserInteractionCallback = onUserInteractionCallback; mBubblesManagerOptional = bubblesManagerOptional; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index e2a3111fc08f..5ca0866209a5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -29,6 +29,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.flags.FeatureFlags; import com.android.systemui.flags.Flags; @@ -100,6 +101,7 @@ public class ExpandableNotificationRowController implements NotifViewController private final SmartReplyController mSmartReplyController; private final ExpandableNotificationRowDragController mDragController; private final NotificationDismissibilityProvider mDismissibilityProvider; + private final IStatusBarService mStatusBarService; private final ExpandableNotificationRow.ExpandableNotificationRowLogger mLoggerCallback = new ExpandableNotificationRow.ExpandableNotificationRowLogger() { @Override @@ -157,7 +159,8 @@ public class ExpandableNotificationRowController implements NotifViewController PeopleNotificationIdentifier peopleNotificationIdentifier, Optional<BubblesManager> bubblesManagerOptional, ExpandableNotificationRowDragController dragController, - NotificationDismissibilityProvider dismissibilityProvider) { + NotificationDismissibilityProvider dismissibilityProvider, + IStatusBarService statusBarService) { mView = view; mListContainer = listContainer; mRemoteInputViewSubcomponentFactory = rivSubcomponentFactory; @@ -189,6 +192,7 @@ public class ExpandableNotificationRowController implements NotifViewController mSmartReplyConstants = smartReplyConstants; mSmartReplyController = smartReplyController; mDismissibilityProvider = dismissibilityProvider; + mStatusBarService = statusBarService; } /** @@ -220,7 +224,8 @@ public class ExpandableNotificationRowController implements NotifViewController mMetricsLogger, mSmartReplyConstants, mSmartReplyController, - mFeatureFlags + mFeatureFlags, + mStatusBarService ); mView.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); if (mAllowLongPress) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java index 5834dcbf8645..78392f78428f 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentView.java @@ -21,10 +21,13 @@ import android.annotation.Nullable; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; +import android.os.RemoteException; import android.provider.Settings; +import android.service.notification.StatusBarNotification; import android.util.ArrayMap; import android.util.AttributeSet; import android.util.IndentingPrintWriter; @@ -39,6 +42,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.R; import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin; import com.android.systemui.statusbar.RemoteInputController; @@ -129,6 +133,7 @@ public class NotificationContentView extends FrameLayout implements Notification private Runnable mExpandedVisibleListener; private PeopleNotificationIdentifier mPeopleIdentifier; private RemoteInputViewSubcomponent.Factory mRemoteInputSubcomponentFactory; + private IStatusBarService mStatusBarService; /** * List of listeners for when content views become inactive (i.e. not the showing view). @@ -196,11 +201,13 @@ public class NotificationContentView extends FrameLayout implements Notification PeopleNotificationIdentifier peopleNotificationIdentifier, RemoteInputViewSubcomponent.Factory rivSubcomponentFactory, SmartReplyConstants smartReplyConstants, - SmartReplyController smartReplyController) { + SmartReplyController smartReplyController, + IStatusBarService statusBarService) { mPeopleIdentifier = peopleNotificationIdentifier; mRemoteInputSubcomponentFactory = rivSubcomponentFactory; mSmartReplyConstants = smartReplyConstants; mSmartReplyController = smartReplyController; + mStatusBarService = statusBarService; } public void reinflate() { @@ -2193,4 +2200,36 @@ public class NotificationContentView extends FrameLayout implements Notification protected void setHeadsUpWrapper(NotificationViewWrapper headsUpWrapper) { mHeadsUpWrapper = headsUpWrapper; } + + @Override + protected void dispatchDraw(Canvas canvas) { + try { + super.dispatchDraw(canvas); + } catch (Exception e) { + // Catch draw exceptions that may be caused by RemoteViews + Log.e(TAG, "Drawing view failed: " + e); + cancelNotification(e); + } + } + + private void cancelNotification(Exception exception) { + try { + setVisibility(GONE); + final StatusBarNotification sbn = mNotificationEntry.getSbn(); + if (mStatusBarService != null) { + // report notification inflation errors back up + // to notification delegates + mStatusBarService.onNotificationError( + sbn.getPackageName(), + sbn.getTag(), + sbn.getId(), + sbn.getUid(), + sbn.getInitialPid(), + exception.getMessage(), + sbn.getUser().getIdentifier()); + } + } catch (RemoteException ex) { + Log.e(TAG, "cancelNotification failed: " + ex); + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index e8058b85d376..769edf74f838 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -831,18 +831,16 @@ public class NotificationStackScrollLayoutController { private boolean isInVisibleLocation(NotificationEntry entry) { ExpandableNotificationRow row = entry.getRow(); - ExpandableViewState childViewState = row.getViewState(); - - if (childViewState == null) { + if (row == null) { return false; } + + ExpandableViewState childViewState = row.getViewState(); if ((childViewState.location & ExpandableViewState.VISIBLE_LOCATIONS) == 0) { return false; } - if (row.getVisibility() != View.VISIBLE) { - return false; - } - return true; + + return row.getVisibility() == View.VISIBLE; } public boolean isViewAffectedBySwipe(ExpandableView expandableView) { 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 5438a59fadc2..5e3c1c59666d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -1052,6 +1052,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { // 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()) { + updateIsKeyguard(); + if (mLightRevealScrim.getRevealAmount() != 1f) { Log.e(TAG, "Keyguard is done going away, but someone left the light reveal " + "scrim at reveal amount: " + mLightRevealScrim.getRevealAmount()); @@ -2943,6 +2945,10 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { showKeyguardImpl(); } } else { + final boolean isLaunchingOrGoingAway = + mNotificationPanelViewController.isLaunchAnimationRunning() + || mKeyguardStateController.isKeyguardGoingAway(); + // During folding a foldable device this might be called as a result of // 'onScreenTurnedOff' call for the inner display. // In this case: @@ -2954,7 +2960,14 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { if (!mScreenOffAnimationController.isKeyguardHideDelayed() // If we're animating occluded, there's an activity launching over the keyguard // UI. Wait to hide it until after the animation concludes. - && !mKeyguardViewMediator.isOccludeAnimationPlaying()) { + && !mKeyguardViewMediator.isOccludeAnimationPlaying() + // If we're occluded, but playing an animation (launch or going away animations) + // the keyguard is visible behind the animation. + && !(mKeyguardStateController.isOccluded() && isLaunchingOrGoingAway)) { + // If we're going away and occluded, it means we are launching over the + // unsecured keyguard, which will subsequently go away. Wait to hide it until + // after the animation concludes to avoid the lockscreen UI changing into the + // shade UI behind the launch animation. return hideKeyguardImpl(forceStateChange); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 0bded7327ea6..46603df955d7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -809,6 +809,11 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) { mOccludeAnimationPlaying = occludeAnimationPlaying; + + for (ScrimState state : ScrimState.values()) { + state.setOccludeAnimationPlaying(occludeAnimationPlaying); + } + applyAndDispatchState(); } @@ -853,7 +858,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump if (mState == ScrimState.UNLOCKED || mState == ScrimState.DREAMING) { final boolean occluding = mOccludeAnimationPlaying || mState.mLaunchingAffordanceWithPreview; - // Darken scrim as it's pulled down while unlocked. If we're unlocked but playing the // screen off/occlusion animations, ignore expansion changes while those animations // play. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index 0e9d3ce33d5b..7b2028310a84 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -243,7 +243,12 @@ public enum ScrimState { : CentralSurfaces.FADE_KEYGUARD_DURATION; boolean fromAod = previousState == AOD || previousState == PULSING; - mAnimateChange = !mLaunchingAffordanceWithPreview && !fromAod; + // If launch/occlude animations were playing, they already animated the scrim + // alpha to 0f as part of the animation. If we animate it now, we'll set it back + // to 1f and animate it back to 0f, causing an unwanted scrim flash. + mAnimateChange = !mLaunchingAffordanceWithPreview + && !mOccludeAnimationPlaying + && !fromAod; mFrontTint = Color.TRANSPARENT; mBehindTint = Color.BLACK; @@ -308,6 +313,7 @@ public enum ScrimState { boolean mWallpaperSupportsAmbientMode; boolean mHasBackdrop; boolean mLaunchingAffordanceWithPreview; + boolean mOccludeAnimationPlaying; boolean mWakeLockScreenSensorActive; boolean mKeyguardFadingAway; long mKeyguardFadingAwayDuration; @@ -411,6 +417,10 @@ public enum ScrimState { mLaunchingAffordanceWithPreview = launchingAffordanceWithPreview; } + public void setOccludeAnimationPlaying(boolean occludeAnimationPlaying) { + mOccludeAnimationPlaying = occludeAnimationPlaying; + } + public boolean isLowPowerState() { return false; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 69b683b9d054..06d0758c90eb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -54,6 +54,7 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; import com.android.keyguard.KeyguardViewController; import com.android.keyguard.ViewMediatorCallback; +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; @@ -282,6 +283,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>(); private boolean mIsModernAlternateBouncerEnabled; private boolean mIsBackAnimationEnabled; + private final boolean mUdfpsNewTouchDetectionEnabled; + private final UdfpsOverlayInteractor mUdfpsOverlayInteractor; private OnDismissAction mAfterKeyguardGoneAction; private Runnable mKeyguardGoneCancelAction; @@ -336,7 +339,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor, PrimaryBouncerInteractor primaryBouncerInteractor, BouncerView primaryBouncerView, - AlternateBouncerInteractor alternateBouncerInteractor) { + AlternateBouncerInteractor alternateBouncerInteractor, + UdfpsOverlayInteractor udfpsOverlayInteractor + ) { mContext = context; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; @@ -362,6 +367,8 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mAlternateBouncerInteractor = alternateBouncerInteractor; mIsBackAnimationEnabled = featureFlags.isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM); + mUdfpsNewTouchDetectionEnabled = featureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION); + mUdfpsOverlayInteractor = udfpsOverlayInteractor; } @Override @@ -1443,16 +1450,48 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } /** + * An opportunity for the AlternateBouncer to handle the touch instead of sending + * the touch to NPVC child views. + * @return true if the alternate bouncer should consime the touch and prevent it from + * going to its child views + */ + public boolean dispatchTouchEvent(MotionEvent event) { + if (shouldInterceptTouchEvent(event) + && !mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(event)) { + onTouch(event); + } + return shouldInterceptTouchEvent(event); + } + + /** + * Whether the touch should be intercepted by the AlternateBouncer before going to the + * notification shade's child views. + */ + public boolean shouldInterceptTouchEvent(MotionEvent event) { + return mAlternateBouncerInteractor.isVisibleState(); + } + + /** * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently * showing. */ public boolean onTouch(MotionEvent event) { - boolean handledTouch = false; - if (event.getAction() == MotionEvent.ACTION_UP - && mAlternateBouncerInteractor.isVisibleState() - && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) { - showPrimaryBouncer(true); - handledTouch = true; + boolean handleTouch = shouldInterceptTouchEvent(event); + if (handleTouch) { + final boolean actionDown = event.getActionMasked() == MotionEvent.ACTION_DOWN; + final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch() + && event.getActionMasked() == MotionEvent.ACTION_UP; + final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade = + mUdfpsNewTouchDetectionEnabled && mKeyguardUpdateManager.isUdfpsEnrolled(); + final boolean actionOutsideShouldDismissAlternateBouncer = + event.getActionMasked() == MotionEvent.ACTION_OUTSIDE + && !udfpsOverlayWillForwardEventsOutsideNotificationShade; + if (actionDown) { + mAlternateBouncerInteractor.setReceivedDownTouch(true); + } else if ((actionDownThenUp || actionOutsideShouldDismissAlternateBouncer) + && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) { + showPrimaryBouncer(true); + } } // Forward NPVC touches to callbacks in case they want to respond to touches @@ -1460,7 +1499,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb callback.onTouch(event); } - return handledTouch; + return handleTouch; } /** Update keyguard position based on a tapped X coordinate. */ diff --git a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java index a0a0372426ec..209ea41fed61 100644 --- a/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/util/leak/GarbageMonitor.java @@ -59,6 +59,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.concurrency.MessageRouter; @@ -412,6 +413,7 @@ public class GarbageMonitor implements Dumpable { private final GarbageMonitor gm; private ProcessMemInfo pmi; private boolean dumpInProgress; + private final PanelInteractor mPanelInteractor; @Inject public MemoryTile( @@ -423,11 +425,13 @@ public class GarbageMonitor implements Dumpable { StatusBarStateController statusBarStateController, ActivityStarter activityStarter, QSLogger qsLogger, - GarbageMonitor monitor + GarbageMonitor monitor, + PanelInteractor panelInteractor ) { super(host, backgroundLooper, mainHandler, falsingManager, metricsLogger, statusBarStateController, activityStarter, qsLogger); gm = monitor; + mPanelInteractor = panelInteractor; } @Override @@ -457,7 +461,7 @@ public class GarbageMonitor implements Dumpable { mHandler.post(() -> { dumpInProgress = false; refreshState(); - getHost().collapsePanels(); + mPanelInteractor.collapsePanels(); mActivityStarter.postStartActivityDismissingKeyguard(shareIntent, 0); }); } diff --git a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java index a062e7b2db50..492f2318fec6 100644 --- a/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java +++ b/packages/SystemUI/src/com/android/systemui/wallet/ui/WalletScreenController.java @@ -77,8 +77,11 @@ public class WalletScreenController implements private final FalsingManager mFalsingManager; private final UiEventLogger mUiEventLogger; - @VisibleForTesting String mSelectedCardId; - @VisibleForTesting boolean mIsDismissed; + + @VisibleForTesting + String mSelectedCardId; + @VisibleForTesting + boolean mIsDismissed; public WalletScreenController( Context context, @@ -124,9 +127,20 @@ public class WalletScreenController implements } Log.i(TAG, "Successfully retrieved wallet cards."); List<WalletCard> walletCards = response.getWalletCards(); - List<WalletCardViewInfo> data = new ArrayList<>(walletCards.size()); + + boolean allUnknown = true; + for (WalletCard card : walletCards) { + if (card.getCardType() != WalletCard.CARD_TYPE_UNKNOWN) { + allUnknown = false; + break; + } + } + + List<WalletCardViewInfo> paymentCardData = new ArrayList<>(); for (WalletCard card : walletCards) { - data.add(new QAWalletCardViewInfo(mContext, card)); + if (allUnknown || card.getCardType() == WalletCard.CARD_TYPE_PAYMENT) { + paymentCardData.add(new QAWalletCardViewInfo(mContext, card)); + } } // Get on main thread for UI updates. @@ -134,18 +148,18 @@ public class WalletScreenController implements if (mIsDismissed) { return; } - if (data.isEmpty()) { + if (paymentCardData.isEmpty()) { showEmptyStateView(); } else { int selectedIndex = response.getSelectedIndex(); - if (selectedIndex >= data.size()) { + if (selectedIndex >= paymentCardData.size()) { Log.w(TAG, "Invalid selected card index, showing empty state."); showEmptyStateView(); } else { boolean isUdfpsEnabled = mKeyguardUpdateMonitor.isUdfpsEnrolled() && mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); mWalletView.showCardCarousel( - data, + paymentCardData, selectedIndex, !mKeyguardStateController.isUnlocked(), isUdfpsEnabled); @@ -213,7 +227,6 @@ public class WalletScreenController implements } - @Override public void onCardClicked(@NonNull WalletCardViewInfo cardInfo) { if (mFalsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) { diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt new file mode 100644 index 000000000000..eb86c0590018 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2023 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.keyguard + +import android.telephony.PinResult +import android.telephony.TelephonyManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.LayoutInflater +import androidx.test.filters.SmallTest +import com.android.internal.util.LatencyTracker +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.util.mockito.any +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class KeyguardSimPinViewControllerTest : SysuiTestCase() { + private lateinit var simPinView: KeyguardSimPinView + private lateinit var underTest: KeyguardSimPinViewController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode + @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback + @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory + @Mock private lateinit var latencyTracker: LatencyTracker + @Mock private lateinit var liftToActivateListener: LiftToActivateListener + @Mock private lateinit var telephonyManager: TelephonyManager + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var emergencyButtonController: EmergencyButtonController + @Mock + private lateinit var keyguardMessageAreaController: + KeyguardMessageAreaController<BouncerKeyguardMessageArea> + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + `when`(messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java))) + .thenReturn(keyguardMessageAreaController) + `when`(telephonyManager.createForSubscriptionId(anyInt())).thenReturn(telephonyManager) + `when`(telephonyManager.supplyIccLockPin(anyString())) + .thenReturn(mock(PinResult::class.java)) + simPinView = + LayoutInflater.from(context).inflate(R.layout.keyguard_sim_pin_view, null) + as KeyguardSimPinView + underTest = + KeyguardSimPinViewController( + simPinView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + keyguardSecurityCallback, + messageAreaControllerFactory, + latencyTracker, + liftToActivateListener, + telephonyManager, + falsingCollector, + emergencyButtonController + ) + underTest.init() + } + + @Test + fun onViewAttached() { + underTest.onViewAttached() + } + + @Test + fun onViewDetached() { + underTest.onViewDetached() + } + + @Test + fun onResume() { + underTest.onResume(KeyguardSecurityView.VIEW_REVEALED) + verify(keyguardUpdateMonitor) + .registerCallback(any(KeyguardUpdateMonitorCallback::class.java)) + } + + @Test + fun onPause() { + underTest.onPause() + verify(keyguardUpdateMonitor).removeCallback(any(KeyguardUpdateMonitorCallback::class.java)) + } + + @Test + fun startAppearAnimation() { + underTest.startAppearAnimation() + verify(keyguardMessageAreaController) + .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false) + } + + @Test + fun startDisappearAnimation() { + underTest.startDisappearAnimation {} + } + + @Test + fun resetState() { + underTest.resetState() + verify(keyguardMessageAreaController).setMessage("") + } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt new file mode 100644 index 000000000000..2dcca55b9318 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSimPukViewControllerTest.kt @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2023 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.keyguard + +import android.telephony.PinResult +import android.telephony.TelephonyManager +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import android.view.LayoutInflater +import androidx.test.filters.SmallTest +import com.android.internal.util.LatencyTracker +import com.android.internal.widget.LockPatternUtils +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingCollector +import com.android.systemui.util.mockito.any +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyString +import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.MockitoAnnotations + +@SmallTest +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper +class KeyguardSimPukViewControllerTest : SysuiTestCase() { + private lateinit var simPukView: KeyguardSimPukView + private lateinit var underTest: KeyguardSimPukViewController + @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor + @Mock private lateinit var securityMode: KeyguardSecurityModel.SecurityMode + @Mock private lateinit var lockPatternUtils: LockPatternUtils + @Mock private lateinit var keyguardSecurityCallback: KeyguardSecurityCallback + @Mock private lateinit var messageAreaControllerFactory: KeyguardMessageAreaController.Factory + @Mock private lateinit var latencyTracker: LatencyTracker + @Mock private lateinit var liftToActivateListener: LiftToActivateListener + @Mock private lateinit var telephonyManager: TelephonyManager + @Mock private lateinit var falsingCollector: FalsingCollector + @Mock private lateinit var emergencyButtonController: EmergencyButtonController + @Mock + private lateinit var keyguardMessageAreaController: + KeyguardMessageAreaController<BouncerKeyguardMessageArea> + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + Mockito.`when`( + messageAreaControllerFactory.create(Mockito.any(KeyguardMessageArea::class.java)) + ) + .thenReturn(keyguardMessageAreaController) + Mockito.`when`(telephonyManager.createForSubscriptionId(Mockito.anyInt())) + .thenReturn(telephonyManager) + Mockito.`when`(telephonyManager.supplyIccLockPuk(anyString(), anyString())) + .thenReturn(Mockito.mock(PinResult::class.java)) + simPukView = + LayoutInflater.from(context).inflate(R.layout.keyguard_sim_puk_view, null) + as KeyguardSimPukView + underTest = + KeyguardSimPukViewController( + simPukView, + keyguardUpdateMonitor, + securityMode, + lockPatternUtils, + keyguardSecurityCallback, + messageAreaControllerFactory, + latencyTracker, + liftToActivateListener, + telephonyManager, + falsingCollector, + emergencyButtonController + ) + underTest.init() + } + + @Test + fun onViewAttached() { + underTest.onViewAttached() + Mockito.verify(keyguardUpdateMonitor) + .registerCallback(any(KeyguardUpdateMonitorCallback::class.java)) + } + + @Test + fun onViewDetached() { + underTest.onViewDetached() + Mockito.verify(keyguardUpdateMonitor) + .removeCallback(any(KeyguardUpdateMonitorCallback::class.java)) + } + + @Test + fun onResume() { + underTest.onResume(KeyguardSecurityView.VIEW_REVEALED) + } + + @Test + fun onPause() { + underTest.onPause() + } + + @Test + fun startAppearAnimation() { + underTest.startAppearAnimation() + Mockito.verify(keyguardMessageAreaController) + .setMessage(context.resources.getString(R.string.keyguard_enter_your_pin), false) + } + + @Test + fun startDisappearAnimation() { + underTest.startDisappearAnimation {} + } + + @Test + fun resetState() { + underTest.resetState() + Mockito.verify(keyguardMessageAreaController) + .setMessage(context.resources.getString(R.string.kg_puk_enter_puk_hint)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt index ca6f42618e2a..eb8295653199 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogTest.kt @@ -25,12 +25,19 @@ import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView +import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.settings.FakeSettings +import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SystemSettings +import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations + +private const val ON: Int = 1 +private const val OFF: Int = 0 /** Tests for [FontScalingDialog]. */ @SmallTest @@ -39,6 +46,8 @@ import org.junit.runner.RunWith class FontScalingDialogTest : SysuiTestCase() { private lateinit var fontScalingDialog: FontScalingDialog private lateinit var systemSettings: SystemSettings + private lateinit var secureSettings: SecureSettings + private lateinit var backgroundExecutor: FakeExecutor private val fontSizeValueArray: Array<String> = mContext .getResources() @@ -46,9 +55,13 @@ class FontScalingDialogTest : SysuiTestCase() { @Before fun setUp() { + MockitoAnnotations.initMocks(this) val mainHandler = Handler(TestableLooper.get(this).getLooper()) systemSettings = FakeSettings() - fontScalingDialog = FontScalingDialog(mContext, systemSettings as FakeSettings) + secureSettings = FakeSettings() + backgroundExecutor = FakeExecutor(FakeSystemClock()) + fontScalingDialog = + FontScalingDialog(mContext, systemSettings, secureSettings, backgroundExecutor) } @Test @@ -76,6 +89,7 @@ class FontScalingDialogTest : SysuiTestCase() { seekBarWithIconButtonsView.setProgress(0) iconEndFrame.performClick() + backgroundExecutor.runAllReady() val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f) assertThat(seekBar.getProgress()).isEqualTo(1) @@ -96,6 +110,7 @@ class FontScalingDialogTest : SysuiTestCase() { seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1) iconStartFrame.performClick() + backgroundExecutor.runAllReady() val currentScale = systemSettings.getFloat(Settings.System.FONT_SCALE, /* def = */ 1.0f) assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2) @@ -104,4 +119,26 @@ class FontScalingDialogTest : SysuiTestCase() { fontScalingDialog.dismiss() } + + @Test + fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() { + fontScalingDialog.show() + + val seekBarWithIconButtonsView: SeekBarWithIconButtonsView = + fontScalingDialog.findViewById(R.id.font_scaling_slider)!! + secureSettings.putInt(Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF) + + // Default seekbar progress for font size is 1, set it to another progress 0 + seekBarWithIconButtonsView.setProgress(0) + backgroundExecutor.runAllReady() + + val currentSettings = + secureSettings.getInt( + Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, + /* def = */ OFF + ) + assertThat(currentSettings).isEqualTo(ON) + + fontScalingDialog.dismiss() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 9a73898ca7c7..445cc8739463 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -53,6 +53,7 @@ import android.hardware.fingerprint.FingerprintSensorProperties; import android.hardware.fingerprint.FingerprintSensorPropertiesInternal; import android.hardware.fingerprint.IUdfpsOverlayController; import android.hardware.fingerprint.IUdfpsOverlayControllerCallback; +import android.hardware.input.InputManager; import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; @@ -63,6 +64,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.Surface; import android.view.View; +import android.view.ViewRootImpl; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; @@ -231,6 +233,10 @@ public class UdfpsControllerTest extends SysuiTestCase { private FingerprintSensorPropertiesInternal mOpticalProps; private FingerprintSensorPropertiesInternal mUltrasonicProps; private UdfpsUtils mUdfpsUtils; + @Mock + private InputManager mInputManager; + @Mock + private ViewRootImpl mViewRootImpl; @Before public void setUp() { @@ -248,6 +254,7 @@ public class UdfpsControllerTest extends SysuiTestCase { when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); when(mSessionTracker.getSessionId(anyInt())).thenReturn( (new InstanceIdSequence(1 << 20)).newInstanceId()); + when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, @@ -304,7 +311,7 @@ public class UdfpsControllerTest extends SysuiTestCase { mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker, mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor, mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker, - mAlternateBouncerInteractor, mSecureSettings, mUdfpsUtils); + mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils); verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture()); mOverlayController = mOverlayCaptor.getValue(); verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture()); @@ -1262,6 +1269,81 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test + public void onTouch_withNewTouchDetection_pilferPointer() throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( + InteractionEvent.DOWN, 1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultDown); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // THEN the touch is pilfered, expected twice (valid overlap and touch on sensor) + verify(mInputManager, times(2)).pilferPointers(any()); + } + + @Test + public void onTouch_withNewTouchDetection_doNotPilferPointer() throws RemoteException { + final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, + 0L); + final TouchProcessorResult processorResultUnchanged = + new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED, + 1 /* pointerId */, touchData); + + // Enable new touch detection. + when(mFeatureFlags.isEnabled(Flags.UDFPS_NEW_TOUCH_DETECTION)).thenReturn(true); + + // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. + initUdfpsController(mOpticalProps, false /* hasAlternateTouchProvider */); + + // Configure UdfpsView to not accept the ACTION_DOWN event + when(mUdfpsView.isDisplayConfigured()).thenReturn(false); + when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(false); + + // GIVEN that the overlay is showing and a11y touch exploration NOT enabled + when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false); + mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, + BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); + mFgExecutor.runAllReady(); + + verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); + + // WHEN ACTION_DOWN is received and touch is not within sensor + when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( + processorResultUnchanged); + MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); + mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); + mBiometricExecutor.runAllReady(); + downEvent.recycle(); + + // THEN the touch is NOT pilfered + verify(mInputManager, times(0)).pilferPointers(any()); + } + + @Test public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException { // GIVEN UDFPS overlay is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, @@ -1285,6 +1367,5 @@ public class UdfpsControllerTest extends SysuiTestCase { // THEN is fingerDown should be FALSE assertFalse(mUdfpsController.isFingerDown()); - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt index 87d5ae64dee8..9431d86f63e1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt @@ -77,16 +77,14 @@ class UdfpsOverlayInteractorTest : SysuiTestCase() { runCurrent() - // Then touch should not be intercepted - val canInterceptTrue = underTest.canInterceptTouchInUdfpsBounds(downEv) - assertThat(canInterceptTrue).isFalse() + // Then touch is within udfps area + assertThat(underTest.isTouchWithinUdfpsArea(downEv)).isTrue() // When touch is outside of bounds whenever(overlayBounds.contains(downEv.x.toInt(), downEv.y.toInt())).thenReturn(false) - // Then touch should be intercepted - val canInterceptFalse = underTest.canInterceptTouchInUdfpsBounds(downEv) - assertThat(canInterceptFalse).isTrue() + // Then touch is not within udfps area + assertThat(underTest.isTouchWithinUdfpsArea(downEv)).isFalse() } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java index 3312c4335ab4..aad49f9b8069 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/DreamHomeControlsComplicationTest.java @@ -35,7 +35,6 @@ import android.view.View; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; -import com.android.systemui.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.view.LaunchableImageView; import com.android.systemui.condition.SelfExecutingMonitor; @@ -89,9 +88,6 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { private ArgumentCaptor<ControlsListingController.ControlsListingCallback> mCallbackCaptor; @Mock - private View mView; - - @Mock private LaunchableImageView mHomeControlsView; @Mock @@ -115,7 +111,6 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { when(mControlsComponent.getControlsListingController()).thenReturn( Optional.of(mControlsListingController)); when(mControlsComponent.getVisibility()).thenReturn(AVAILABLE); - when(mView.findViewById(R.id.home_controls_chip)).thenReturn(mHomeControlsView); mMonitor = SelfExecutingMonitor.createInstance(); } @@ -223,7 +218,7 @@ public class DreamHomeControlsComplicationTest extends SysuiTestCase { public void testClick_logsUiEvent() { final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController = new DreamHomeControlsComplication.DreamHomeControlsChipViewController( - mView, + mHomeControlsView, mActivityStarter, mContext, mControlsComponent, diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt index 0fac3db2dc1f..4565762929d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaViewControllerTest.kt @@ -21,7 +21,6 @@ import android.content.res.Configuration.ORIENTATION_LANDSCAPE import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.View -import androidx.constraintlayout.widget.ConstraintSet import androidx.test.filters.SmallTest import com.android.systemui.R import com.android.systemui.SysuiTestCase @@ -61,8 +60,6 @@ class MediaViewControllerTest : SysuiTestCase() { @Mock private lateinit var mediaSubTitleWidgetState: WidgetState @Mock private lateinit var mediaContainerWidgetState: WidgetState @Mock private lateinit var mediaFlags: MediaFlags - @Mock private lateinit var expandedLayout: ConstraintSet - @Mock private lateinit var collapsedLayout: ConstraintSet val delta = 0.1F @@ -82,16 +79,47 @@ class MediaViewControllerTest : SysuiTestCase() { } @Test - fun testOrientationChanged_layoutsAreLoaded() { - mediaViewController.expandedLayout = expandedLayout - mediaViewController.collapsedLayout = collapsedLayout + fun testOrientationChanged_heightOfPlayerIsUpdated() { + val newConfig = Configuration() + + mediaViewController.attach(player, MediaViewController.TYPE.PLAYER) + // Change the height to see the effect of orientation change. + MediaViewController.backgroundIds.forEach { id -> + mediaViewController.expandedLayout.getConstraint(id).layout.mHeight = 10 + } + newConfig.orientation = ORIENTATION_LANDSCAPE + configurationController.onConfigurationChanged(newConfig) + MediaViewController.backgroundIds.forEach { id -> + assertTrue( + mediaViewController.expandedLayout.getConstraint(id).layout.mHeight == + context.resources.getDimensionPixelSize( + R.dimen.qs_media_session_height_expanded + ) + ) + } + } + + @Test + fun testOrientationChanged_heightOfRecCardIsUpdated() { val newConfig = Configuration() + + mediaViewController.attach(recommendation, MediaViewController.TYPE.RECOMMENDATION) + // Change the height to see the effect of orientation change. + mediaViewController.expandedLayout + .getConstraint(MediaViewController.recSizingViewId) + .layout + .mHeight = 10 newConfig.orientation = ORIENTATION_LANDSCAPE configurationController.onConfigurationChanged(newConfig) - verify(expandedLayout).load(context, R.xml.media_session_expanded) - verify(collapsedLayout).load(context, R.xml.media_session_collapsed) + assertTrue( + mediaViewController.expandedLayout + .getConstraint(MediaViewController.recSizingViewId) + .layout + .mHeight == + context.resources.getDimensionPixelSize(R.dimen.qs_media_session_height_expanded) + ) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt index bc31a0ec81a6..8e32f81b193f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/gestural/BackPanelControllerTest.kt @@ -127,7 +127,7 @@ class BackPanelControllerTest : SysuiTestCase() { mBackPanelController.params.deactivationSwipeTriggerThreshold ) clearInvocations(backCallback) - Thread.sleep(MIN_DURATION_ACTIVE_ANIMATION) + Thread.sleep(MIN_DURATION_ACTIVE_BEFORE_INACTIVE_ANIMATION) // Move in the opposite direction to cross the deactivation threshold and cancel back continueTouch(START_X) diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt index 40c733a19ccd..0a8cd269139f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/NoteTaskControllerTest.kt @@ -216,6 +216,41 @@ internal class NoteTaskControllerTest : SysuiTestCase() { } @Test + fun showNoteTaskWithUser_keyguardIsLocked_shouldStartActivityWithExpectedUserAndLogUiEvent() { + val user10 = UserHandle.of(/* userId= */ 10) + val expectedInfo = + noteTaskInfo.copy( + entryPoint = NoteTaskEntryPoint.TAIL_BUTTON, + isKeyguardLocked = true, + ) + whenever(keyguardManager.isKeyguardLocked).thenReturn(expectedInfo.isKeyguardLocked) + whenever(resolver.resolveInfo(any(), any())).thenReturn(expectedInfo) + + createNoteTaskController() + .showNoteTaskAsUser( + entryPoint = expectedInfo.entryPoint!!, + user = user10, + ) + + val intentCaptor = argumentCaptor<Intent>() + val userCaptor = argumentCaptor<UserHandle>() + verify(context).startActivityAsUser(capture(intentCaptor), capture(userCaptor)) + intentCaptor.value.let { intent -> + assertThat(intent.action).isEqualTo(Intent.ACTION_CREATE_NOTE) + assertThat(intent.`package`).isEqualTo(NOTES_PACKAGE_NAME) + assertThat(intent.flags and FLAG_ACTIVITY_NEW_TASK).isEqualTo(FLAG_ACTIVITY_NEW_TASK) + assertThat(intent.flags and FLAG_ACTIVITY_MULTIPLE_TASK) + .isEqualTo(FLAG_ACTIVITY_MULTIPLE_TASK) + assertThat(intent.flags and FLAG_ACTIVITY_NEW_DOCUMENT) + .isEqualTo(FLAG_ACTIVITY_NEW_DOCUMENT) + assertThat(intent.getBooleanExtra(Intent.EXTRA_USE_STYLUS_MODE, false)).isTrue() + } + assertThat(userCaptor.value).isEqualTo(user10) + verify(eventLogger).logNoteTaskOpened(expectedInfo) + verifyZeroInteractions(bubbles) + } + + @Test fun showNoteTask_keyguardIsUnlocked_shouldStartBubblesWithoutLoggingUiEvent() { val expectedInfo = noteTaskInfo.copy( diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java index 64e9a3e58bd6..7e052bfa15d2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileServicesTest.java @@ -29,6 +29,7 @@ import static org.mockito.Mockito.when; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Intent; +import android.os.Binder; import android.os.Handler; import android.os.RemoteException; import android.os.UserHandle; @@ -41,6 +42,7 @@ import android.testing.TestableLooper.RunWithLooper; import com.android.systemui.SysuiTestCase; import com.android.systemui.broadcast.BroadcastDispatcher; import com.android.systemui.qs.QSHost; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.phone.StatusBarIconController; @@ -91,6 +93,8 @@ public class TileServicesTest extends SysuiTestCase { private TileLifecycleManager mTileLifecycleManager; @Mock private QSHost mQSHost; + @Mock + private PanelInteractor mPanelInteractor; @Before public void setUp() throws Exception { @@ -107,7 +111,8 @@ public class TileServicesTest extends SysuiTestCase { Provider<Handler> provider = () -> new Handler(mTestableLooper.getLooper()); mTileService = new TestTileServices(mQSHost, provider, mBroadcastDispatcher, - mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController); + mUserTracker, mKeyguardStateController, mCommandQueue, mStatusBarIconController, + mPanelInteractor); } @After @@ -222,13 +227,37 @@ public class TileServicesTest extends SysuiTestCase { verify(tile, never()).startActivityAndCollapse(pi); } + @Test + public void testOnStartActivityCollapsesPanel() { + CustomTile tile = mock(CustomTile.class); + ComponentName componentName = mock(ComponentName.class); + when(tile.getComponent()).thenReturn(componentName); + when(componentName.getPackageName()).thenReturn(this.getContext().getPackageName()); + TileServiceManager manager = mTileService.getTileWrapper(tile); + + mTileService.onStartActivity(manager.getToken()); + verify(mPanelInteractor).forceCollapsePanels(); + } + + @Test + public void testOnShowDialogCollapsesPanel() { + CustomTile tile = mock(CustomTile.class); + ComponentName componentName = mock(ComponentName.class); + when(tile.getComponent()).thenReturn(componentName); + when(componentName.getPackageName()).thenReturn(this.getContext().getPackageName()); + TileServiceManager manager = mTileService.getTileWrapper(tile); + + mTileService.onShowDialog(manager.getToken()); + verify(mPanelInteractor).forceCollapsePanels(); + } + private class TestTileServices extends TileServices { TestTileServices(QSHost host, Provider<Handler> handlerProvider, BroadcastDispatcher broadcastDispatcher, UserTracker userTracker, KeyguardStateController keyguardStateController, CommandQueue commandQueue, - StatusBarIconController statusBarIconController) { + StatusBarIconController statusBarIconController, PanelInteractor panelInteractor) { super(host, handlerProvider, broadcastDispatcher, userTracker, keyguardStateController, - commandQueue, statusBarIconController); + commandQueue, statusBarIconController, panelInteractor); } @Override @@ -237,6 +266,8 @@ public class TileServicesTest extends SysuiTestCase { TileServiceManager manager = mock(TileServiceManager.class); mManagers.add(manager); when(manager.isLifecycleStarted()).thenReturn(true); + Binder b = new Binder(); + when(manager.getToken()).thenReturn(b); return manager; } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt new file mode 100644 index 000000000000..45783abe9ee4 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2023 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.systemui.qs.pipeline.domain.interactor + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.CentralSurfaces +import java.util.Optional +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class PanelInteractorImplTest : SysuiTestCase() { + + @Mock private lateinit var centralSurfaces: CentralSurfaces + + @Before + fun setup() { + MockitoAnnotations.initMocks(this) + } + + @Test + fun openPanels_callsCentralSurfaces() { + val underTest = PanelInteractorImpl(Optional.of(centralSurfaces)) + + underTest.openPanels() + + verify(centralSurfaces).postAnimateOpenPanels() + } + + @Test + fun collapsePanels_callsCentralSurfaces() { + val underTest = PanelInteractorImpl(Optional.of(centralSurfaces)) + + underTest.collapsePanels() + + verify(centralSurfaces).postAnimateCollapsePanels() + } + + @Test + fun forceCollapsePanels_callsCentralSurfaces() { + val underTest = PanelInteractorImpl(Optional.of(centralSurfaces)) + + underTest.forceCollapsePanels() + + verify(centralSurfaces).postAnimateForceCollapsePanels() + } + + @Test + fun whenOptionalEmpty_doesnThrow() { + val underTest = PanelInteractorImpl(Optional.empty()) + + underTest.openPanels() + underTest.collapsePanels() + underTest.forceCollapsePanels() + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt index bd99cd482859..eeebd4fb7792 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/FontScalingTileTest.kt @@ -84,6 +84,7 @@ class FontScalingTileTest : SysuiTestCase() { qsLogger, dialogLaunchAnimator, FakeSettings(), + FakeSettings(), featureFlags ) fontScalingTile.initialize() diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt index 33921c7c84b1..3642e874e7ae 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/LocationTileTest.kt @@ -31,9 +31,12 @@ import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.qs.QSHost import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.statusbar.policy.LocationController +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.capture import com.google.common.truth.Truth.assertThat import org.junit.After import org.junit.Before @@ -41,6 +44,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @@ -65,6 +69,8 @@ class LocationTileTest : SysuiTestCase() { private lateinit var locationController: LocationController @Mock private lateinit var keyguardStateController: KeyguardStateController + @Mock + private lateinit var panelInteractor: PanelInteractor private val uiEventLogger = UiEventLoggerFake() private lateinit var testableLooper: TestableLooper @@ -86,7 +92,9 @@ class LocationTileTest : SysuiTestCase() { activityStarter, qsLogger, locationController, - keyguardStateController) + keyguardStateController, + panelInteractor, + ) } @After @@ -116,4 +124,18 @@ class LocationTileTest : SysuiTestCase() { assertThat(state.icon) .isEqualTo(QSTileImpl.ResourceIcon.get(R.drawable.qs_location_icon_on)) } + + @Test + fun testClickWhenLockedWillCallOpenPanels() { + `when`(keyguardStateController.isMethodSecure).thenReturn(true) + `when`(keyguardStateController.isShowing).thenReturn(true) + + tile.handleClick(null) + + val captor = argumentCaptor<Runnable>() + verify(activityStarter).postQSRunnableDismissingKeyguard(capture(captor)) + captor.value.run() + + verify(panelInteractor).openPanels() + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java index 5aef75832fac..d9ed1a299f51 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/ScreenRecordTileTest.java @@ -45,6 +45,7 @@ import com.android.systemui.plugins.qs.QSTile; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.qs.QSHost; import com.android.systemui.qs.logging.QSLogger; +import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor; import com.android.systemui.qs.tileimpl.QSTileImpl; import com.android.systemui.screenrecord.RecordingController; import com.android.systemui.statusbar.phone.KeyguardDismissUtil; @@ -83,6 +84,8 @@ public class ScreenRecordTileTest extends SysuiTestCase { private KeyguardStateController mKeyguardStateController; @Mock private DialogLaunchAnimator mDialogLaunchAnimator; + @Mock + private PanelInteractor mPanelInteractor; private TestableLooper mTestableLooper; private ScreenRecordTile mTile; @@ -108,7 +111,8 @@ public class ScreenRecordTileTest extends SysuiTestCase { mController, mKeyguardDismissUtil, mKeyguardStateController, - mDialogLaunchAnimator + mDialogLaunchAnimator, + mPanelInteractor ); mTile.initialize(); @@ -146,7 +150,7 @@ public class ScreenRecordTileTest extends SysuiTestCase { assertNotNull(onStartRecordingClicked.getValue()); onStartRecordingClicked.getValue().run(); verify(mDialogLaunchAnimator).disableAllCurrentDialogsExitAnimations(); - verify(mHost).collapsePanels(); + verify(mPanelInteractor).collapsePanels(); } // Test that the tile is active and labeled correctly when the controller is starting diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 526dc8d150fe..dd7929771bb2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -223,6 +223,16 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { } @Test + public void attach_fadingAway_wallpaperVisible() { + clearInvocations(mWindowManager); + mNotificationShadeWindowController.attach(); + mNotificationShadeWindowController.setKeyguardFadingAway(true); + + verify(mWindowManager).updateViewLayout(any(), mLayoutParameters.capture()); + assertThat((mLayoutParameters.getValue().flags & FLAG_SHOW_WALLPAPER) != 0).isTrue(); + } + + @Test public void setBackgroundBlurRadius_expandedWithBlurs() { mNotificationShadeWindowController.setBackgroundBlurRadius(10); verify(mNotificationShadeWindowView).setVisibility(eq(View.VISIBLE)); diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index bdb0e7ed8d9d..629208e130af 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -26,13 +26,11 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel @@ -69,8 +67,8 @@ import org.mockito.Mockito.anyFloat import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations +import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -97,8 +95,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var pulsingGestureListener: PulsingGestureListener @Mock private lateinit var notificationInsetsController: NotificationInsetsController - @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor - @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor @Mock lateinit var keyguardBouncerComponentFactory: KeyguardBouncerComponent.Factory @Mock lateinit var keyguardBouncerComponent: KeyguardBouncerComponent @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @@ -127,7 +123,8 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { .thenReturn(emptyFlow<TransitionStep>()) val featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false) + featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true) + featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false) featureFlags.set(Flags.DUAL_SHADE, false) val inputProxy = MultiShadeInputProxy() @@ -154,8 +151,6 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { pulsingGestureListener, keyguardBouncerViewModel, keyguardBouncerComponentFactory, - alternateBouncerInteractor, - udfpsOverlayInteractor, keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, featureFlags, @@ -310,17 +305,15 @@ class NotificationShadeWindowViewControllerTest : SysuiTestCase() { } @Test - fun shouldInterceptTouchEvent_downEventAlternateBouncer_ignoreIfInUdfpsOverlay() = - testScope.runTest { - // Down event within udfpsOverlay bounds while alternateBouncer is showing - whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(DOWN_EVENT)) - .thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() { + // down event should be intercepted by keyguardViewManager + whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) + .thenReturn(true) - // Then touch should not be intercepted - val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) - assertThat(shouldIntercept).isFalse() - } + // Then touch should not be intercepted + val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) + assertThat(shouldIntercept).isTrue() + } @Test fun testGetBouncerContainer() = diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index 5d0f408a0522..b4b5ec126234 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -26,13 +26,11 @@ import com.android.keyguard.LockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.R import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.dock.DockManager import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyguard.KeyguardUnlockAnimationController -import com.android.systemui.keyguard.domain.interactor.AlternateBouncerInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel @@ -103,14 +101,12 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @Mock private lateinit var notificationInsetsController: NotificationInsetsController - @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock private lateinit var primaryBouncerToGoneTransitionViewModel: PrimaryBouncerToGoneTransitionViewModel @Captor private lateinit var interactionEventHandlerCaptor: ArgumentCaptor<InteractionEventHandler> - @Mock private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor private lateinit var underTest: NotificationShadeWindowView private lateinit var controller: NotificationShadeWindowViewController @@ -139,7 +135,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { .thenReturn(emptyFlow()) val featureFlags = FakeFeatureFlags() - featureFlags.set(Flags.TRACKPAD_GESTURE_BACK, false) + featureFlags.set(Flags.TRACKPAD_GESTURE_COMMON, true) + featureFlags.set(Flags.TRACKPAD_GESTURE_FEATURES, false) featureFlags.set(Flags.DUAL_SHADE, false) val inputProxy = MultiShadeInputProxy() testScope = TestScope() @@ -165,8 +162,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { pulsingGestureListener, keyguardBouncerViewModel, keyguardBouncerComponentFactory, - alternateBouncerInteractor, - udfpsOverlayInteractor, keyguardTransitionInteractor, primaryBouncerToGoneTransitionViewModel, featureFlags, @@ -206,8 +201,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) - whenever(udfpsOverlayInteractor.canInterceptTouchInUdfpsBounds(any())).thenReturn(true) + whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())).thenReturn(true) whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) // THEN we should intercept touch @@ -221,7 +215,8 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(false) + whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())) + .thenReturn(false) whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) // THEN we shouldn't intercept touch @@ -235,7 +230,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(alternateBouncerInteractor.isVisibleState()).thenReturn(true) + whenever(statusBarKeyguardViewManager.onTouch(any())).thenReturn(true) whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) // THEN we should handle the touch diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt index c92134b2212b..60bc3a45c0dd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowControllerTest.kt @@ -21,6 +21,7 @@ import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.internal.logging.MetricsLogger +import com.android.internal.statusbar.IStatusBarService import com.android.systemui.SysuiTestCase import com.android.systemui.classifier.FalsingCollector import com.android.systemui.flags.FeatureFlags @@ -93,6 +94,7 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { private val bubblesManager: BubblesManager = mock() private val dragController: ExpandableNotificationRowDragController = mock() private val dismissibilityProvider: NotificationDismissibilityProvider = mock() + private val statusBarService: IStatusBarService = mock() private lateinit var controller: ExpandableNotificationRowController @@ -130,7 +132,8 @@ class ExpandableNotificationRowControllerTest : SysuiTestCase() { peopleNotificationIdentifier, Optional.of(bubblesManager), dragController, - dismissibilityProvider + dismissibilityProvider, + statusBarService ) whenever(view.childrenContainer).thenReturn(childrenContainer) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt index 7b2051da4d15..0b90ebec3ec6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentViewTest.kt @@ -74,7 +74,7 @@ class NotificationContentViewTest : SysuiTestCase() { doReturn(10).whenever(spyRow).intrinsicHeight with(view) { - initialize(mPeopleNotificationIdentifier, mock(), mock(), mock()) + initialize(mPeopleNotificationIdentifier, mock(), mock(), mock(), mock()) setContainingNotification(spyRow) setHeights(/* smallHeight= */ 10, /* headsUpMaxHeight= */ 20, /* maxHeight= */ 30) contractedChild = createViewWithHeight(10) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java index f8a8e50efb4c..813bae893569 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java @@ -49,6 +49,7 @@ import android.view.LayoutInflater; import android.widget.RemoteViews; import com.android.internal.logging.MetricsLogger; +import com.android.internal.statusbar.IStatusBarService; import com.android.systemui.TestableDependency; import com.android.systemui.classifier.FalsingCollectorFake; import com.android.systemui.classifier.FalsingManagerFake; @@ -582,7 +583,8 @@ public class NotificationTestHelper { mock(MetricsLogger.class), mock(SmartReplyConstants.class), mock(SmartReplyController.class), - mFeatureFlags); + mFeatureFlags, + mock(IStatusBarService.class)); row.setAboveShelfChangedListener(aboveShelf -> { }); mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 7db219719bf0..1aba1fc37b64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -1342,6 +1342,18 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test + public void keyguard_notHidden_ifGoingAwayAndOccluded() { + setKeyguardShowingAndOccluded(true /* showing */, false /* occluded */); + + when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(true); + when(mKeyguardStateController.isOccluded()).thenReturn(true); + + mCentralSurfaces.updateIsKeyguard(false); + + verify(mStatusBarStateController, never()).setState(eq(SHADE), anyBoolean()); + } + + @Test public void frpLockedDevice_shadeDisabled() { when(mDeviceProvisionedController.isFrpActive()).thenReturn(true); when(mDozeServiceHost.isPulsing()).thenReturn(true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index 7a1270f3521d..a9ed17531926 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -25,6 +25,7 @@ import static com.android.systemui.statusbar.phone.ScrimState.SHADE_LOCKED; import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; @@ -1163,8 +1164,8 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testScrimFocus() { mScrimController.transitionTo(ScrimState.AOD); - Assert.assertFalse("Should not be focusable on AOD", mScrimBehind.isFocusable()); - Assert.assertFalse("Should not be focusable on AOD", mScrimInFront.isFocusable()); + assertFalse("Should not be focusable on AOD", mScrimBehind.isFocusable()); + assertFalse("Should not be focusable on AOD", mScrimInFront.isFocusable()); mScrimController.transitionTo(ScrimState.KEYGUARD); Assert.assertTrue("Should be focusable on keyguard", mScrimBehind.isFocusable()); @@ -1224,7 +1225,7 @@ public class ScrimControllerTest extends SysuiTestCase { public void testAnimatesTransitionToAod() { when(mDozeParameters.shouldControlScreenOff()).thenReturn(false); ScrimState.AOD.prepare(ScrimState.KEYGUARD); - Assert.assertFalse("No animation when ColorFade kicks in", + assertFalse("No animation when ColorFade kicks in", ScrimState.AOD.getAnimateChange()); reset(mDozeParameters); @@ -1236,9 +1237,9 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void testViewsDontHaveFocusHighlight() { - Assert.assertFalse("Scrim shouldn't have focus highlight", + assertFalse("Scrim shouldn't have focus highlight", mScrimInFront.getDefaultFocusHighlightEnabled()); - Assert.assertFalse("Scrim shouldn't have focus highlight", + assertFalse("Scrim shouldn't have focus highlight", mScrimBehind.getDefaultFocusHighlightEnabled()); } @@ -1738,7 +1739,7 @@ public class ScrimControllerTest extends SysuiTestCase { @Test public void aodStateSetsFrontScrimToNotBlend() { mScrimController.transitionTo(ScrimState.AOD); - Assert.assertFalse("Front scrim should not blend with main color", + assertFalse("Front scrim should not blend with main color", mScrimInFront.shouldBlendWithMainColor()); } @@ -1773,6 +1774,14 @@ public class ScrimControllerTest extends SysuiTestCase { verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway(); } + @Test + public void testDoNotAnimateChangeIfOccludeAnimationPlaying() { + mScrimController.setOccludeAnimationPlaying(true); + mScrimController.transitionTo(ScrimState.UNLOCKED); + + assertFalse(ScrimState.UNLOCKED.mAnimateChange); + } + private void assertAlphaAfterExpansion(ScrimView scrim, float expectedAlpha, float expansion) { mScrimController.setRawPanelExpansionFraction(expansion); finishAnimationsImmediately(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 31462623ce2d..d9546877a861 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -58,6 +59,7 @@ import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.SysuiTestCase; +import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.flags.FeatureFlags; @@ -126,12 +128,14 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor; @Mock private AlternateBouncerInteractor mAlternateBouncerInteractor; + @Mock private UdfpsOverlayInteractor mUdfpsOverlayInteractor; @Mock private BouncerView mBouncerView; @Mock private BouncerViewDelegate mBouncerViewDelegate; @Mock private OnBackAnimationCallback mBouncerViewDelegateBackCallback; @Mock private NotificationShadeWindowView mNotificationShadeWindowView; @Mock private WindowInsetsController mWindowInsetsController; @Mock private TaskbarDelegate mTaskbarDelegate; + @Mock private StatusBarKeyguardViewManager.KeyguardViewManagerCallback mCallback; private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback @@ -188,7 +192,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, mBouncerView, - mAlternateBouncerInteractor) { + mAlternateBouncerInteractor, + mUdfpsOverlayInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -675,7 +680,8 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { mPrimaryBouncerCallbackInteractor, mPrimaryBouncerInteractor, mBouncerView, - mAlternateBouncerInteractor) { + mAlternateBouncerInteractor, + mUdfpsOverlayInteractor) { @Override public ViewRootImpl getViewRootImpl() { return mViewRootImpl; @@ -713,7 +719,115 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void testAlternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() { + public void handleDispatchTouchEvent_alternateBouncerNotVisible() { + mStatusBarKeyguardViewManager.addCallback(mCallback); + + // GIVEN the alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); + + // THEN handleDispatchTouchEvent doesn't use the touches + assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + + // THEN the touch is not acted upon + verify(mCallback, never()).onTouch(any()); + } + + @Test + public void handleDispatchTouchEvent_shouldInterceptTouchAndHandleTouch() { + mStatusBarKeyguardViewManager.addCallback(mCallback); + + // GIVEN the alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // GIVEN all touches are NOT the udfps overlay + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); + + // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent + // to its child views (handleDispatchTouchEvent returns true) + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + + // THEN the touch is acted upon once for each dispatchTOuchEvent call + verify(mCallback, times(3)).onTouch(any()); + } + + @Test + public void handleDispatchTouchEvent_shouldInterceptTouchButNotHandleTouch() { + mStatusBarKeyguardViewManager.addCallback(mCallback); + + // GIVEN the alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // GIVEN all touches are within the udfps overlay + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(true); + + // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent + // to its child views (handleDispatchTouchEvent returns true) + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + + // THEN the touch is NOT acted upon at the moment + verify(mCallback, never()).onTouch(any()); + } + + @Test + public void shouldInterceptTouch_alternateBouncerNotVisible() { + // GIVEN the alternate bouncer is not visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); + + // THEN no motion events are intercepted + assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + } + + @Test + public void shouldInterceptTouch_alternateBouncerVisible() { + // GIVEN the alternate bouncer is visible + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + + // THEN all motion events are intercepted + assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) + )); + assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) + )); + } + + @Test + public void alternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() { // GIVEN the alternate bouncer has shown and calls to hide() will result in successfully // hiding it when(mAlternateBouncerInteractor.hide()).thenReturn(true); @@ -729,30 +843,67 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - public void testAlternateBouncerOnTouch_actionDown_doesNotHandleTouch() { + public void alternateBouncerOnTouch_actionDownThenUp_noMinTimeShown_noHideAltBouncer() { + reset(mAlternateBouncerInteractor); + + // GIVEN the alternate bouncer has shown for a minimum amount of time + when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(false); + when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); + + // WHEN ACTION_DOWN and ACTION_UP touch event comes + boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true); + boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); + + // THEN the touches are handled (doesn't let touches through to underlying views) + assertTrue(touchHandledDown); + assertTrue(touchHandledUp); + + // THEN alternate bouncer does NOT attempt to hide since min showing time wasn't met + verify(mAlternateBouncerInteractor, never()).hide(); + } + + @Test + public void alternateBouncerOnTouch_actionDownThenUp_handlesTouch_hidesAltBouncer() { + reset(mAlternateBouncerInteractor); + // GIVEN the alternate bouncer has shown for a minimum amount of time when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - // WHEN ACTION_DOWN touch event comes - boolean touchHandled = mStatusBarKeyguardViewManager.onTouch( + // WHEN ACTION_DOWN and ACTION_UP touch event comes + boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch( MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); + when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true); + boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch( + MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); + + // THEN the touches are handled + assertTrue(touchHandledDown); + assertTrue(touchHandledUp); - // THEN the touch is not handled - assertFalse(touchHandled); + // THEN alternate bouncer attempts to hide + verify(mAlternateBouncerInteractor).hide(); } @Test - public void testAlternateBouncerOnTouch_actionUp_handlesTouch() { + public void alternateBouncerOnTouch_actionUp_doesNotHideAlternateBouncer() { + reset(mAlternateBouncerInteractor); + // GIVEN the alternate bouncer has shown for a minimum amount of time when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); + when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - // WHEN ACTION_UP touch event comes - boolean touchHandled = mStatusBarKeyguardViewManager.onTouch( + // WHEN only ACTION_UP touch event comes + mStatusBarKeyguardViewManager.onTouch( MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - // THEN the touch is handled - assertTrue(touchHandled); + // THEN the alternateBouncer doesn't hide + verify(mAlternateBouncerInteractor, never()).hide(); } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java index 481d453fa0b1..c8f28bc17926 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingControllerTest.java @@ -55,6 +55,9 @@ import org.mockito.MockitoAnnotations; public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase { private static final String[] DEFAULT_SETTINGS = new String[]{"0:1", "2:0:1", "1:2"}; + private static final int[] DEFAULT_FOLDED_STATES = new int[]{0}; + private static final int[] DEFAULT_HALF_FOLDED_STATES = new int[]{2}; + private static final int[] DEFAULT_UNFOLDED_STATES = new int[]{1}; @Mock private DeviceStateManager mDeviceStateManager; @Mock private DeviceStateRotationLockSettingControllerLogger mLogger; @@ -73,6 +76,9 @@ public class DeviceStateRotationLockSettingControllerTest extends SysuiTestCase MockitoAnnotations.initMocks(/* testClass= */ this); TestableResources resources = mContext.getOrCreateTestableResources(); resources.addOverride(R.array.config_perDeviceStateRotationLockDefaults, DEFAULT_SETTINGS); + resources.addOverride(R.array.config_foldedDeviceStates, DEFAULT_FOLDED_STATES); + resources.addOverride(R.array.config_halfFoldedDeviceStates, DEFAULT_HALF_FOLDED_STATES); + resources.addOverride(R.array.config_openDeviceStates, DEFAULT_UNFOLDED_STATES); ArgumentCaptor<DeviceStateManager.DeviceStateCallback> deviceStateCallbackArgumentCaptor = ArgumentCaptor.forClass(DeviceStateManager.DeviceStateCallback.class); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java index b1950eac9846..692af6a9a37b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wallet/ui/WalletScreenControllerTest.java @@ -22,6 +22,9 @@ import static android.view.View.VISIBLE; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -63,6 +66,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; import java.util.Collections; +import java.util.List; @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper @@ -99,6 +103,8 @@ public class WalletScreenControllerTest extends SysuiTestCase { ArgumentCaptor<PendingIntent> mIntentCaptor; @Captor ArgumentCaptor<QuickAccessWalletClient.OnWalletCardsRetrievedCallback> mCallbackCaptor; + @Captor + ArgumentCaptor<List<WalletCardViewInfo>> mPaymentCardDataCaptor; private WalletScreenController mController; private TestableLooper mTestableLooper; @@ -107,7 +113,7 @@ public class WalletScreenControllerTest extends SysuiTestCase { MockitoAnnotations.initMocks(this); mTestableLooper = TestableLooper.get(this); when(mUserTracker.getUserContext()).thenReturn(mContext); - mWalletView = new WalletView(mContext); + mWalletView = spy(new WalletView(mContext)); mWalletView.getCardCarousel().setExpectedViewWidth(CARD_CAROUSEL_WIDTH); when(mWalletClient.getLogo()).thenReturn(mWalletLogo); when(mWalletClient.getShortcutLongLabel()).thenReturn(SHORTCUT_LONG_LABEL); @@ -430,6 +436,41 @@ public class WalletScreenControllerTest extends SysuiTestCase { assertEquals(GONE, mWalletView.getVisibility()); } + @Test + public void onWalletCardsRetrieved_cardDataAllUnknown_showsAllCards() { + List<WalletCard> walletCardList = List.of( + createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN), + createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN), + createWalletCardWithType(mContext, WalletCard.CARD_TYPE_UNKNOWN)); + GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0); + mController.onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + verify(mWalletView).showCardCarousel(mPaymentCardDataCaptor.capture(), anyInt(), + anyBoolean(), + anyBoolean()); + List<WalletCardViewInfo> paymentCardData = mPaymentCardDataCaptor.getValue(); + assertEquals(paymentCardData.size(), walletCardList.size()); + } + + @Test + public void onWalletCardsRetrieved_cardDataDifferentTypes_onlyShowsPayment() { + List<WalletCard> walletCardList = List.of(createWalletCardWithType(mContext, + WalletCard.CARD_TYPE_UNKNOWN), + createWalletCardWithType(mContext, WalletCard.CARD_TYPE_PAYMENT), + createWalletCardWithType(mContext, WalletCard.CARD_TYPE_NON_PAYMENT) + ); + GetWalletCardsResponse response = new GetWalletCardsResponse(walletCardList, 0); + mController.onWalletCardsRetrieved(response); + mTestableLooper.processAllMessages(); + + verify(mWalletView).showCardCarousel(mPaymentCardDataCaptor.capture(), anyInt(), + anyBoolean(), + anyBoolean()); + List<WalletCardViewInfo> paymentCardData = mPaymentCardDataCaptor.getValue(); + assertEquals(paymentCardData.size(), 1); + } + private WalletCard createNonActiveWalletCard(Context context) { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); @@ -457,6 +498,15 @@ public class WalletScreenControllerTest extends SysuiTestCase { .build(); } + private WalletCard createWalletCardWithType(Context context, int cardType) { + PendingIntent pendingIntent = + PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); + return new WalletCard.Builder(CARD_ID_1, cardType, createIcon(), "•••• 1234", pendingIntent) + .setCardIcon(createIcon()) + .setCardLabel("Hold to reader") + .build(); + } + private WalletCard createCrazyWalletCard(Context context, boolean hasLabel) { PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, mWalletIntent, PendingIntent.FLAG_IMMUTABLE); diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index d422f9a1ab63..0edb8f27d413 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2263,6 +2263,15 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } if (userState.mEnabledServices.contains(componentName) && !mUiAutomationManager.suppressingAccessibilityServicesLocked()) { + // Skip the enabling service disallowed by device admin policy. + if (!isAccessibilityTargetAllowed(componentName.getPackageName(), + installedService.getResolveInfo().serviceInfo.applicationInfo.uid, + userState.mUserId)) { + Slog.d(LOG_TAG, "Skipping enabling service disallowed by device admin policy: " + + componentName); + disableAccessibilityServiceLocked(componentName, userState.mUserId); + continue; + } if (service == null) { service = new AccessibilityServiceConnection(userState, mContext, componentName, installedService, sIdCounter++, mMainHandler, mLock, mSecurityPolicy, @@ -3875,32 +3884,29 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub } } - @Override - @RequiresPermission(anyOf = { - android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.QUERY_ADMIN_POLICY}) public boolean isAccessibilityTargetAllowed(String packageName, int uid, int userId) { - final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); - final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId); - - // permittedServices null means all accessibility services are allowed. - boolean allowed = permittedServices == null || permittedServices.contains(packageName); - if (allowed) { - final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); - final int mode = appOps.noteOpNoThrow( - AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, - uid, packageName, /* attributionTag= */ null, /* message= */ null); - final boolean ecmEnabled = mContext.getResources().getBoolean( - R.bool.config_enhancedConfirmationModeEnabled); - return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; + final long identity = Binder.clearCallingIdentity(); + try { + final DevicePolicyManager dpm = mContext.getSystemService(DevicePolicyManager.class); + final List<String> permittedServices = dpm.getPermittedAccessibilityServices(userId); + + // permittedServices null means all accessibility services are allowed. + boolean allowed = permittedServices == null || permittedServices.contains(packageName); + if (allowed) { + final AppOpsManager appOps = mContext.getSystemService(AppOpsManager.class); + final int mode = appOps.noteOpNoThrow( + AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + uid, packageName, /* attributionTag= */ null, /* message= */ null); + final boolean ecmEnabled = mContext.getResources().getBoolean( + R.bool.config_enhancedConfirmationModeEnabled); + return !ecmEnabled || mode == AppOpsManager.MODE_ALLOWED; + } + return false; + } finally { + Binder.restoreCallingIdentity(identity); } - return false; } - @Override - @RequiresPermission(anyOf = { - android.Manifest.permission.MANAGE_USERS, - android.Manifest.permission.QUERY_ADMIN_POLICY}) public boolean sendRestrictedDialogIntent(String packageName, int uid, int userId) { // The accessibility service is allowed. Don't show the restricted dialog. if (isAccessibilityTargetAllowed(packageName, uid, userId)) { diff --git a/services/core/java/android/app/usage/UsageStatsManagerInternal.java b/services/core/java/android/app/usage/UsageStatsManagerInternal.java index 70eeb7fecc8f..fc565111dbe8 100644 --- a/services/core/java/android/app/usage/UsageStatsManagerInternal.java +++ b/services/core/java/android/app/usage/UsageStatsManagerInternal.java @@ -203,6 +203,16 @@ public abstract class UsageStatsManagerInternal { public abstract void setActiveAdminApps(Set<String> adminApps, int userId); /** + * Called by DevicePolicyManagerService to inform about the protected packages for a user. + * User control will be disabled for protected packages. + * + * @param packageNames the set of protected packages for {@code userId}. + * @param userId the userId to which the protected packages belong. + */ + public abstract void setAdminProtectedPackages(@Nullable Set<String> packageNames, + @UserIdInt int userId); + + /** * Called by DevicePolicyManagerService during boot to inform that admin data is loaded and * pushed to UsageStatsService. */ diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java index a3dc21e70281..55069b779a37 100644 --- a/services/core/java/com/android/server/DropBoxManagerService.java +++ b/services/core/java/com/android/server/DropBoxManagerService.java @@ -311,14 +311,10 @@ public final class DropBoxManagerService extends SystemService { extrasMerger.setMergeStrategy(DropBoxManager.EXTRA_DROPPED_COUNT, BundleMerger.STRATEGY_NUMBER_INCREMENT_FIRST_AND_ADD); - final String tag = intent.getStringExtra(DropBoxManager.EXTRA_TAG); - final IntentFilter matchingFilter = new IntentFilter( - DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED); - matchingFilter.addExtra(DropBoxManager.EXTRA_TAG, tag); - return BroadcastOptions.makeBasic() .setDeliveryGroupPolicy(BroadcastOptions.DELIVERY_GROUP_POLICY_MERGED) - .setDeliveryGroupMatchingFilter(matchingFilter) + .setDeliveryGroupMatchingKey(DropBoxManager.ACTION_DROPBOX_ENTRY_ADDED, + intent.getStringExtra(DropBoxManager.EXTRA_TAG)) .setDeliveryGroupExtrasMerger(extrasMerger) .setDeferralPolicy(BroadcastOptions.DEFERRAL_POLICY_UNTIL_ACTIVE) .toBundle(); diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 755019684da6..b4e75e193b65 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -9351,7 +9351,9 @@ public class ActivityManagerService extends IActivityManager.Stub String logcatSetting = Settings.Global.ERROR_LOGCAT_PREFIX + dropboxTag; String maxBytesSetting = Settings.Global.MAX_ERROR_BYTES_PREFIX + dropboxTag; - int lines = Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0); + int lines = Build.IS_USER + ? 0 + : Settings.Global.getInt(mContext.getContentResolver(), logcatSetting, 0); int dropboxMaxSize = Settings.Global.getInt( mContext.getContentResolver(), maxBytesSetting, DROPBOX_DEFAULT_MAX_SIZE); int maxDataFileSize = dropboxMaxSize - sb.length() diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java index 94d08bf7b59d..72e17d8764aa 100644 --- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java +++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java @@ -1066,16 +1066,26 @@ final class ActivityManagerShellCommand extends ShellCommand { } @NeverCompile - int runCompact(PrintWriter pw) { + int runCompact(PrintWriter pw) throws RemoteException { ProcessRecord app; String op = getNextArgRequired(); boolean isFullCompact = op.equals("full"); boolean isSomeCompact = op.equals("some"); if (isFullCompact || isSomeCompact) { String processName = getNextArgRequired(); - String uid = getNextArgRequired(); synchronized (mInternal.mProcLock) { - app = mInternal.getProcessRecordLocked(processName, Integer.parseInt(uid)); + // Default to current user + int userId = mInterface.getCurrentUserId(); + String userOpt = getNextOption(); + if (userOpt != null && "--user".equals(userOpt)) { + int inputUserId = UserHandle.parseUserArg(getNextArgRequired()); + if (inputUserId != UserHandle.USER_CURRENT) { + userId = inputUserId; + } + } + final int uid = + mInternal.getPackageManagerInternal().getPackageUid(processName, 0, userId); + app = mInternal.getProcessRecordLocked(processName, uid); } pw.println("Process record found pid: " + app.mPid); if (isFullCompact) { @@ -1101,6 +1111,28 @@ final class ActivityManagerShellCommand extends ShellCommand { mInternal.mOomAdjuster.mCachedAppOptimizer.compactAllSystem(); } pw.println("Finished system compaction"); + } else if (op.equals("native")) { + op = getNextArgRequired(); + isFullCompact = op.equals("full"); + isSomeCompact = op.equals("some"); + int pid; + String pidStr = getNextArgRequired(); + try { + pid = Integer.parseInt(pidStr); + } catch (Exception e) { + getErrPrintWriter().println("Error: failed to parse '" + pidStr + "' as a PID"); + return -1; + } + if (isFullCompact) { + mInternal.mOomAdjuster.mCachedAppOptimizer.compactNative( + CachedAppOptimizer.CompactProfile.FULL, pid); + } else if (isSomeCompact) { + mInternal.mOomAdjuster.mCachedAppOptimizer.compactNative( + CachedAppOptimizer.CompactProfile.SOME, pid); + } else { + getErrPrintWriter().println("Error: unknown compaction type '" + op + "'"); + return -1; + } } else { getErrPrintWriter().println("Error: unknown compact command '" + op + "'"); return -1; @@ -4018,11 +4050,17 @@ final class ActivityManagerShellCommand extends ShellCommand { pw.println(" --allow-background-activity-starts: The receiver may start activities"); pw.println(" even if in the background."); pw.println(" --async: Send without waiting for the completion of the receiver."); - pw.println(" compact [some|full|system] <process_name> <Package UID>"); - pw.println(" Force process compaction."); + pw.println(" compact [some|full] <process_name> [--user <USER_ID>]"); + pw.println(" Perform a single process compaction."); pw.println(" some: execute file compaction."); pw.println(" full: execute anon + file compaction."); pw.println(" system: system compaction."); + pw.println(" compact system"); + pw.println(" Perform a full system compaction."); + pw.println(" compact native [some|full] <pid>"); + pw.println(" Perform a native compaction for process with <pid>."); + pw.println(" some: execute file compaction."); + pw.println(" full: execute anon + file compaction."); pw.println(" instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]"); pw.println(" [--user <USER_ID> | current]"); pw.println(" [--no-hidden-api-checks [--no-test-api-access]]"); diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java index d09ca5cadfe7..7c84b7230816 100644 --- a/services/core/java/com/android/server/am/BatteryStatsService.java +++ b/services/core/java/com/android/server/am/BatteryStatsService.java @@ -843,7 +843,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub final long sessionStart = mBatteryUsageStatsStore .getLastBatteryUsageStatsBeforeResetAtomPullTimestamp(); - final long sessionEnd = mStats.getStartClockTime(); + final long sessionEnd; + synchronized (mStats) { + sessionEnd = mStats.getStartClockTime(); + } final BatteryUsageStatsQuery queryBeforeReset = new BatteryUsageStatsQuery.Builder() .setMaxStatsAgeMs(0) diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index f4685f0f6125..8675bfd9b2cc 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -66,8 +66,6 @@ public final class CachedAppOptimizer { // Flags stored in the DeviceConfig API. @VisibleForTesting static final String KEY_USE_COMPACTION = "use_compaction"; @VisibleForTesting static final String KEY_USE_FREEZER = "use_freezer"; - @VisibleForTesting static final String KEY_COMPACT_ACTION_1 = "compact_action_1"; - @VisibleForTesting static final String KEY_COMPACT_ACTION_2 = "compact_action_2"; @VisibleForTesting static final String KEY_COMPACT_THROTTLE_1 = "compact_throttle_1"; @VisibleForTesting static final String KEY_COMPACT_THROTTLE_2 = "compact_throttle_2"; @VisibleForTesting static final String KEY_COMPACT_THROTTLE_3 = "compact_throttle_3"; @@ -99,15 +97,6 @@ public final class CachedAppOptimizer { private static final int RSS_ANON_INDEX = 2; private static final int RSS_SWAP_INDEX = 3; - // Phenotype sends int configurations and we map them to the strings we'll use on device, - // preventing a weird string value entering the kernel. - private static final int COMPACT_ACTION_NONE = 0; - private static final int COMPACT_ACTION_FILE = 1; - private static final int COMPACT_ACTION_ANON = 2; - private static final int COMPACT_ACTION_ALL = 3; - - private static final String COMPACT_ACTION_STRING[] = {"", "file", "anon", "all"}; - // Keeps these flags in sync with services/core/jni/com_android_server_am_CachedAppOptimizer.cpp private static final int COMPACT_ACTION_FILE_FLAG = 1; private static final int COMPACT_ACTION_ANON_FLAG = 2; @@ -117,11 +106,11 @@ public final class CachedAppOptimizer { private static final int FREEZE_BINDER_TIMEOUT_MS = 100; + @VisibleForTesting static final boolean ENABLE_FILE_COMPACT = false; + // Defaults for phenotype flags. @VisibleForTesting static final Boolean DEFAULT_USE_COMPACTION = true; @VisibleForTesting static final Boolean DEFAULT_USE_FREEZER = true; - @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_2 = COMPACT_ACTION_ALL; - @VisibleForTesting static final int DEFAULT_COMPACT_ACTION_1 = COMPACT_ACTION_FILE; @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_1 = 5_000; @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_2 = 10_000; @VisibleForTesting static final long DEFAULT_COMPACT_THROTTLE_3 = 500; @@ -156,24 +145,17 @@ public final class CachedAppOptimizer { @VisibleForTesting interface ProcessDependencies { long[] getRss(int pid); - void performCompaction(CompactAction action, int pid) throws IOException; + void performCompaction(CompactProfile action, int pid) throws IOException; } // This indicates the compaction we want to perform public enum CompactProfile { + NONE, // No compaction SOME, // File compaction + ANON, // Anon compaction FULL // File+anon compaction } - // Low level actions that can be performed for compaction - // currently determined by the compaction profile - public enum CompactAction { - NONE, // No compaction - FILE, // File+anon compaction - ANON, - ALL - } - // This indicates the process OOM memory state that initiated the compaction request public enum CompactSource { APP, PERSISTENT, BFGS } @@ -187,6 +169,7 @@ public final class CachedAppOptimizer { static final int COMPACT_SYSTEM_MSG = 2; static final int SET_FROZEN_PROCESS_MSG = 3; static final int REPORT_UNFREEZE_MSG = 4; + static final int COMPACT_NATIVE_MSG = 5; // When free swap falls below this percentage threshold any full (file + anon) // compactions will be downgraded to file only compactions to reduce pressure @@ -240,9 +223,6 @@ public final class CachedAppOptimizer { for (String name : properties.getKeyset()) { if (KEY_USE_COMPACTION.equals(name)) { updateUseCompaction(); - } else if (KEY_COMPACT_ACTION_1.equals(name) - || KEY_COMPACT_ACTION_2.equals(name)) { - updateCompactionActions(); } else if (KEY_COMPACT_THROTTLE_1.equals(name) || KEY_COMPACT_THROTTLE_2.equals(name) || KEY_COMPACT_THROTTLE_3.equals(name) @@ -314,12 +294,6 @@ public final class CachedAppOptimizer { // Configured by phenotype. Updates from the server take effect immediately. @GuardedBy("mPhenotypeFlagLock") - @VisibleForTesting - volatile CompactAction mCompactActionSome = compactActionIntToAction(DEFAULT_COMPACT_ACTION_1); - @GuardedBy("mPhenotypeFlagLock") - @VisibleForTesting - volatile CompactAction mCompactActionFull = compactActionIntToAction(DEFAULT_COMPACT_ACTION_2); - @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting volatile long mCompactThrottleSomeSome = DEFAULT_COMPACT_THROTTLE_1; @GuardedBy("mPhenotypeFlagLock") @VisibleForTesting volatile long mCompactThrottleSomeFull = DEFAULT_COMPACT_THROTTLE_2; @@ -542,7 +516,6 @@ public final class CachedAppOptimizer { CACHED_APP_FREEZER_ENABLED_URI, false, mSettingsObserver); synchronized (mPhenotypeFlagLock) { updateUseCompaction(); - updateCompactionActions(); updateCompactionThrottles(); updateCompactStatsdSampleRate(); updateFreezerStatsdSampleRate(); @@ -587,8 +560,6 @@ public final class CachedAppOptimizer { pw.println("CachedAppOptimizer settings"); synchronized (mPhenotypeFlagLock) { pw.println(" " + KEY_USE_COMPACTION + "=" + mUseCompaction); - pw.println(" " + KEY_COMPACT_ACTION_1 + "=" + mCompactActionSome); - pw.println(" " + KEY_COMPACT_ACTION_2 + "=" + mCompactActionFull); pw.println(" " + KEY_COMPACT_THROTTLE_1 + "=" + mCompactThrottleSomeSome); pw.println(" " + KEY_COMPACT_THROTTLE_2 + "=" + mCompactThrottleSomeFull); pw.println(" " + KEY_COMPACT_THROTTLE_3 + "=" + mCompactThrottleFullSome); @@ -761,19 +732,9 @@ public final class CachedAppOptimizer { return false; } - private CompactAction resolveCompactActionForProfile(CompactProfile profile) { - CompactAction action; - switch (profile) { - case SOME: - action = CompactAction.FILE; - break; - case FULL: - action = CompactAction.ALL; - break; - default: - action = CompactAction.NONE; - } - return action; + void compactNative(CompactProfile compactProfile, int pid) { + mCompactionHandler.sendMessage(mCompactionHandler.obtainMessage( + COMPACT_NATIVE_MSG, pid, compactProfile.ordinal())); } private AggregatedProcessCompactionStats getPerProcessAggregatedCompactStat( @@ -1051,18 +1012,6 @@ public final class CachedAppOptimizer { } @GuardedBy("mPhenotypeFlagLock") - private void updateCompactionActions() { - int compactAction1 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - KEY_COMPACT_ACTION_1, DEFAULT_COMPACT_ACTION_1); - - int compactAction2 = DeviceConfig.getInt(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - KEY_COMPACT_ACTION_2, DEFAULT_COMPACT_ACTION_2); - - mCompactActionSome = compactActionIntToAction(compactAction1); - mCompactActionFull = compactActionIntToAction(compactAction2); - } - - @GuardedBy("mPhenotypeFlagLock") private void updateCompactionThrottles() { boolean useThrottleDefaults = false; // TODO: improve efficiency by calling DeviceConfig only once for all flags. @@ -1235,14 +1184,6 @@ public final class CachedAppOptimizer { return true; } - static CompactAction compactActionIntToAction(int action) { - if (action < 0 || action >= CompactAction.values().length) { - return CompactAction.NONE; - } - - return CompactAction.values()[action]; - } - // This will ensure app will be out of the freezer for at least mFreezerDebounceTimeout. @GuardedBy("mAm") void unfreezeTemporarily(ProcessRecord app, @OomAdjuster.OomAdjReason int reason) { @@ -1475,8 +1416,10 @@ public final class CachedAppOptimizer { 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 - compactApp(app, CompactProfile.SOME, CompactSource.APP, false); + if (ENABLE_FILE_COMPACT) { + // Perform a minor compaction when a perceptible app becomes the prev/home app + compactApp(app, CompactProfile.SOME, CompactSource.APP, false); + } } else if (oldAdj < ProcessList.CACHED_APP_MIN_ADJ && newAdj >= ProcessList.CACHED_APP_MIN_ADJ && newAdj <= ProcessList.CACHED_APP_MAX_ADJ) { @@ -1486,23 +1429,37 @@ public final class CachedAppOptimizer { } /** - * Applies a compaction downgrade when swap is low. + * Computes the final compaction profile to be used which depends on compaction + * features enabled and swap usage. */ - CompactProfile downgradeCompactionIfRequired(CompactProfile profile) { - // Downgrade compaction under swap memory pressure + CompactProfile resolveCompactionProfile(CompactProfile profile) { if (profile == CompactProfile.FULL) { double swapFreePercent = getFreeSwapPercent(); + // Downgrade compaction under swap memory pressure if (swapFreePercent < COMPACT_DOWNGRADE_FREE_SWAP_THRESHOLD) { profile = CompactProfile.SOME; + ++mTotalCompactionDowngrades; if (DEBUG_COMPACTION) { Slog.d(TAG_AM, - "Downgraded compaction to file only due to low swap." + "Downgraded compaction to "+ profile +" due to low swap." + " Swap Free% " + swapFreePercent); } } } + if (!ENABLE_FILE_COMPACT) { + if (profile == CompactProfile.SOME) { + profile = CompactProfile.NONE; + } else if (profile == CompactProfile.FULL) { + profile = CompactProfile.ANON; + } + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, + "Final compaction profile "+ profile +" due to file compact disabled"); + } + } + return profile; } @@ -1733,7 +1690,6 @@ public final class CachedAppOptimizer { ProcessRecord proc; final ProcessCachedOptimizerRecord opt; int pid; - CompactAction resolvedAction; final String name; CompactProfile lastCompactProfile; long lastCompactTime; @@ -1811,17 +1767,24 @@ public final class CachedAppOptimizer { } CompactProfile resolvedProfile = - downgradeCompactionIfRequired(requestedProfile); - resolvedAction = resolveCompactActionForProfile(resolvedProfile); + resolveCompactionProfile(requestedProfile); + if (resolvedProfile == CompactProfile.NONE) { + // No point on issuing compaction call as we don't want to compact. + if (DEBUG_COMPACTION) { + Slog.d(TAG_AM, "Resolved no compaction for "+ name + + " requested profile="+requestedProfile); + } + return; + } try { Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, - "Compact " + resolvedAction.name() + ": " + name + "Compact " + resolvedProfile.name() + ": " + name + " lastOomAdjReason: " + oomAdjReason + " source: " + compactSource.name()); long zramUsedKbBefore = getUsedZramMemory(); long startCpuTime = threadCpuTimeNs(); - mProcessDependencies.performCompaction(resolvedAction, pid); + mProcessDependencies.performCompaction(resolvedProfile, pid); long endCpuTime = threadCpuTimeNs(); long[] rssAfter = mProcessDependencies.getRss(pid); long end = SystemClock.uptimeMillis(); @@ -1877,7 +1840,7 @@ public final class CachedAppOptimizer { return; } EventLog.writeEvent(EventLogTags.AM_COMPACT, pid, name, - resolvedAction.name(), rssBefore[RSS_TOTAL_INDEX], + resolvedProfile.name(), rssBefore[RSS_TOTAL_INDEX], rssBefore[RSS_FILE_INDEX], rssBefore[RSS_ANON_INDEX], rssBefore[RSS_SWAP_INDEX], deltaTotalRss, deltaFileRss, deltaAnonRss, deltaSwapRss, time, lastCompactProfile.name(), @@ -1907,6 +1870,21 @@ public final class CachedAppOptimizer { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); break; } + case COMPACT_NATIVE_MSG: { + int pid = msg.arg1; + CompactProfile compactProfile = CompactProfile.values()[msg.arg2]; + Slog.d(TAG_AM, + "Performing native compaction for pid=" + pid + + " type=" + compactProfile.name()); + Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "compactSystem"); + try { + mProcessDependencies.performCompaction(compactProfile, pid); + } catch (Exception e) { + Slog.d(TAG_AM, "Failed compacting native pid= " + pid); + } + Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); + break; + } } } } @@ -2170,14 +2148,14 @@ public final class CachedAppOptimizer { // Compact process. @Override - public void performCompaction(CompactAction action, int pid) throws IOException { + public void performCompaction(CompactProfile profile, int pid) throws IOException { mPidCompacting = pid; - if (action == CompactAction.ALL) { - compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG); - } else if (action == CompactAction.FILE) { - compactProcess(pid, COMPACT_ACTION_FILE_FLAG); - } else if (action == CompactAction.ANON) { - compactProcess(pid, COMPACT_ACTION_ANON_FLAG); + if (profile == CompactProfile.FULL) { + compactProcess(pid, COMPACT_ACTION_FILE_FLAG | COMPACT_ACTION_ANON_FLAG); + } else if (profile == CompactProfile.SOME) { + compactProcess(pid, COMPACT_ACTION_FILE_FLAG); + } else if (profile == CompactProfile.ANON) { + compactProcess(pid, COMPACT_ACTION_ANON_FLAG); } mPidCompacting = -1; } diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java index 58a47d7b1dc4..83caf0f6048a 100644 --- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java +++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java @@ -459,7 +459,10 @@ class ProcessErrorStateRecord { // don't dump native PIDs for background ANRs unless // it is the process of interest String[] nativeProcs = null; - if (isSilentAnr || onlyDumpSelf) { + boolean isSystemApp = mApp.info.isSystemApp() || mApp.info.isSystemExt(); + // Do not collect system daemons dumps as this is not likely to be useful + // for non-system apps. + if (!isSystemApp || isSilentAnr || onlyDumpSelf) { for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) { if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) { nativeProcs = new String[] { mApp.processName }; diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index aae1d3884344..6758581d1fc0 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -597,7 +597,13 @@ public class AudioDeviceInventory { if (wdcs.mState == AudioService.CONNECTION_STATE_DISCONNECTED && AudioSystem.DEVICE_OUT_ALL_USB_SET.contains( wdcs.mAttributes.getInternalType())) { - mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved(info); + if (info != null) { + mDeviceBroker.dispatchPreferredMixerAttributesChangedCausedByDeviceRemoved( + info); + } else { + Log.e(TAG, "Didn't find AudioDeviceInfo to notify preferred mixer " + + "attributes change for type=" + wdcs.mAttributes.getType()); + } } sendDeviceConnectionIntent(type, wdcs.mState, wdcs.mAttributes.getAddress(), wdcs.mAttributes.getName()); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index 3e86c454fae8..127a9d8b41ee 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -1745,7 +1745,9 @@ public class AudioService extends IAudioService.Stub mSoundDoseHelper.reset(); // Restore rotation information. - RotationHelper.forceUpdate(); + if (mMonitorRotation) { + RotationHelper.forceUpdate(); + } onIndicateSystemReady(); // indicate the end of reconfiguration phase to audio HAL @@ -7235,7 +7237,7 @@ public class AudioService extends IAudioService.Stub super.setDeviceVolumeBehavior_enforcePermission(); // verify arguments Objects.requireNonNull(device); - AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior); + AudioManager.enforceSettableVolumeBehavior(deviceVolumeBehavior); sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:" + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:" @@ -10471,16 +10473,16 @@ public class AudioService extends IAudioService.Stub @Override @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) - public float getRs2Value() { - super.getRs2Value_enforcePermission(); - return mSoundDoseHelper.getRs2Value(); + public float getOutputRs2UpperBound() { + super.getOutputRs2UpperBound_enforcePermission(); + return mSoundDoseHelper.getOutputRs2UpperBound(); } @Override @android.annotation.EnforcePermission(android.Manifest.permission.MODIFY_AUDIO_SETTINGS_PRIVILEGED) - public void setRs2Value(float rs2Value) { - super.setRs2Value_enforcePermission(); - mSoundDoseHelper.setRs2Value(rs2Value); + public void setOutputRs2UpperBound(float rs2Value) { + super.setOutputRs2UpperBound_enforcePermission(); + mSoundDoseHelper.setOutputRs2UpperBound(rs2Value); } @Override diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java index c2483671a065..d6a9d3aa8ce3 100644 --- a/services/core/java/com/android/server/audio/SoundDoseHelper.java +++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java @@ -300,7 +300,7 @@ public class SoundDoseHelper { SAFE_MEDIA_VOLUME_UNINITIALIZED); } - float getRs2Value() { + float getOutputRs2UpperBound() { if (!mEnableCsd) { return 0.f; } @@ -312,14 +312,14 @@ public class SoundDoseHelper { } try { - return soundDose.getOutputRs2(); + return soundDose.getOutputRs2UpperBound(); } catch (RemoteException e) { Log.e(TAG, "Exception while getting the RS2 exposure value", e); return 0.f; } } - void setRs2Value(float rs2Value) { + void setOutputRs2UpperBound(float rs2Value) { if (!mEnableCsd) { return; } @@ -331,7 +331,7 @@ public class SoundDoseHelper { } try { - soundDose.setOutputRs2(rs2Value); + soundDose.setOutputRs2UpperBound(rs2Value); } catch (RemoteException e) { Log.e(TAG, "Exception while setting the RS2 exposure value", e); } diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java index 82444f0cc299..6bd48802baf5 100644 --- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java +++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java @@ -16,14 +16,22 @@ package com.android.server.biometrics.log; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.hardware.biometrics.BiometricsProtoEnums; import android.hardware.biometrics.IBiometricContextListener; +import android.hardware.biometrics.common.AuthenticateReason; +import android.hardware.biometrics.common.OperationContext; import android.hardware.biometrics.common.OperationReason; +import android.hardware.biometrics.common.WakeReason; import android.util.Slog; import android.view.Surface; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; +import java.util.stream.Stream; + /** * Wrapper for {@link FrameworkStatsLog} to isolate the testable parts. */ @@ -63,7 +71,7 @@ public class BiometricFrameworkStatsLogger { orientationType(operationContext.getOrientation()), foldType(operationContext.getFoldState()), operationContext.getOrderAndIncrement(), - BiometricsProtoEnums.WAKE_REASON_UNKNOWN); + toProtoWakeReason(operationContext)); } /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ @@ -89,7 +97,8 @@ public class BiometricFrameworkStatsLogger { orientationType(operationContext.getOrientation()), foldType(operationContext.getFoldState()), operationContext.getOrderAndIncrement(), - BiometricsProtoEnums.WAKE_REASON_UNKNOWN); + toProtoWakeReason(operationContext), + toProtoWakeReasonDetails(operationContext)); } /** {@see FrameworkStatsLog.BIOMETRIC_AUTHENTICATED}. */ @@ -137,7 +146,81 @@ public class BiometricFrameworkStatsLogger { orientationType(operationContext.getOrientation()), foldType(operationContext.getFoldState()), operationContext.getOrderAndIncrement(), - BiometricsProtoEnums.WAKE_REASON_UNKNOWN); + toProtoWakeReason(operationContext), + toProtoWakeReasonDetails(operationContext)); + } + + @VisibleForTesting + static int[] toProtoWakeReasonDetails(@NonNull OperationContextExt operationContext) { + final OperationContext ctx = operationContext.toAidlContext(); + return Stream.of(toProtoWakeReasonDetails(ctx.authenticateReason)) + .mapToInt(i -> i) + .filter(i -> i != BiometricsProtoEnums.DETAILS_UNKNOWN) + .toArray(); + } + + @VisibleForTesting + static int toProtoWakeReason(@NonNull OperationContextExt operationContext) { + @WakeReason final int reason = operationContext.getWakeReason(); + switch (reason) { + case WakeReason.POWER_BUTTON: + return BiometricsProtoEnums.WAKE_REASON_POWER_BUTTON; + case WakeReason.GESTURE: + return BiometricsProtoEnums.WAKE_REASON_GESTURE; + case WakeReason.WAKE_KEY: + return BiometricsProtoEnums.WAKE_REASON_WAKE_KEY; + case WakeReason.WAKE_MOTION: + return BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION; + case WakeReason.LID: + return BiometricsProtoEnums.WAKE_REASON_LID; + case WakeReason.DISPLAY_GROUP_ADDED: + return BiometricsProtoEnums.WAKE_REASON_DISPLAY_GROUP_ADDED; + case WakeReason.TAP: + return BiometricsProtoEnums.WAKE_REASON_TAP; + case WakeReason.LIFT: + return BiometricsProtoEnums.WAKE_REASON_LIFT; + case WakeReason.BIOMETRIC: + return BiometricsProtoEnums.WAKE_REASON_BIOMETRIC; + default: + return BiometricsProtoEnums.WAKE_REASON_UNKNOWN; + } + } + + private static int toProtoWakeReasonDetails(@Nullable AuthenticateReason reason) { + if (reason != null) { + switch (reason.getTag()) { + case AuthenticateReason.faceAuthenticateReason: + return toProtoWakeReasonDetailsFromFace(reason.getFaceAuthenticateReason()); + } + } + return BiometricsProtoEnums.DETAILS_UNKNOWN; + } + + private static int toProtoWakeReasonDetailsFromFace(@AuthenticateReason.Face int reason) { + switch (reason) { + case AuthenticateReason.Face.STARTED_WAKING_UP: + return BiometricsProtoEnums.DETAILS_FACE_STARTED_WAKING_UP; + case AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN: + return BiometricsProtoEnums.DETAILS_FACE_PRIMARY_BOUNCER_SHOWN; + case AuthenticateReason.Face.ASSISTANT_VISIBLE: + return BiometricsProtoEnums.DETAILS_FACE_ASSISTANT_VISIBLE; + case AuthenticateReason.Face.ALTERNATE_BIOMETRIC_BOUNCER_SHOWN: + return BiometricsProtoEnums.DETAILS_FACE_ALTERNATE_BIOMETRIC_BOUNCER_SHOWN; + case AuthenticateReason.Face.NOTIFICATION_PANEL_CLICKED: + return BiometricsProtoEnums.DETAILS_FACE_NOTIFICATION_PANEL_CLICKED; + case AuthenticateReason.Face.OCCLUDING_APP_REQUESTED: + return BiometricsProtoEnums.DETAILS_FACE_OCCLUDING_APP_REQUESTED; + case AuthenticateReason.Face.PICK_UP_GESTURE_TRIGGERED: + return BiometricsProtoEnums.DETAILS_FACE_PICK_UP_GESTURE_TRIGGERED; + case AuthenticateReason.Face.QS_EXPANDED: + return BiometricsProtoEnums.DETAILS_FACE_QS_EXPANDED; + case AuthenticateReason.Face.SWIPE_UP_ON_BOUNCER: + return BiometricsProtoEnums.DETAILS_FACE_SWIPE_UP_ON_BOUNCER; + case AuthenticateReason.Face.UDFPS_POINTER_DOWN: + return BiometricsProtoEnums.DETAILS_FACE_UDFPS_POINTER_DOWN; + default: + return BiometricsProtoEnums.DETAILS_UNKNOWN; + } } /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */ diff --git a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java index ecb7e7ca08fb..293433990cbd 100644 --- a/services/core/java/com/android/server/biometrics/log/OperationContextExt.java +++ b/services/core/java/com/android/server/biometrics/log/OperationContextExt.java @@ -188,10 +188,17 @@ public class OperationContextExt { } /** {@link OperationContext#reason}. */ + @OperationReason public byte getReason() { return mAidlContext.reason; } + /** {@link OperationContext#wakeReason}. */ + @WakeReason + public int getWakeReason() { + return mAidlContext.wakeReason; + } + /** If the screen is currently on. */ public boolean isDisplayOn() { return mIsDisplayOn; diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java index b66120d428c3..6a010424db13 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java @@ -86,8 +86,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { @Override public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig, - boolean withAudio, ITunerCallback callback, int targetSdkVersion) - throws RemoteException { + boolean withAudio, ITunerCallback callback) throws RemoteException { if (isDebugEnabled()) { Slogf.d(TAG, "Opening module %d", moduleId); } @@ -95,7 +94,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } - return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback, targetSdkVersion); + return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback); } @Override diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java index 8a1ba19e24ab..408fba1bff3b 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java @@ -92,8 +92,7 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { @Override public ITuner openTuner(int moduleId, RadioManager.BandConfig bandConfig, - boolean withAudio, ITunerCallback callback, int targetSdkVersion) - throws RemoteException { + boolean withAudio, ITunerCallback callback) throws RemoteException { if (isDebugEnabled()) { Slog.d(TAG, "Opening module " + moduleId); } diff --git a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java index 772cd4144f11..03acf72725e7 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/BroadcastRadioServiceImpl.java @@ -199,8 +199,7 @@ public final class BroadcastRadioServiceImpl { */ @Nullable public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, - boolean withAudio, ITunerCallback callback, int targetSdkVersion) - throws RemoteException { + boolean withAudio, ITunerCallback callback) throws RemoteException { if (DEBUG) { Slogf.d(TAG, "Open AIDL radio session"); } @@ -223,7 +222,7 @@ public final class BroadcastRadioServiceImpl { } } - TunerSession tunerSession = radioModule.openSession(callback, targetSdkVersion); + TunerSession tunerSession = radioModule.openSession(callback); if (legacyConfig != null) { tunerSession.setConfiguration(legacyConfig); } diff --git a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java index c0a238feb9b5..4f2bfd1c1e52 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/ConversionUtils.java @@ -17,6 +17,10 @@ package com.android.server.broadcastradio.aidl; import android.annotation.Nullable; +import android.annotation.SuppressLint; +import android.app.compat.CompatChanges; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.hardware.broadcastradio.AmFmRegionConfig; import android.hardware.broadcastradio.Announcement; import android.hardware.broadcastradio.DabTableEntry; @@ -57,16 +61,24 @@ import java.util.Set; * {@link android.hardware.radio} */ final class ConversionUtils { - // TODO(b/241118988): Add unit test for ConversionUtils class private static final String TAG = "BcRadioAidlSrv.convert"; + /** + * With RADIO_U_VERSION_REQUIRED enabled, 44-bit DAB identifier + * {@link IdentifierType#DAB_SID_EXT} from broadcast radio HAL can be passed as + * {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} to {@link RadioTuner}. + */ + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + public static final long RADIO_U_VERSION_REQUIRED = 261770108L; + private ConversionUtils() { throw new UnsupportedOperationException("ConversionUtils class is noninstantiable"); } - static boolean isAtLeastU(int targetSdkVersion) { - // TODO(b/261770108): Use version code for U. - return targetSdkVersion >= Build.VERSION_CODES.CUR_DEVELOPMENT; + @SuppressLint("AndroidFrameworkRequiresPermission") + static boolean isAtLeastU(int uid) { + return CompatChanges.isChangeEnabled(RADIO_U_VERSION_REQUIRED, uid); } static RuntimeException throwOnError(RuntimeException halException, String action) { @@ -584,9 +596,8 @@ final class ConversionUtils { return id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT; } - static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, - int targetSdkVersion) { - if (isAtLeastU(targetSdkVersion)) { + static boolean programSelectorMeetsSdkVersionRequirement(ProgramSelector sel, int uid) { + if (isAtLeastU(uid)) { return true; } if (sel.getPrimaryId().getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) { @@ -601,12 +612,11 @@ final class ConversionUtils { return true; } - static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, - int targetSdkVersion) { - if (isAtLeastU(targetSdkVersion)) { + static boolean programInfoMeetsSdkVersionRequirement(RadioManager.ProgramInfo info, int uid) { + if (isAtLeastU(uid)) { return true; } - if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), targetSdkVersion)) { + if (!programSelectorMeetsSdkVersionRequirement(info.getSelector(), uid)) { return false; } if (isNewIdentifierInU(info.getLogicallyTunedTo()) @@ -622,16 +632,15 @@ final class ConversionUtils { return true; } - static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, - int targetSdkVersion) { - if (isAtLeastU(targetSdkVersion)) { + static ProgramList.Chunk convertChunkToTargetSdkVersion(ProgramList.Chunk chunk, int uid) { + if (isAtLeastU(uid)) { return chunk; } Set<RadioManager.ProgramInfo> modified = new ArraySet<>(); Iterator<RadioManager.ProgramInfo> modifiedIterator = chunk.getModified().iterator(); while (modifiedIterator.hasNext()) { RadioManager.ProgramInfo info = modifiedIterator.next(); - if (programInfoMeetsSdkVersionRequirement(info, targetSdkVersion)) { + if (programInfoMeetsSdkVersionRequirement(info, uid)) { modified.add(info); } } diff --git a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java index f8c19ae2b8f1..132fb8ef8a95 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/RadioModule.java @@ -101,9 +101,9 @@ final class RadioModule { ConversionUtils.programSelectorFromHalProgramSelector(programSelector); int tunerResult = ConversionUtils.halResultToTunerResult(result); synchronized (mLock) { - fanoutAidlCallbackLocked((cb, sdkVersion) -> { + fanoutAidlCallbackLocked((cb, uid) -> { if (csel != null && !ConversionUtils - .programSelectorMeetsSdkVersionRequirement(csel, sdkVersion)) { + .programSelectorMeetsSdkVersionRequirement(csel, uid)) { Slogf.e(TAG, "onTuneFailed: cannot send program selector " + "requiring higher target SDK version"); return; @@ -123,9 +123,9 @@ final class RadioModule { "Program info from AIDL HAL is invalid"); synchronized (mLock) { mCurrentProgramInfo = currentProgramInfo; - fanoutAidlCallbackLocked((cb, sdkVersion) -> { + fanoutAidlCallbackLocked((cb, uid) -> { if (!ConversionUtils.programInfoMeetsSdkVersionRequirement( - currentProgramInfo, sdkVersion)) { + currentProgramInfo, uid)) { Slogf.e(TAG, "onCurrentProgramInfoChanged: cannot send " + "program info requiring higher target SDK version"); return; @@ -156,7 +156,7 @@ final class RadioModule { fireLater(() -> { synchronized (mLock) { mAntennaConnected = connected; - fanoutAidlCallbackLocked((cb, sdkVersion) -> cb.onAntennaState(connected)); + fanoutAidlCallbackLocked((cb, uid) -> cb.onAntennaState(connected)); } }); } @@ -165,7 +165,7 @@ final class RadioModule { public void onConfigFlagUpdated(int flag, boolean value) { fireLater(() -> { synchronized (mLock) { - fanoutAidlCallbackLocked((cb, sdkVersion) -> { + fanoutAidlCallbackLocked((cb, uid) -> { cb.onConfigFlagUpdated(flag, value); }); } @@ -178,7 +178,7 @@ final class RadioModule { synchronized (mLock) { Map<String, String> cparam = ConversionUtils.vendorInfoFromHalVendorKeyValues(parameters); - fanoutAidlCallbackLocked((cb, sdkVersion) -> { + fanoutAidlCallbackLocked((cb, uid) -> { cb.onParametersUpdated(cparam); }); } @@ -244,14 +244,14 @@ final class RadioModule { mService.setTunerCallback(mHalTunerCallback); } - TunerSession openSession(android.hardware.radio.ITunerCallback userCb, int targetSdkVersion) + TunerSession openSession(android.hardware.radio.ITunerCallback userCb) throws RemoteException { mLogger.logRadioEvent("Open TunerSession"); TunerSession tunerSession; Boolean antennaConnected; RadioManager.ProgramInfo currentProgramInfo; synchronized (mLock) { - tunerSession = new TunerSession(this, mService, userCb, targetSdkVersion); + tunerSession = new TunerSession(this, mService, userCb); mAidlTunerSessions.add(tunerSession); antennaConnected = mAntennaConnected; currentProgramInfo = mCurrentProgramInfo; @@ -404,7 +404,7 @@ final class RadioModule { } interface AidlCallbackRunnable { - void run(android.hardware.radio.ITunerCallback callback, int targetSdkVersion) + void run(android.hardware.radio.ITunerCallback callback, int uid) throws RemoteException; } @@ -423,7 +423,7 @@ final class RadioModule { for (int i = 0; i < mAidlTunerSessions.size(); i++) { try { runnable.run(mAidlTunerSessions.valueAt(i).mCallback, - mAidlTunerSessions.valueAt(i).getTargetSdkVersion()); + mAidlTunerSessions.valueAt(i).getUid()); } catch (DeadObjectException ex) { // The other side died without calling close(), so just purge it from our records. Slogf.e(TAG, "Removing dead TunerSession"); diff --git a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java index fe8c23845d2a..0a3823f038fa 100644 --- a/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/aidl/TunerSession.java @@ -24,6 +24,7 @@ import android.hardware.radio.ITuner; import android.hardware.radio.ProgramList; import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; +import android.os.Binder; import android.os.RemoteException; import android.util.ArrayMap; import android.util.ArraySet; @@ -46,7 +47,7 @@ final class TunerSession extends ITuner.Stub { private final RadioLogger mLogger; private final RadioModule mModule; final android.hardware.radio.ITunerCallback mCallback; - private final int mTargetSdkVersion; + private final int mUid; private final IBroadcastRadio mService; @GuardedBy("mLock") @@ -61,11 +62,11 @@ final class TunerSession extends ITuner.Stub { private RadioManager.BandConfig mPlaceHolderConfig; TunerSession(RadioModule radioModule, IBroadcastRadio service, - android.hardware.radio.ITunerCallback callback, int targetSdkVersion) { + android.hardware.radio.ITunerCallback callback) { mModule = Objects.requireNonNull(radioModule, "radioModule cannot be null"); mService = Objects.requireNonNull(service, "service cannot be null"); mCallback = Objects.requireNonNull(callback, "callback cannot be null"); - mTargetSdkVersion = targetSdkVersion; + mUid = Binder.getCallingUid(); mLogger = new RadioLogger(TAG, TUNER_EVENT_LOGGER_QUEUE_SIZE); } @@ -130,7 +131,7 @@ final class TunerSession extends ITuner.Stub { mPlaceHolderConfig = Objects.requireNonNull(config, "config cannot be null"); } Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL AIDL"); - mModule.fanoutAidlCallback((cb, sdkVersion) -> cb.onConfigurationChanged(config)); + mModule.fanoutAidlCallback((cb, mUid) -> cb.onConfigurationChanged(config)); } @Override @@ -254,7 +255,7 @@ final class TunerSession extends ITuner.Stub { Slogf.w(TAG, "Cannot start background scan on AIDL HAL client from non-current user"); return false; } - mModule.fanoutAidlCallback((cb, sdkVersion) -> { + mModule.fanoutAidlCallback((cb, mUid) -> { cb.onBackgroundScanComplete(); }); return true; @@ -284,8 +285,8 @@ final class TunerSession extends ITuner.Stub { mModule.onTunerSessionProgramListFilterChanged(this); } - int getTargetSdkVersion() { - return mTargetSdkVersion; + int getUid() { + return mUid; } ProgramList.Filter getProgramListFilter() { @@ -323,10 +324,9 @@ final class TunerSession extends ITuner.Stub { } for (int i = 0; i < chunks.size(); i++) { try { - if (!ConversionUtils.isAtLeastU(getTargetSdkVersion())) { + if (!ConversionUtils.isAtLeastU(getUid())) { ProgramList.Chunk downgradedChunk = - ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i), - getTargetSdkVersion()); + ConversionUtils.convertChunkToTargetSdkVersion(chunks.get(i), getUid()); mCallback.onProgramListUpdated(downgradedChunk); } else { mCallback.onProgramListUpdated(chunks.get(i)); diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java index 06b45bf0fb4b..97507be96d84 100644 --- a/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java +++ b/services/core/java/com/android/server/cpu/CpuAvailabilityInfo.java @@ -21,6 +21,8 @@ import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACK import com.android.internal.util.Preconditions; +import java.util.Objects; + /** CPU availability information. */ public final class CpuAvailabilityInfo { /** Constant to indicate missing CPU availability percent. */ @@ -35,29 +37,64 @@ public final class CpuAvailabilityInfo { @CpuAvailabilityMonitoringConfig.Cpuset public final int cpuset; + /** Uptime (in milliseconds) when the data in this object was captured. */ + public final long dataTimestampUptimeMillis; + /** The latest average CPU availability percent. */ public final int latestAvgAvailabilityPercent; - /** The past N-second average CPU availability percent. */ - public final int pastNSecAvgAvailabilityPercent; + /** + * The past N-millisecond average CPU availability percent. + * + * <p>When there is not enough data to calculate the past N-millisecond average, this field will + * contain the value {@link MISSING_CPU_AVAILABILITY_PERCENT}. + */ + public final int pastNMillisAvgAvailabilityPercent; - /** The duration over which the {@link pastNSecAvgAvailabilityPercent} was calculated. */ - public final int avgAvailabilityDurationSec; + /** The duration over which the {@link pastNMillisAvgAvailabilityPercent} was calculated. */ + public final long pastNMillisDuration; @Override public String toString() { - return "CpuAvailabilityInfo{" + "cpuset=" + cpuset + ", latestAvgAvailabilityPercent=" - + latestAvgAvailabilityPercent + ", pastNSecAvgAvailabilityPercent=" - + pastNSecAvgAvailabilityPercent + ", avgAvailabilityDurationSec=" - + avgAvailabilityDurationSec + '}'; + return "CpuAvailabilityInfo{" + "cpuset = " + cpuset + ", dataTimestampUptimeMillis = " + + dataTimestampUptimeMillis + ", latestAvgAvailabilityPercent = " + + latestAvgAvailabilityPercent + ", pastNMillisAvgAvailabilityPercent = " + + pastNMillisAvgAvailabilityPercent + ", pastNMillisDuration = " + + pastNMillisDuration + '}'; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof CpuAvailabilityInfo)) { + return false; + } + CpuAvailabilityInfo info = (CpuAvailabilityInfo) obj; + return cpuset == info.cpuset && dataTimestampUptimeMillis == info.dataTimestampUptimeMillis + && latestAvgAvailabilityPercent == info.latestAvgAvailabilityPercent + && pastNMillisAvgAvailabilityPercent == info.pastNMillisAvgAvailabilityPercent + && pastNMillisDuration == info.pastNMillisDuration; + } + + @Override + public int hashCode() { + return Objects.hash(cpuset, dataTimestampUptimeMillis, latestAvgAvailabilityPercent, + pastNMillisAvgAvailabilityPercent, pastNMillisDuration); } - CpuAvailabilityInfo(int cpuset, int latestAvgAvailabilityPercent, - int pastNSecAvgAvailabilityPercent, int avgAvailabilityDurationSec) { + CpuAvailabilityInfo(int cpuset, long dataTimestampUptimeMillis, + int latestAvgAvailabilityPercent, int pastNMillisAvgAvailabilityPercent, + long pastNMillisDuration) { this.cpuset = Preconditions.checkArgumentInRange(cpuset, CPUSET_ALL, CPUSET_BACKGROUND, "cpuset"); - this.latestAvgAvailabilityPercent = latestAvgAvailabilityPercent; - this.pastNSecAvgAvailabilityPercent = pastNSecAvgAvailabilityPercent; - this.avgAvailabilityDurationSec = avgAvailabilityDurationSec; + this.dataTimestampUptimeMillis = + Preconditions.checkArgumentNonnegative(dataTimestampUptimeMillis); + this.latestAvgAvailabilityPercent = Preconditions.checkArgumentNonnegative( + latestAvgAvailabilityPercent); + this.pastNMillisAvgAvailabilityPercent = pastNMillisAvgAvailabilityPercent; + this.pastNMillisDuration = Preconditions.checkArgumentNonnegative( + pastNMillisDuration); } } diff --git a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java index a3c4c9e828b4..cbe02fc09d84 100644 --- a/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java +++ b/services/core/java/com/android/server/cpu/CpuAvailabilityMonitoringConfig.java @@ -90,8 +90,19 @@ public final class CpuAvailabilityMonitoringConfig { @Override public String toString() { - return "CpuAvailabilityMonitoringConfig{cpuset=" + cpuset + ", mThresholds=" + mThresholds - + ')'; + return "CpuAvailabilityMonitoringConfig{cpuset=" + toCpusetString(cpuset) + ", mThresholds=" + + mThresholds + ')'; + } + + /** Returns the string equivalent of the provided cpuset. */ + public static String toCpusetString(int cpuset) { + switch (cpuset) { + case CPUSET_ALL: + return "CPUSET_ALL"; + case CPUSET_BACKGROUND: + return "CPUSET_BACKGROUND"; + } + return "Invalid cpuset: " + cpuset; } private CpuAvailabilityMonitoringConfig(Builder builder) { diff --git a/services/core/java/com/android/server/cpu/CpuInfoReader.java b/services/core/java/com/android/server/cpu/CpuInfoReader.java index ca97a9847b39..ce68edbb9fa1 100644 --- a/services/core/java/com/android/server/cpu/CpuInfoReader.java +++ b/services/core/java/com/android/server/cpu/CpuInfoReader.java @@ -21,8 +21,10 @@ import static com.android.server.cpu.CpuMonitorService.TAG; import android.annotation.IntDef; import android.annotation.Nullable; +import android.os.SystemClock; import android.system.Os; import android.system.OsConstants; +import android.util.IndentingPrintWriter; import android.util.IntArray; import android.util.LongSparseLongArray; import android.util.SparseArray; @@ -50,6 +52,9 @@ public final class CpuInfoReader { private static final String POLICY_DIR_PREFIX = "policy"; private static final String RELATED_CPUS_FILE = "related_cpus"; private static final String AFFECTED_CPUS_FILE = "affected_cpus"; + // TODO(b/263154344): Avoid reading from cpuinfo_cur_freq because non-root users don't have + // read permission for this file. The file permissions are set by the Kernel. Instead, read + // the current frequency only from scaling_cur_freq. private static final String CUR_CPUFREQ_FILE = "cpuinfo_cur_freq"; private static final String MAX_CPUFREQ_FILE = "cpuinfo_max_freq"; private static final String CUR_SCALING_FREQ_FILE = "scaling_cur_freq"; @@ -70,16 +75,18 @@ public final class CpuInfoReader { private static final Pattern TIME_IN_STATE_PATTERN = Pattern.compile("(?<freqKHz>[0-9]+)\\s(?<time>[0-9]+)"); private static final long MILLIS_PER_CLOCK_TICK = 1000L / Os.sysconf(OsConstants._SC_CLK_TCK); + private static final long MIN_READ_INTERVAL_MILLISECONDS = 500; @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"FLAG_CPUSET_CATEGORY_"}, flag = true, value = { FLAG_CPUSET_CATEGORY_TOP_APP, FLAG_CPUSET_CATEGORY_BACKGROUND }) - private @interface CpusetCategory{} + /** package **/ @interface CpusetCategory{} // TODO(b/242722241): Protect updatable variables with a local lock. private final File mCpusetDir; + private final long mMinReadIntervalMillis; private final SparseIntArray mCpusetCategoriesByCpus = new SparseIntArray(); private final SparseArray<File> mCpuFreqPolicyDirsById = new SparseArray<>(); private final SparseArray<StaticPolicyInfo> mStaticPolicyInfoById = new SparseArray<>(); @@ -90,16 +97,20 @@ public final class CpuInfoReader { private SparseArray<CpuUsageStats> mCumulativeCpuUsageStats = new SparseArray<>(); private boolean mIsEnabled; private boolean mHasTimeInStateFile; + private long mLastReadUptimeMillis; + private SparseArray<CpuInfo> mLastReadCpuInfos; public CpuInfoReader() { - this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH)); + this(new File(CPUSET_DIR_PATH), new File(CPUFREQ_DIR_PATH), new File(PROC_STAT_FILE_PATH), + MIN_READ_INTERVAL_MILLISECONDS); } @VisibleForTesting - CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile) { + CpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile, long minReadIntervalMillis) { mCpusetDir = cpusetDir; mCpuFreqDir = cpuFreqDir; mProcStatFile = procStatFile; + mMinReadIntervalMillis = minReadIntervalMillis; } /** @@ -167,6 +178,16 @@ public final class CpuInfoReader { if (!mIsEnabled) { return null; } + long uptimeMillis = SystemClock.uptimeMillis(); + if (mLastReadUptimeMillis > 0 + && uptimeMillis - mLastReadUptimeMillis < mMinReadIntervalMillis) { + Slogf.w(TAG, "Skipping reading from device and returning the last read CpuInfos. " + + "Last read was %d ms ago, min read interval is %d ms", + uptimeMillis - mLastReadUptimeMillis, mMinReadIntervalMillis); + return mLastReadCpuInfos; + } + mLastReadUptimeMillis = uptimeMillis; + mLastReadCpuInfos = null; SparseArray<CpuUsageStats> cpuUsageStatsByCpus = readLatestCpuUsageStats(); if (cpuUsageStatsByCpus == null || cpuUsageStatsByCpus.size() == 0) { Slogf.e(TAG, "Failed to read latest CPU usage stats"); @@ -202,6 +223,12 @@ public final class CpuInfoReader { + " policy ID %d", policyId); continue; } + if (curFreqKHz > maxFreqKHz) { + Slogf.w(TAG, "Current CPU frequency (%d) is greater than maximum CPU frequency" + + " (%d) for policy ID (%d). Skipping CPU frequency policy", curFreqKHz, + maxFreqKHz, policyId); + continue; + } for (int coreIdx = 0; coreIdx < staticPolicyInfo.relatedCpuCores.size(); coreIdx++) { int relatedCpuCore = staticPolicyInfo.relatedCpuCores.get(coreIdx); CpuInfo prevCpuInfo = cpuInfoByCpus.get(relatedCpuCore); @@ -241,9 +268,73 @@ public final class CpuInfoReader { } } } + mLastReadCpuInfos = cpuInfoByCpus; return cpuInfoByCpus; } + /** Dumps the current state. */ + public void dump(IndentingPrintWriter writer) { + writer.printf("*%s*\n", getClass().getSimpleName()); + writer.increaseIndent(); // Add intend for the outermost block. + + writer.printf("mCpusetDir = %s\n", mCpusetDir.getAbsolutePath()); + writer.printf("mCpuFreqDir = %s\n", mCpuFreqDir.getAbsolutePath()); + writer.printf("mProcStatFile = %s\n", mProcStatFile.getAbsolutePath()); + writer.printf("mIsEnabled = %s\n", mIsEnabled); + writer.printf("mHasTimeInStateFile = %s\n", mHasTimeInStateFile); + writer.printf("mLastReadUptimeMillis = %d\n", mLastReadUptimeMillis); + writer.printf("mMinReadIntervalMillis = %d\n", mMinReadIntervalMillis); + + writer.printf("Cpuset categories by CPU core:\n"); + writer.increaseIndent(); + for (int i = 0; i < mCpusetCategoriesByCpus.size(); i++) { + writer.printf("CPU core id = %d, %s\n", mCpusetCategoriesByCpus.keyAt(i), + toCpusetCategoriesStr(mCpusetCategoriesByCpus.valueAt(i))); + } + writer.decreaseIndent(); + + writer.println("Cpu frequency policy directories by policy id:"); + writer.increaseIndent(); + for (int i = 0; i < mCpuFreqPolicyDirsById.size(); i++) { + writer.printf("Policy id = %d, Dir = %s\n", mCpuFreqPolicyDirsById.keyAt(i), + mCpuFreqPolicyDirsById.valueAt(i)); + } + writer.decreaseIndent(); + + writer.println("Static cpu frequency policy infos by policy id:"); + writer.increaseIndent(); + for (int i = 0; i < mStaticPolicyInfoById.size(); i++) { + writer.printf("Policy id = %d, %s\n", mStaticPolicyInfoById.keyAt(i), + mStaticPolicyInfoById.valueAt(i)); + } + writer.decreaseIndent(); + + writer.println("Cpu time in frequency state by policy id:"); + writer.increaseIndent(); + for (int i = 0; i < mTimeInStateByPolicyId.size(); i++) { + writer.printf("Policy id = %d, Time(millis) in state by CPU frequency(KHz) = %s\n", + mTimeInStateByPolicyId.keyAt(i), mTimeInStateByPolicyId.valueAt(i)); + } + writer.decreaseIndent(); + + writer.println("Last read CPU infos:"); + writer.increaseIndent(); + for (int i = 0; i < mLastReadCpuInfos.size(); i++) { + writer.printf("%s\n", mLastReadCpuInfos.valueAt(i)); + } + writer.decreaseIndent(); + + writer.println("Latest cumulative CPU usage stats by CPU core:"); + writer.increaseIndent(); + for (int i = 0; i < mCumulativeCpuUsageStats.size(); i++) { + writer.printf("CPU core id = %d, %s\n", mCumulativeCpuUsageStats.keyAt(i), + mCumulativeCpuUsageStats.valueAt(i)); + } + writer.decreaseIndent(); + + writer.decreaseIndent(); // Remove intend for the outermost block. + } + /** * Sets the CPU frequency for testing. * @@ -496,6 +587,9 @@ public final class CpuInfoReader { for (int i = 0; i < timeInState.size(); i++) { totalTimeInState += timeInState.valueAt(i); } + if (totalTimeInState == 0) { + return CpuInfo.MISSING_FREQUENCY; + } double avgFreqKHz = 0; for (int i = 0; i < timeInState.size(); i++) { avgFreqKHz += (timeInState.keyAt(i) * timeInState.valueAt(i)) / totalTimeInState; @@ -624,16 +718,29 @@ public final class CpuInfoReader { @CpusetCategory public final int cpusetCategories; public final boolean isOnline; + public final long maxCpuFreqKHz; // Values in the below fields may be missing when a CPU core is offline. public final long curCpuFreqKHz; - public final long maxCpuFreqKHz; public final long avgTimeInStateCpuFreqKHz; @Nullable public final CpuUsageStats latestCpuUsageStats; + private long mNormalizedAvailableCpuFreqKHz; + CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline, long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz, CpuUsageStats latestCpuUsageStats) { + this(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz, + avgTimeInStateCpuFreqKHz, /* normalizedAvailableCpuFreqKHz= */ 0, + latestCpuUsageStats); + this.mNormalizedAvailableCpuFreqKHz = computeNormalizedAvailableCpuFreqKHz(); + } + + // Should be used only for testing. + @VisibleForTesting + CpuInfo(int cpuCore, @CpusetCategory int cpusetCategories, boolean isOnline, + long curCpuFreqKHz, long maxCpuFreqKHz, long avgTimeInStateCpuFreqKHz, + long normalizedAvailableCpuFreqKHz, CpuUsageStats latestCpuUsageStats) { this.cpuCore = cpuCore; this.cpusetCategories = cpusetCategories; this.isOnline = isOnline; @@ -641,6 +748,11 @@ public final class CpuInfoReader { this.maxCpuFreqKHz = maxCpuFreqKHz; this.avgTimeInStateCpuFreqKHz = avgTimeInStateCpuFreqKHz; this.latestCpuUsageStats = latestCpuUsageStats; + this.mNormalizedAvailableCpuFreqKHz = normalizedAvailableCpuFreqKHz; + } + + public long getNormalizedAvailableCpuFreqKHz() { + return mNormalizedAvailableCpuFreqKHz; } @Override @@ -657,6 +769,8 @@ public final class CpuInfoReader { .append(avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY ? "missing" : avgTimeInStateCpuFreqKHz) .append(", latestCpuUsageStats = ").append(latestCpuUsageStats) + .append(", mNormalizedAvailableCpuFreqKHz = ") + .append(mNormalizedAvailableCpuFreqKHz) .append(" }").toString(); } @@ -673,13 +787,32 @@ public final class CpuInfoReader { && isOnline == other.isOnline && curCpuFreqKHz == other.curCpuFreqKHz && maxCpuFreqKHz == other.maxCpuFreqKHz && avgTimeInStateCpuFreqKHz == other.avgTimeInStateCpuFreqKHz - && latestCpuUsageStats.equals(other.latestCpuUsageStats); + && latestCpuUsageStats.equals(other.latestCpuUsageStats) + && mNormalizedAvailableCpuFreqKHz == other.mNormalizedAvailableCpuFreqKHz; } @Override public int hashCode() { return Objects.hash(cpuCore, cpusetCategories, isOnline, curCpuFreqKHz, maxCpuFreqKHz, - avgTimeInStateCpuFreqKHz, latestCpuUsageStats); + avgTimeInStateCpuFreqKHz, latestCpuUsageStats, mNormalizedAvailableCpuFreqKHz); + } + + private long computeNormalizedAvailableCpuFreqKHz() { + if (!isOnline) { + return MISSING_FREQUENCY; + } + long totalTimeMillis = latestCpuUsageStats.getTotalTimeMillis(); + if (totalTimeMillis == 0) { + Slogf.wtf(TAG, "Total CPU time millis is 0. This shouldn't happen unless stats are" + + " polled too frequently"); + return MISSING_FREQUENCY; + } + double nonIdlePercent = 100.0 * (totalTimeMillis + - (double) latestCpuUsageStats.idleTimeMillis) / totalTimeMillis; + long curFreqKHz = avgTimeInStateCpuFreqKHz == MISSING_FREQUENCY + ? curCpuFreqKHz : avgTimeInStateCpuFreqKHz; + double availablePercent = 100.0 - (nonIdlePercent * curFreqKHz / maxCpuFreqKHz); + return (long) ((availablePercent * maxCpuFreqKHz) / 100.0); } } @@ -712,7 +845,7 @@ public final class CpuInfoReader { this.guestNiceTimeMillis = guestNiceTimeMillis; } - public long getTotalTime() { + public long getTotalTimeMillis() { return userTimeMillis + niceTimeMillis + systemTimeMillis + idleTimeMillis + iowaitTimeMillis + irqTimeMillis + softirqTimeMillis + stealTimeMillis + guestTimeMillis + guestNiceTimeMillis; @@ -796,8 +929,8 @@ public final class CpuInfoReader { @Override public String toString() { - return "FrequencyPair{cpuFreqKHz=" + cpuFreqKHz + ", scalingFreqKHz=" + scalingFreqKHz - + '}'; + return "FrequencyPair{cpuFreqKHz = " + cpuFreqKHz + ", scalingFreqKHz = " + + scalingFreqKHz + '}'; } } @@ -812,7 +945,7 @@ public final class CpuInfoReader { @Override public String toString() { - return "StaticPolicyInfo{maxCpuFreqPair=" + maxCpuFreqPair + ", relatedCpuCores=" + return "StaticPolicyInfo{maxCpuFreqPair = " + maxCpuFreqPair + ", relatedCpuCores = " + relatedCpuCores + '}'; } } @@ -831,9 +964,9 @@ public final class CpuInfoReader { @Override public String toString() { - return "DynamicPolicyInfo{curCpuFreqPair=" + curCpuFreqPair - + ", avgTimeInStateCpuFreqKHz=" + avgTimeInStateCpuFreqKHz - + ", affectedCpuCores=" + affectedCpuCores + '}'; + return "DynamicPolicyInfo{curCpuFreqPair = " + curCpuFreqPair + + ", avgTimeInStateCpuFreqKHz = " + avgTimeInStateCpuFreqKHz + + ", affectedCpuCores = " + affectedCpuCores + '}'; } } } diff --git a/services/core/java/com/android/server/cpu/CpuMonitorService.java b/services/core/java/com/android/server/cpu/CpuMonitorService.java index 4eefe5c8cad5..df8cfad4ab03 100644 --- a/services/core/java/com/android/server/cpu/CpuMonitorService.java +++ b/services/core/java/com/android/server/cpu/CpuMonitorService.java @@ -18,15 +18,33 @@ package com.android.server.cpu; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; +import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL; +import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND; +import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND; +import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP; + +import android.annotation.Nullable; import android.content.Context; import android.os.Binder; -import android.util.ArrayMap; +import android.os.Build; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Process; +import android.os.SystemClock; import android.util.IndentingPrintWriter; +import android.util.IntArray; import android.util.Log; +import android.util.LongSparseArray; +import android.util.SparseArray; +import android.util.SparseArrayMap; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; +import com.android.server.ServiceThread; import com.android.server.SystemService; +import com.android.server.Watchdog; import com.android.server.utils.PriorityDump; import com.android.server.utils.Slogf; @@ -34,28 +52,55 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.Objects; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; /** Service to monitor CPU availability and usage. */ public final class CpuMonitorService extends SystemService { static final String TAG = CpuMonitorService.class.getSimpleName(); static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); - // TODO(b/242722241): Make this a resource overlay property. - // Maintain 3 monitoring intervals: - // * One to poll very frequently when mCpuAvailabilityCallbackInfoByCallbacks are available and - // CPU availability is above a threshold (such as at least 10% of CPU is available). - // * One to poll less frequently when mCpuAvailabilityCallbackInfoByCallbacks are available - // and CPU availability is below a threshold (such as less than 10% of CPU is available). - // * One to poll very less frequently when no callbacks are available and the build is either - // user-debug or eng. This will be useful for debugging in development environment. - static final int DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS = 5_000; + // TODO(b/267500110): Make these constants resource overlay properties. + /** Default monitoring interval when no monitoring is in progress. */ + static final long DEFAULT_MONITORING_INTERVAL_MILLISECONDS = -1; + /** Monitoring interval when callbacks are registered and the CPU load is normal. */ + private static final long NORMAL_MONITORING_INTERVAL_MILLISECONDS = + TimeUnit.SECONDS.toMillis(5); + + /** + * Monitoring interval when no registered callbacks and the build is either user-debug or eng. + */ + private static final long DEBUG_MONITORING_INTERVAL_MILLISECONDS = TimeUnit.MINUTES.toMillis(1); + /** + * Size of the in-memory cache relative to the current uptime. + * + * On user-debug or eng builds, continuously cache stats with a bigger cache size for debugging + * purposes. + */ + private static final long CACHE_DURATION_MILLISECONDS = Build.IS_USERDEBUG || Build.IS_ENG + ? TimeUnit.MINUTES.toMillis(30) : TimeUnit.MINUTES.toMillis(10); + // TODO(b/267500110): Investigate whether this duration should change when the monitoring + // interval is updated. When the CPU is under heavy load, the monitoring will happen less + // frequently. Should this duration be increased as well when this happens? + private static final long LATEST_AVAILABILITY_DURATION_MILLISECONDS = + TimeUnit.SECONDS.toMillis(30); private final Context mContext; + private final HandlerThread mHandlerThread; + private final CpuInfoReader mCpuInfoReader; + private final boolean mShouldDebugMonitor; + private final long mNormalMonitoringIntervalMillis; + private final long mDebugMonitoringIntervalMillis; + private final long mLatestAvailabilityDurationMillis; private final Object mLock = new Object(); @GuardedBy("mLock") - private final ArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, CpuAvailabilityCallbackInfo> - mCpuAvailabilityCallbackInfoByCallbacks = new ArrayMap<>(); + private final SparseArrayMap<CpuMonitorInternal.CpuAvailabilityCallback, + CpuAvailabilityCallbackInfo> mAvailabilityCallbackInfosByCallbacksByCpuset; @GuardedBy("mLock") - private long mMonitoringIntervalMilliseconds = DEFAULT_CPU_MONITORING_INTERVAL_MILLISECONDS; + private final SparseArray<CpusetInfo> mCpusetInfosByCpuset; + private final Runnable mMonitorCpuStats = this::monitorCpuStats; + + @GuardedBy("mLock") + private long mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS; + private Handler mHandler; private final CpuMonitorInternal mLocalService = new CpuMonitorInternal() { @Override @@ -63,89 +108,389 @@ public final class CpuMonitorService extends SystemService { CpuAvailabilityMonitoringConfig config, CpuAvailabilityCallback callback) { Objects.requireNonNull(callback, "Callback must be non-null"); Objects.requireNonNull(config, "Config must be non-null"); + CpuAvailabilityCallbackInfo callbackInfo; synchronized (mLock) { - if (mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) { - Slogf.i(TAG, "Overwriting the existing CpuAvailabilityCallback %s", - mCpuAvailabilityCallbackInfoByCallbacks.get(callback)); - // TODO(b/242722241): Overwrite any internal cache (will be added in future CLs) - // that maps callbacks based on the CPU availability thresholds. - } - CpuAvailabilityCallbackInfo info = new CpuAvailabilityCallbackInfo(config, - executor); - mCpuAvailabilityCallbackInfoByCallbacks.put(callback, info); - if (DEBUG) { - Slogf.d(TAG, "Added a CPU availability callback: %s", info); + // Verify all CPUSET entries before adding the callback because this will help + // delete any previously added callback for a different CPUSET. + for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) { + int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i); + callbackInfo = mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset, + callback); + if (callbackInfo != null) { + Slogf.i(TAG, "Overwriting the existing %s", callbackInfo); + } } + callbackInfo = newCallbackInfoLocked(config, callback, executor); + } + asyncNotifyMonitoringIntervalChangeToClient(callbackInfo); + if (DEBUG) { + Slogf.d(TAG, "Successfully added %s", callbackInfo); } - // TODO(b/242722241): - // * On the executor or on the handler thread, call the callback with the latest CPU - // availability info and monitoring interval. - // * Monitor the CPU stats more frequently when the first callback is added. } @Override public void removeCpuAvailabilityCallback(CpuAvailabilityCallback callback) { synchronized (mLock) { - if (!mCpuAvailabilityCallbackInfoByCallbacks.containsKey(callback)) { - Slogf.i(TAG, "CpuAvailabilityCallback was not previously added." - + " Ignoring the remove request"); - return; - } - CpuAvailabilityCallbackInfo info = - mCpuAvailabilityCallbackInfoByCallbacks.remove(callback); - if (DEBUG) { - Slogf.d(TAG, "Removed a CPU availability callback: %s", info); + for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) { + int cpuset = mAvailabilityCallbackInfosByCallbacksByCpuset.keyAt(i); + CpuAvailabilityCallbackInfo callbackInfo = + mAvailabilityCallbackInfosByCallbacksByCpuset.delete(cpuset, callback); + if (callbackInfo != null) { + if (DEBUG) { + Slogf.d(TAG, "Successfully removed %s", callbackInfo); + } + checkAndStopMonitoringLocked(); + return; + } } + Slogf.w(TAG, "CpuAvailabilityCallback was not previously added. Ignoring the remove" + + " request"); } - // TODO(b/242722241): Increase CPU monitoring interval when all callbacks are removed. } }; public CpuMonitorService(Context context) { + this(context, new CpuInfoReader(), new ServiceThread(TAG, + Process.THREAD_PRIORITY_BACKGROUND, /* allowIo= */ true), + Build.IS_USERDEBUG || Build.IS_ENG, NORMAL_MONITORING_INTERVAL_MILLISECONDS, + DEBUG_MONITORING_INTERVAL_MILLISECONDS, LATEST_AVAILABILITY_DURATION_MILLISECONDS); + } + + @VisibleForTesting + CpuMonitorService(Context context, CpuInfoReader cpuInfoReader, HandlerThread handlerThread, + boolean shouldDebugMonitor, long normalMonitoringIntervalMillis, + long debugMonitoringIntervalMillis, long latestAvailabilityDurationMillis) { super(context); mContext = context; + mHandlerThread = handlerThread; + mShouldDebugMonitor = shouldDebugMonitor; + mNormalMonitoringIntervalMillis = normalMonitoringIntervalMillis; + mDebugMonitoringIntervalMillis = debugMonitoringIntervalMillis; + mLatestAvailabilityDurationMillis = latestAvailabilityDurationMillis; + mCpuInfoReader = cpuInfoReader; + mCpusetInfosByCpuset = new SparseArray<>(2); + mCpusetInfosByCpuset.append(CPUSET_ALL, new CpusetInfo(CPUSET_ALL)); + mCpusetInfosByCpuset.append(CPUSET_BACKGROUND, new CpusetInfo(CPUSET_BACKGROUND)); + mAvailabilityCallbackInfosByCallbacksByCpuset = new SparseArrayMap<>(); } @Override public void onStart() { + // Initialize CPU info reader and perform the first read to make sure the CPU stats are + // readable without any issues. + if (!mCpuInfoReader.init() || mCpuInfoReader.readCpuInfos() == null) { + Slogf.wtf(TAG, "Failed to initialize CPU info reader. This happens when the CPU " + + "frequency stats are not available or the sysfs interface has changed in " + + "the Kernel. Cannot monitor CPU without these stats. Terminating CPU monitor " + + "service"); + return; + } + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); publishLocalService(CpuMonitorInternal.class, mLocalService); publishBinderService("cpu_monitor", new CpuMonitorBinder(), /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL); + Watchdog.getInstance().addThread(mHandler); + synchronized (mLock) { + if (mShouldDebugMonitor && !mHandler.hasCallbacks(mMonitorCpuStats)) { + mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis; + Slogf.i(TAG, "Starting debug monitoring"); + mHandler.post(mMonitorCpuStats); + } + } + } + + @VisibleForTesting + long getCurrentMonitoringIntervalMillis() { + synchronized (mLock) { + return mCurrentMonitoringIntervalMillis; + } } private void doDump(IndentingPrintWriter writer) { writer.printf("*%s*\n", getClass().getSimpleName()); writer.increaseIndent(); + mCpuInfoReader.dump(writer); + writer.printf("mShouldDebugMonitor = %s\n", mShouldDebugMonitor ? "Yes" : "No"); + writer.printf("mNormalMonitoringIntervalMillis = %d\n", mNormalMonitoringIntervalMillis); + writer.printf("mDebugMonitoringIntervalMillis = %d\n", mDebugMonitoringIntervalMillis); + writer.printf("mLatestAvailabilityDurationMillis = %d\n", + mLatestAvailabilityDurationMillis); synchronized (mLock) { - writer.printf("CPU monitoring interval: %d ms\n", mMonitoringIntervalMilliseconds); - if (!mCpuAvailabilityCallbackInfoByCallbacks.isEmpty()) { + writer.printf("mCurrentMonitoringIntervalMillis = %d\n", + mCurrentMonitoringIntervalMillis); + if (hasClientCallbacksLocked()) { writer.println("CPU availability change callbacks:"); writer.increaseIndent(); - for (int i = 0; i < mCpuAvailabilityCallbackInfoByCallbacks.size(); i++) { - writer.printf("%s: %s\n", mCpuAvailabilityCallbackInfoByCallbacks.keyAt(i), - mCpuAvailabilityCallbackInfoByCallbacks.valueAt(i)); + mAvailabilityCallbackInfosByCallbacksByCpuset.forEach( + (callbackInfo) -> writer.printf("%s\n", callbackInfo)); + writer.decreaseIndent(); + } + if (mCpusetInfosByCpuset.size() > 0) { + writer.println("Cpuset infos:"); + writer.increaseIndent(); + for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) { + writer.printf("%s\n", mCpusetInfosByCpuset.valueAt(i)); } writer.decreaseIndent(); } } - // TODO(b/242722241): Print the recent past CPU stats. writer.decreaseIndent(); } + private void monitorCpuStats() { + long uptimeMillis = SystemClock.uptimeMillis(); + // Remove duplicate callbacks caused by switching form debug to normal monitoring. + // The removal of the duplicate callback done in the {@link newCallbackInfoLocked} method + // may result in a no-op when a duplicate execution of this callback has already started + // on the handler thread. + mHandler.removeCallbacks(mMonitorCpuStats); + SparseArray<CpuInfoReader.CpuInfo> cpuInfosByCoreId = mCpuInfoReader.readCpuInfos(); + if (cpuInfosByCoreId == null) { + // This shouldn't happen because the CPU infos are read & verified during + // the {@link onStart} call. + Slogf.wtf(TAG, "Failed to read CPU info from device"); + synchronized (mLock) { + stopMonitoringCpuStatsLocked(); + } + // Monitoring is stopped but no client callback is removed. + // TODO(b/267500110): Identify whether the clients should be notified about this state. + return; + } + + synchronized (mLock) { + // 1. Populate the {@link mCpusetInfosByCpuset} with the latest cpuInfo. + for (int i = 0; i < cpuInfosByCoreId.size(); i++) { + CpuInfoReader.CpuInfo cpuInfo = cpuInfosByCoreId.valueAt(i); + for (int j = 0; j < mCpusetInfosByCpuset.size(); j++) { + mCpusetInfosByCpuset.valueAt(j).appendCpuInfo(uptimeMillis, cpuInfo); + } + } + + // 2. Verify whether any monitoring thresholds are crossed and notify the corresponding + // clients. + for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) { + CpusetInfo cpusetInfo = mCpusetInfosByCpuset.valueAt(i); + cpusetInfo.populateLatestCpuAvailabilityInfo(uptimeMillis, + mLatestAvailabilityDurationMillis); + checkClientThresholdsAndNotifyLocked(cpusetInfo); + } + + // TODO(b/267500110): Detect heavy CPU load. On detecting heavy CPU load, increase + // the monitoring interval and notify the clients. + + // 3. Continue monitoring only when either there is at least one registered client + // callback or debug monitoring is enabled. + if (mCurrentMonitoringIntervalMillis > 0 + && (hasClientCallbacksLocked() || mShouldDebugMonitor)) { + mHandler.postAtTime(mMonitorCpuStats, + uptimeMillis + mCurrentMonitoringIntervalMillis); + } else { + stopMonitoringCpuStatsLocked(); + } + } + } + + @GuardedBy("mLock") + private void checkClientThresholdsAndNotifyLocked(CpusetInfo cpusetInfo) { + int prevAvailabilityPercent = cpusetInfo.getPrevCpuAvailabilityPercent(); + CpuAvailabilityInfo latestAvailabilityInfo = cpusetInfo.getLatestCpuAvailabilityInfo(); + if (latestAvailabilityInfo == null || prevAvailabilityPercent < 0 + || mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKey( + cpusetInfo.cpuset) == 0) { + // When either the current or the previous CPU availability percents are + // missing, skip the current cpuset as there is not enough data to verify + // whether the CPU availability has crossed any monitoring threshold. + return; + } + for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) { + for (int j = 0; j < mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt( + i); j++) { + CpuAvailabilityCallbackInfo callbackInfo = + mAvailabilityCallbackInfosByCallbacksByCpuset.valueAt(i, j); + if (callbackInfo.config.cpuset != cpusetInfo.cpuset) { + continue; + } + if (didCrossAnyThreshold(prevAvailabilityPercent, + latestAvailabilityInfo.latestAvgAvailabilityPercent, + callbackInfo.config.getThresholds())) { + asyncNotifyCpuAvailabilityToClient(latestAvailabilityInfo, callbackInfo); + } + } + } + } + + private void asyncNotifyMonitoringIntervalChangeToClient( + CpuAvailabilityCallbackInfo callbackInfo) { + if (callbackInfo.executor == null) { + mHandler.post(callbackInfo.notifyMonitoringIntervalChangeRunnable); + } else { + callbackInfo.executor.execute(callbackInfo.notifyMonitoringIntervalChangeRunnable); + } + } + + private void asyncNotifyCpuAvailabilityToClient(CpuAvailabilityInfo availabilityInfo, + CpuAvailabilityCallbackInfo callbackInfo) { + callbackInfo.notifyCpuAvailabilityChangeRunnable.prepare(availabilityInfo); + if (callbackInfo.executor == null) { + mHandler.post(callbackInfo.notifyCpuAvailabilityChangeRunnable); + } else { + callbackInfo.executor.execute(callbackInfo.notifyCpuAvailabilityChangeRunnable); + } + } + + @GuardedBy("mLock") + private CpuAvailabilityCallbackInfo newCallbackInfoLocked( + CpuAvailabilityMonitoringConfig config, + CpuMonitorInternal.CpuAvailabilityCallback callback, Executor executor) { + CpuAvailabilityCallbackInfo callbackInfo = new CpuAvailabilityCallbackInfo(this, config, + callback, executor); + String cpusetStr = CpuAvailabilityMonitoringConfig.toCpusetString( + callbackInfo.config.cpuset); + CpusetInfo cpusetInfo = mCpusetInfosByCpuset.get(callbackInfo.config.cpuset); + Preconditions.checkState(cpusetInfo != null, "Missing cpuset info for cpuset %s", + cpusetStr); + boolean hasExistingClientCallbacks = hasClientCallbacksLocked(); + mAvailabilityCallbackInfosByCallbacksByCpuset.add(callbackInfo.config.cpuset, + callbackInfo.callback, callbackInfo); + if (DEBUG) { + Slogf.d(TAG, "Added a CPU availability callback: %s", callbackInfo); + } + CpuAvailabilityInfo latestInfo = cpusetInfo.getLatestCpuAvailabilityInfo(); + if (latestInfo != null) { + asyncNotifyCpuAvailabilityToClient(latestInfo, callbackInfo); + } + if (hasExistingClientCallbacks && mHandler.hasCallbacks(mMonitorCpuStats)) { + return callbackInfo; + } + // Remove existing callbacks to ensure any debug monitoring (if started) is stopped before + // starting normal monitoring. + mHandler.removeCallbacks(mMonitorCpuStats); + mCurrentMonitoringIntervalMillis = mNormalMonitoringIntervalMillis; + mHandler.post(mMonitorCpuStats); + return callbackInfo; + } + + @GuardedBy("mLock") + private void checkAndStopMonitoringLocked() { + if (hasClientCallbacksLocked()) { + return; + } + if (mShouldDebugMonitor) { + if (DEBUG) { + Slogf.e(TAG, "Switching to debug monitoring"); + } + mCurrentMonitoringIntervalMillis = mDebugMonitoringIntervalMillis; + } else { + stopMonitoringCpuStatsLocked(); + } + } + + @GuardedBy("mLock") + private boolean hasClientCallbacksLocked() { + for (int i = 0; i < mAvailabilityCallbackInfosByCallbacksByCpuset.numMaps(); i++) { + if (mAvailabilityCallbackInfosByCallbacksByCpuset.numElementsForKeyAt(i) > 0) { + return true; + } + } + return false; + } + + @GuardedBy("mLock") + private void stopMonitoringCpuStatsLocked() { + mHandler.removeCallbacks(mMonitorCpuStats); + mCurrentMonitoringIntervalMillis = DEFAULT_MONITORING_INTERVAL_MILLISECONDS; + // When the monitoring is stopped, the latest CPU availability info and the snapshots in + // {@code mCpusetInfosByCpuset} will become obsolete soon. So, remove them. + for (int i = 0; i < mCpusetInfosByCpuset.size(); i++) { + mCpusetInfosByCpuset.valueAt(i).clear(); + } + } + + private static boolean containsCpuset(@CpuInfoReader.CpusetCategory int cpusetCategories, + @CpuAvailabilityMonitoringConfig.Cpuset int expectedCpuset) { + switch (expectedCpuset) { + case CPUSET_ALL: + return (cpusetCategories & FLAG_CPUSET_CATEGORY_TOP_APP) != 0; + case CPUSET_BACKGROUND: + return (cpusetCategories & FLAG_CPUSET_CATEGORY_BACKGROUND) != 0; + default: + Slogf.wtf(TAG, "Provided invalid expectedCpuset %d", expectedCpuset); + } + return false; + } + + private static boolean didCrossAnyThreshold(int prevAvailabilityPercent, + int curAvailabilityPercent, IntArray thresholds) { + if (prevAvailabilityPercent == curAvailabilityPercent) { + return false; + } + for (int i = 0; i < thresholds.size(); i++) { + int threshold = thresholds.get(i); + // TODO(b/267500110): Identify whether or not the clients need to be notified when + // the CPU availability jumps too frequently around the provided thresholds. + // A. Should the client be notified twice - once when the availability reaches + // the threshold and once when it moves away (increase/decrease) from the threshold + // immediately? + // B. Should there be some sort of rate-limiting to avoid notifying the client too + // frequently? Should the client be able to config the rate-limit? + if (prevAvailabilityPercent < threshold && curAvailabilityPercent >= threshold) { + return true; + } + if (prevAvailabilityPercent >= threshold && curAvailabilityPercent < threshold) { + return true; + } + } + return false; + } + private static final class CpuAvailabilityCallbackInfo { + public final CpuMonitorService service; public final CpuAvailabilityMonitoringConfig config; + public final CpuMonitorInternal.CpuAvailabilityCallback callback; + @Nullable public final Executor executor; + public final Runnable notifyMonitoringIntervalChangeRunnable = new Runnable() { + @Override + public void run() { + callback.onMonitoringIntervalChanged(service.getCurrentMonitoringIntervalMillis()); + } + }; + public final NotifyCpuAvailabilityChangeRunnable notifyCpuAvailabilityChangeRunnable = + new NotifyCpuAvailabilityChangeRunnable(); - CpuAvailabilityCallbackInfo(CpuAvailabilityMonitoringConfig config, - Executor executor) { + CpuAvailabilityCallbackInfo(CpuMonitorService service, + CpuAvailabilityMonitoringConfig config, + CpuMonitorInternal.CpuAvailabilityCallback callback, @Nullable Executor executor) { + this.service = service; this.config = config; + this.callback = callback; this.executor = executor; } @Override public String toString() { - return "CpuAvailabilityCallbackInfo{" + "config=" + config + ", mExecutor=" + executor - + '}'; + return "CpuAvailabilityCallbackInfo{config = " + config + ", callback = " + callback + + ", mExecutor = " + executor + '}'; + } + + private final class NotifyCpuAvailabilityChangeRunnable implements Runnable { + private final Object mLock = new Object(); + @GuardedBy("mLock") + private CpuAvailabilityInfo mCpuAvailabilityInfo; + + public void prepare(CpuAvailabilityInfo cpuAvailabilityInfo) { + synchronized (mLock) { + mCpuAvailabilityInfo = cpuAvailabilityInfo; + } + } + + @Override + public void run() { + synchronized (mLock) { + callback.onAvailabilityChanged(mCpuAvailabilityInfo); + } + } } } @@ -170,4 +515,157 @@ public final class CpuMonitorService extends SystemService { PriorityDump.dump(mPriorityDumper, fd, pw, args); } } + + private static final class CpusetInfo { + @CpuAvailabilityMonitoringConfig.Cpuset + public final int cpuset; + private final LongSparseArray<Snapshot> mSnapshotsByUptime; + @Nullable + private CpuAvailabilityInfo mLatestCpuAvailabilityInfo; + + CpusetInfo(int cpuset) { + this.cpuset = cpuset; + mSnapshotsByUptime = new LongSparseArray<>(); + } + + public void appendCpuInfo(long uptimeMillis, CpuInfoReader.CpuInfo cpuInfo) { + if (!containsCpuset(cpuInfo.cpusetCategories, cpuset)) { + return; + } + Snapshot currentSnapshot = mSnapshotsByUptime.get(uptimeMillis); + if (currentSnapshot == null) { + currentSnapshot = new Snapshot(uptimeMillis); + mSnapshotsByUptime.append(uptimeMillis, currentSnapshot); + if (mSnapshotsByUptime.size() > 0 + && (uptimeMillis - mSnapshotsByUptime.valueAt(0).uptimeMillis) + > CACHE_DURATION_MILLISECONDS) { + mSnapshotsByUptime.removeAt(0); + } + } + currentSnapshot.appendCpuInfo(cpuInfo); + } + + @Nullable + public CpuAvailabilityInfo getLatestCpuAvailabilityInfo() { + return mLatestCpuAvailabilityInfo; + } + + public void populateLatestCpuAvailabilityInfo(long currentUptimeMillis, + long latestAvailabilityDurationMillis) { + int numSnapshots = mSnapshotsByUptime.size(); + if (numSnapshots == 0) { + mLatestCpuAvailabilityInfo = null; + return; + } + Snapshot latestSnapshot = mSnapshotsByUptime.valueAt(numSnapshots - 1); + if (latestSnapshot.uptimeMillis != currentUptimeMillis) { + // When the cpuset has no stats available for the current polling, the uptime will + // mismatch. When this happens, return {@code null} to avoid returning stale + // information. + if (DEBUG) { + Slogf.d(TAG, "Skipping stale CPU availability information for cpuset %s", + CpuAvailabilityMonitoringConfig.toCpusetString(cpuset)); + } + mLatestCpuAvailabilityInfo = null; + return; + } + // Avoid constructing {@link mLatestCpuAvailabilityInfo} if the uptime hasn't changed. + if (mLatestCpuAvailabilityInfo != null + && mLatestCpuAvailabilityInfo.dataTimestampUptimeMillis + == latestSnapshot.uptimeMillis) { + return; + } + long earliestUptimeMillis = currentUptimeMillis - latestAvailabilityDurationMillis; + mLatestCpuAvailabilityInfo = new CpuAvailabilityInfo(cpuset, + latestSnapshot.uptimeMillis, latestSnapshot.getAverageAvailableCpuFreqPercent(), + getCumulativeAvgAvailabilityPercent(earliestUptimeMillis), + latestAvailabilityDurationMillis); + } + + public int getPrevCpuAvailabilityPercent() { + int numSnapshots = mSnapshotsByUptime.size(); + if (numSnapshots < 2) { + return -1; + } + return mSnapshotsByUptime.valueAt(numSnapshots - 2).getAverageAvailableCpuFreqPercent(); + } + + private int getCumulativeAvgAvailabilityPercent(long earliestUptimeMillis) { + long totalAvailableCpuFreqKHz = 0; + long totalOnlineMaxCpuFreqKHz = 0; + int totalAccountedSnapshots = 0; + long earliestSeenUptimeMillis = Long.MAX_VALUE; + for (int i = mSnapshotsByUptime.size() - 1; i >= 0; i--) { + Snapshot snapshot = mSnapshotsByUptime.valueAt(i); + earliestSeenUptimeMillis = snapshot.uptimeMillis; + if (snapshot.uptimeMillis <= earliestUptimeMillis) { + break; + } + totalAccountedSnapshots++; + totalAvailableCpuFreqKHz += snapshot.totalNormalizedAvailableCpuFreqKHz; + totalOnlineMaxCpuFreqKHz += snapshot.totalOnlineMaxCpuFreqKHz; + } + // The cache must have at least 2 snapshots within the given duration and + // the {@link earliestSeenUptimeMillis} must be earlier than (i,e., less than) the given + // {@link earliestUptimeMillis}. Otherwise, the cache doesn't have enough data to + // calculate the cumulative average for the given duration. + // TODO(b/267500110): Investigate whether the cumulative average duration should be + // shrunk when not enough data points are available. + if (earliestSeenUptimeMillis > earliestUptimeMillis || totalAccountedSnapshots < 2) { + return CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT; + } + return (int) ((totalAvailableCpuFreqKHz * 100.0) / totalOnlineMaxCpuFreqKHz); + } + + public void clear() { + mLatestCpuAvailabilityInfo = null; + mSnapshotsByUptime.clear(); + } + + @Override + public String toString() { + return "CpusetInfo{cpuset = " + CpuAvailabilityMonitoringConfig.toCpusetString(cpuset) + + ", mSnapshotsByUptime = " + mSnapshotsByUptime + + ", mLatestCpuAvailabilityInfo = " + mLatestCpuAvailabilityInfo + '}'; + } + + private static final class Snapshot { + public final long uptimeMillis; + public int totalOnlineCpus; + public int totalOfflineCpus; + public long totalNormalizedAvailableCpuFreqKHz; + public long totalOnlineMaxCpuFreqKHz; + public long totalOfflineMaxCpuFreqKHz; + + Snapshot(long uptimeMillis) { + this.uptimeMillis = uptimeMillis; + } + + public void appendCpuInfo(CpuInfoReader.CpuInfo cpuInfo) { + if (!cpuInfo.isOnline) { + totalOfflineCpus++; + totalOfflineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz; + return; + } + ++totalOnlineCpus; + totalNormalizedAvailableCpuFreqKHz += cpuInfo.getNormalizedAvailableCpuFreqKHz(); + totalOnlineMaxCpuFreqKHz += cpuInfo.maxCpuFreqKHz; + } + + public int getAverageAvailableCpuFreqPercent() { + return (int) ((totalNormalizedAvailableCpuFreqKHz * 100.0) + / totalOnlineMaxCpuFreqKHz); + } + + @Override + public String toString() { + return "Snapshot{uptimeMillis = " + uptimeMillis + ", totalOnlineCpus = " + + totalOnlineCpus + ", totalOfflineCpus = " + totalOfflineCpus + + ", totalNormalizedAvailableCpuFreqKHz = " + + totalNormalizedAvailableCpuFreqKHz + + ", totalOnlineMaxCpuFreqKHz = " + totalOnlineMaxCpuFreqKHz + + ", totalOfflineMaxCpuFreqKHz = " + totalOfflineMaxCpuFreqKHz + '}'; + } + } + } } diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java index 77cd67304729..a081dff9e62d 100644 --- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java +++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java @@ -92,6 +92,8 @@ public class GnssConfiguration { // Represents an HAL interface version. Instances of this class are created in the JNI layer // and returned through native methods. static class HalInterfaceVersion { + // mMajor being this value denotes AIDL HAL. In this case, mMinor denotes the AIDL version. + static final int AIDL_INTERFACE = 3; final int mMajor; final int mMinor; diff --git a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java index 6c4c829b051d..041f11d972fe 100644 --- a/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java +++ b/services/core/java/com/android/server/location/gnss/GnssMeasurementsProvider.java @@ -21,6 +21,7 @@ import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; import static com.android.server.location.gnss.GnssManagerService.D; import static com.android.server.location.gnss.GnssManagerService.TAG; +import android.annotation.NonNull; import android.annotation.Nullable; import android.app.AppOpsManager; import android.location.GnssMeasurementRequest; @@ -31,6 +32,7 @@ import android.os.IBinder; import android.stats.location.LocationStatsEnums; import android.util.Log; +import com.android.server.location.gnss.GnssConfiguration.HalInterfaceVersion; import com.android.server.location.gnss.hal.GnssNative; import com.android.server.location.injector.AppOpsHelper; import com.android.server.location.injector.Injector; @@ -115,16 +117,6 @@ public final class GnssMeasurementsProvider extends if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) { return true; } - // The HAL doc does not specify if consecutive start() calls will be allowed. - // Some vendors may ignore the 2nd start() call if stop() is not called. - // Thus, here we always call stop() before calling start() to avoid being ignored. - if (mGnssNative.stopMeasurementCollection()) { - if (D) { - Log.d(TAG, "stopping gnss measurements"); - } - } else { - Log.e(TAG, "error stopping gnss measurements"); - } if (mGnssNative.startMeasurementCollection(request.isFullTracking(), request.isCorrelationVectorOutputsEnabled(), request.getIntervalMillis())) { @@ -139,6 +131,28 @@ public final class GnssMeasurementsProvider extends } @Override + protected boolean reregisterWithService(GnssMeasurementRequest old, + GnssMeasurementRequest request, + @NonNull Collection<GnssListenerRegistration> registrations) { + if (request.getIntervalMillis() == GnssMeasurementRequest.PASSIVE_INTERVAL) { + unregisterWithService(); + return true; + } + HalInterfaceVersion halInterfaceVersion = + mGnssNative.getConfiguration().getHalInterfaceVersion(); + boolean aidlV3Plus = halInterfaceVersion.mMajor == HalInterfaceVersion.AIDL_INTERFACE + && halInterfaceVersion.mMinor >= 3; + if (!aidlV3Plus) { + // The HAL doc does not specify if consecutive start() calls will be allowed. + // Some vendors may ignore the 2nd start() call if stop() is not called. + // Thus, here we always call stop() before calling start() to avoid being ignored. + // AIDL v3+ is free from this issue. + unregisterWithService(); + } + return registerWithService(request, registrations); + } + + @Override protected void unregisterWithService() { if (mGnssNative.stopMeasurementCollection()) { if (D) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 46337a909539..bb79c99cc985 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2340,7 +2340,6 @@ public class NotificationManagerService extends SystemService { mAppOps, new SysUiStatsEvent.BuilderFactory(), mShowReviewPermissionsNotification); - mPreferencesHelper.updateFixedImportance(mUm.getUsers()); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper, @@ -2771,6 +2770,9 @@ public class NotificationManagerService extends SystemService { maybeShowInitialReviewPermissionsNotification(); } else if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { mSnoozeHelper.scheduleRepostsForPersistedNotifications(System.currentTimeMillis()); + } else if (phase == SystemService.PHASE_DEVICE_SPECIFIC_SERVICES_READY) { + mPreferencesHelper.updateFixedImportance(mUm.getUsers()); + mPreferencesHelper.migrateNotificationPermissions(mUm.getUsers()); } } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 4bafbc73a96b..aa97aa3655e2 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -237,7 +237,6 @@ public class PreferencesHelper implements RankingConfig { Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE, NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW); } - ArrayList<PermissionHelper.PackagePermission> pkgPerms = new ArrayList<>(); synchronized (mPackagePreferences) { while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { tag = parser.getName(); @@ -255,27 +254,18 @@ public class PreferencesHelper implements RankingConfig { String name = parser.getAttributeValue(null, ATT_NAME); if (!TextUtils.isEmpty(name)) { restorePackage(parser, forRestore, userId, name, upgradeForBubbles, - migrateToPermission, pkgPerms); + migrateToPermission); } } } } } - if (migrateToPermission) { - for (PackagePermission p : pkgPerms) { - try { - mPermissionHelper.setNotificationPermission(p); - } catch (Exception e) { - Slog.e(TAG, "could not migrate setting for " + p.packageName, e); - } - } - } } @GuardedBy("mPackagePreferences") private void restorePackage(TypedXmlPullParser parser, boolean forRestore, @UserIdInt int userId, String name, boolean upgradeForBubbles, - boolean migrateToPermission, ArrayList<PermissionHelper.PackagePermission> pkgPerms) { + boolean migrateToPermission) { try { int uid = parser.getAttributeInt(null, ATT_UID, UNKNOWN_UID); if (forRestore) { @@ -379,14 +369,6 @@ public class PreferencesHelper implements RankingConfig { if (migrateToPermission) { r.importance = appImportance; r.migrateToPm = true; - if (r.uid != UNKNOWN_UID) { - // Don't call into permission system until we have a valid uid - PackagePermission pkgPerm = new PackagePermission( - r.pkg, UserHandle.getUserId(r.uid), - r.importance != IMPORTANCE_NONE, - hasUserConfiguredSettings(r)); - pkgPerms.add(pkgPerm); - } } } catch (Exception e) { Slog.w(TAG, "Failed to restore pkg", e); @@ -2663,6 +2645,31 @@ public class PreferencesHelper implements RankingConfig { } } + public void migrateNotificationPermissions(List<UserInfo> users) { + for (UserInfo user : users) { + List<PackageInfo> packages = mPm.getInstalledPackagesAsUser( + PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL), + user.getUserHandle().getIdentifier()); + for (PackageInfo pi : packages) { + synchronized (mPackagePreferences) { + PackagePreferences p = getOrCreatePackagePreferencesLocked( + pi.packageName, pi.applicationInfo.uid); + if (p.migrateToPm && p.uid != UNKNOWN_UID) { + try { + PackagePermission pkgPerm = new PackagePermission( + p.pkg, UserHandle.getUserId(p.uid), + p.importance != IMPORTANCE_NONE, + hasUserConfiguredSettings(p)); + mPermissionHelper.setNotificationPermission(pkgPerm); + } catch (Exception e) { + Slog.e(TAG, "could not migrate setting for " + p.pkg, e); + } + } + } + } + } + } + private void updateConfig() { mRankingHandler.requestSort(); } diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index a119a3ce62cc..9a5ee8124cb4 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; +import static android.content.pm.PackageManager.DELETE_SUCCEEDED; import static android.content.pm.PackageManager.MATCH_KNOWN_PACKAGES; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import static android.os.storage.StorageManager.FLAG_STORAGE_CE; @@ -339,7 +340,7 @@ final class DeletePackageHelper { packageInstallerService.onInstallerPackageDeleted(uninstalledPs.getAppId(), removeUser); } - return res ? PackageManager.DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR; + return res ? DELETE_SUCCEEDED : PackageManager.DELETE_FAILED_INTERNAL_ERROR; } /* @@ -777,21 +778,30 @@ final class DeletePackageHelper { returnCode = deletePackageX(internalPackageName, versionCode, userId, deleteFlags, false /*removedBySystem*/); - // Get a list of child user profiles and delete if package is - // present in that profile. - int[] childUserIds = mUserManagerInternal.getProfileIds(userId, true); - int returnCodeOfChild; - for (int childId : childUserIds) { - if (childId == userId) continue; - UserProperties userProperties = mUserManagerInternal - .getUserProperties(childId); - if (userProperties != null && userProperties.getDeleteAppWithParent()) { - returnCodeOfChild = deletePackageX(internalPackageName, versionCode, - childId, deleteFlags, false /*removedBySystem*/); - if (returnCodeOfChild != PackageManager.DELETE_SUCCEEDED) { - Slog.w(TAG, "Package delete failed for user " + childId - + ", returnCode " + returnCodeOfChild); - returnCode = PackageManager.DELETE_FAILED_FOR_CHILD_PROFILE; + // Delete package in child only if successfully deleted in parent. + if (returnCode == DELETE_SUCCEEDED && packageState != null) { + // Get a list of child user profiles and delete if package is + // present in that profile. + int[] childUserIds = mUserManagerInternal.getProfileIds(userId, true); + int returnCodeOfChild; + for (int childId : childUserIds) { + if (childId == userId) continue; + + // If package is not present in child then don't attempt to delete. + if (!packageState.getUserStateOrDefault(childId).isInstalled()) { + continue; + } + + UserProperties userProperties = mUserManagerInternal + .getUserProperties(childId); + if (userProperties != null && userProperties.getDeleteAppWithParent()) { + returnCodeOfChild = deletePackageX(internalPackageName, versionCode, + childId, deleteFlags, false /*removedBySystem*/); + if (returnCodeOfChild != DELETE_SUCCEEDED) { + Slog.w(TAG, "Package delete failed for user " + childId + + ", returnCode " + returnCodeOfChild); + returnCode = PackageManager.DELETE_FAILED_FOR_CHILD_PROFILE; + } } } } @@ -809,7 +819,7 @@ final class DeletePackageHelper { if (!ArrayUtils.contains(blockUninstallUserIds, userId1)) { returnCode = deletePackageX(internalPackageName, versionCode, userId1, userFlags, false /*removedBySystem*/); - if (returnCode != PackageManager.DELETE_SUCCEEDED) { + if (returnCode != DELETE_SUCCEEDED) { Slog.w(TAG, "Package delete failed for user " + userId1 + ", returnCode " + returnCode); } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 586e1123fdc4..232ca45cb4aa 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -4400,15 +4400,9 @@ class PackageManagerShellCommand extends ShellCommand { pw.println(" -f: force compilation even if not needed"); pw.println(" -m: select compilation mode"); pw.println(" MODE is one of the dex2oat compiler filters:"); - pw.println(" assume-verified"); - pw.println(" extract"); pw.println(" verify"); - pw.println(" quicken"); - pw.println(" space-profile"); - pw.println(" space"); pw.println(" speed-profile"); pw.println(" speed"); - pw.println(" everything"); pw.println(" -r: select compilation reason"); pw.println(" REASON is one of:"); for (int i = 0; i < PackageManagerServiceCompilerMapping.REASON_STRINGS.length; i++) { diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java index d160740712d3..5312ae6ca84c 100644 --- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java +++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java @@ -69,7 +69,6 @@ final class ReconcilePackageUtils { for (InstallRequest installRequest : installRequests) { installRequest.onReconcileStarted(); - final String installPackageName = installRequest.getParsedPackage().getPackageName(); // add / replace existing with incoming packages combinedPackages.put(installRequest.getScannedPackageSetting().getPackageName(), @@ -84,12 +83,17 @@ final class ReconcilePackageUtils { incomingSharedLibraries, info)) { throw ReconcileFailure.ofInternalError( "Shared Library " + info.getName() - + " is being installed twice in this set!", + + " is being installed twice in this set!", PackageManagerException.INTERNAL_ERROR_SHARED_LIB_INSTALLED_TWICE); } } } + } + for (InstallRequest installRequest : installRequests) { + final String installPackageName = installRequest.getParsedPackage().getPackageName(); + final List<SharedLibraryInfo> allowedSharedLibInfos = + sharedLibraries.getAllowedSharedLibInfos(installRequest); final DeletePackageAction deletePackageAction; // we only want to try to delete for non system apps diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index fdc2affda8e7..741464072fd8 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -4737,9 +4737,9 @@ public class UserManagerService extends IUserManager.Stub { UserManager.USER_OPERATION_ERROR_MAX_USERS); } // Keep logic in sync with getRemainingCreatableUserCount() - if (!isGuest && !isProfile && !isDemo && isUserLimitReached()) { + if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) { // If the user limit has been reached, we cannot add a user (except guest/demo). - // Note that profiles can bypass it in certain circumstances (taken + // Note that managed profiles can bypass it in certain circumstances (taken // into account in the profile check below). throwCheckedUserOperationException( "Cannot add user. Maximum user limit is reached.", diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index ea53ea51b424..3eeafeb52b4b 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -3183,6 +3183,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } break; + case KeyEvent.KEYCODE_LANGUAGE_SWITCH: + if (down && repeatCount == 0) { + int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1; + sendSwitchKeyboardLayout(event, direction); + return key_consumed; + } + break; case KeyEvent.KEYCODE_SPACE: // Handle keyboard layout switching. (META + SPACE) if ((metaState & KeyEvent.META_META_MASK) == 0) { diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 5442b6dc4b09..ad789d830c39 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -2128,7 +2128,7 @@ public final class TvInputManagerService extends SystemService { public void notifyTvMessage(IBinder sessionToken, int type, Bundle data, int userId) { final int callingUid = Binder.getCallingUid(); final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, - userId, "timeShiftEnablePositionTracking"); + userId, "notifyTvmessage"); final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -2136,7 +2136,28 @@ public final class TvInputManagerService extends SystemService { getSessionLocked(sessionToken, callingUid, resolvedUserId) .notifyTvMessage(type, data); } catch (RemoteException | SessionNotFoundException e) { - Slog.e(TAG, "error in timeShiftEnablePositionTracking", e); + Slog.e(TAG, "error in notifyTvMessage", e); + } + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void setTvMessageEnabled(IBinder sessionToken, int type, boolean enabled, + int userId) { + final int callingUid = Binder.getCallingUid(); + final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid, + userId, "setTvMessageEnabled"); + final long identity = Binder.clearCallingIdentity(); + try { + synchronized (mLock) { + try { + getSessionLocked(sessionToken, callingUid, resolvedUserId) + .setTvMessageEnabled(type, enabled); + } catch (RemoteException | SessionNotFoundException e) { + Slog.e(TAG, "error in setTvMessageEnabled", e); } } } finally { diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java index c9eef387eeb2..55060a677c8c 100644 --- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java +++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java @@ -90,7 +90,6 @@ import android.os.ShellCallback; import android.os.SystemClock; import android.os.UserHandle; import android.os.UserManager; -import android.os.storage.StorageManager; import android.service.wallpaper.IWallpaperConnection; import android.service.wallpaper.IWallpaperEngine; import android.service.wallpaper.IWallpaperService; @@ -2210,12 +2209,7 @@ public class WallpaperManagerService extends IWallpaperManager.Stub public ParcelFileDescriptor getWallpaperWithFeature(String callingPkg, String callingFeatureId, IWallpaperManagerCallback cb, final int which, Bundle outParams, int wallpaperUserId, boolean getCropped) { - final boolean hasPrivilege = hasPermission(READ_WALLPAPER_INTERNAL); - if (!hasPrivilege) { - mContext.getSystemService(StorageManager.class).checkPermissionReadImages(true, - Binder.getCallingPid(), Binder.getCallingUid(), callingPkg, callingFeatureId); - } - + checkPermission(READ_WALLPAPER_INTERNAL); wallpaperUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), wallpaperUserId, false, true, "getWallpaper", null); diff --git a/services/core/java/com/android/server/wm/LaunchParamsPersister.java b/services/core/java/com/android/server/wm/LaunchParamsPersister.java index bf511adf0bf9..bb50372ba019 100644 --- a/services/core/java/com/android/server/wm/LaunchParamsPersister.java +++ b/services/core/java/com/android/server/wm/LaunchParamsPersister.java @@ -263,8 +263,8 @@ class LaunchParamsPersister { boolean changed = !Objects.equals(params.mDisplayUniqueId, info.uniqueId); params.mDisplayUniqueId = info.uniqueId; - changed |= params.mWindowingMode != task.getWindowingMode(); - params.mWindowingMode = task.getWindowingMode(); + changed |= params.mWindowingMode != task.getTaskDisplayArea().getWindowingMode(); + params.mWindowingMode = task.getTaskDisplayArea().getWindowingMode(); if (task.mLastNonFullscreenBounds != null) { changed |= !Objects.equals(params.mBounds, task.mLastNonFullscreenBounds); diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java index 5db39fc8434c..980a9418725b 100644 --- a/services/core/java/com/android/server/wm/LetterboxUiController.java +++ b/services/core/java/com/android/server/wm/LetterboxUiController.java @@ -447,6 +447,10 @@ final class LetterboxUiController { @ScreenOrientation int overrideOrientationIfNeeded(@ScreenOrientation int candidate) { + // In some cases (e.g. Kids app) we need to map the candidate orientation to some other + // orientation. + candidate = mActivityRecord.mWmService.mapOrientationRequest(candidate); + if (FALSE.equals(mBooleanPropertyAllowOrientationOverride)) { return candidate; } diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java index b1ca72be87bb..78ee6f95fdba 100644 --- a/services/core/java/com/android/server/wm/Session.java +++ b/services/core/java/com/android/server/wm/Session.java @@ -857,7 +857,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public void grantInputChannel(int displayId, SurfaceControl surface, IWindow window, IBinder hostInputToken, int flags, int privateFlags, int type, - IBinder windowToken, IBinder focusGrantToken, String inputHandleName, + int inputFeatures, IBinder windowToken, IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) { if (hostInputToken == null && !mCanAddInternalSystemWindow) { // Callers without INTERNAL_SYSTEM_WINDOW permission cannot grant input channel to @@ -869,7 +869,7 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { try { mService.grantInputChannel(this, mUid, mPid, displayId, surface, window, hostInputToken, flags, mCanAddInternalSystemWindow ? privateFlags : 0, - type, windowToken, focusGrantToken, inputHandleName, + type, inputFeatures, windowToken, focusGrantToken, inputHandleName, outInputChannel); } finally { Binder.restoreCallingIdentity(identity); @@ -878,11 +878,11 @@ class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { @Override public void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface, - int flags, int privateFlags, Region region) { + int flags, int privateFlags, int inputFeatures, Region region) { final long identity = Binder.clearCallingIdentity(); try { mService.updateInputChannel(channelToken, displayId, surface, flags, - mCanAddInternalSystemWindow ? privateFlags : 0, region); + mCanAddInternalSystemWindow ? privateFlags : 0, inputFeatures, region); } finally { Binder.restoreCallingIdentity(identity); } diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 6882e4c1d557..8c6de8e6f638 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -2244,10 +2244,11 @@ class Task extends TaskFragment { return; } - // Don't persist state if display isn't in freeform mode. Then the task will be launched - // back to its last state in a freeform display when it's launched in a freeform display - // next time. - if (getWindowConfiguration().getDisplayWindowingMode() != WINDOWING_MODE_FREEFORM) { + // Don't persist state if Task Display Area isn't in freeform mode. Then the task will be + // launched back to its last state in a freeform Task Display Area when it's launched in a + // freeform Task Display Area next time. + if (getTaskDisplayArea() == null + || getTaskDisplayArea().getWindowingMode() != WINDOWING_MODE_FREEFORM) { return; } diff --git a/services/core/java/com/android/server/wm/TaskOrganizerController.java b/services/core/java/com/android/server/wm/TaskOrganizerController.java index b13136534de3..93c8c3666706 100644 --- a/services/core/java/com/android/server/wm/TaskOrganizerController.java +++ b/services/core/java/com/android/server/wm/TaskOrganizerController.java @@ -1166,12 +1166,15 @@ class TaskOrganizerController extends ITaskOrganizerController.Stub { } @Override - public void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) { - enforceTaskPermission("setIsIgnoreOrientationRequestDisabled()"); + public void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled, + @Nullable int[] fromOrientations, @Nullable int[] toOrientations) { + enforceTaskPermission("setOrientationRequestPolicy()"); final long origId = Binder.clearCallingIdentity(); try { synchronized (mGlobalLock) { - mService.mWindowManager.setIsIgnoreOrientationRequestDisabled(isDisabled); + mService.mWindowManager + .setOrientationRequestPolicy(isIgnoreOrientationRequestDisabled, + fromOrientations, toOrientations); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 370d304ed324..873a83d5527e 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -278,13 +278,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { setTransientLaunchToChanges(activity); if (restoreBelow != null) { + final Task transientRootTask = activity.getRootTask(); // Collect all visible activities which can be occluded by the transient activity to // make sure they are in the participants so their visibilities can be updated when // finishing transition. ((WindowContainer<?>) restoreBelow.getParent()).forAllTasks(t -> { if (t.isVisibleRequested() && !t.isAlwaysOnTop() && !t.getWindowConfiguration().tasksAreFloating()) { - if (t.isRootTask()) { + if (t.isRootTask() && t != transientRootTask) { mTransientHideTasks.add(t); } if (t.isLeafTask()) { @@ -828,6 +829,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } mLogger.mFinishTimeNs = SystemClock.elapsedRealtimeNanos(); mController.mLoggerHandler.post(mLogger::logOnFinish); + mController.mTransitionTracer.logFinishedTransition(this); // Close the transactions now. They were originally copied to Shell in case we needed to // apply them due to a remote failure. Since we don't need to apply them anymore, free them // immediately. @@ -1044,6 +1046,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId); mState = STATE_ABORT; + mController.mTransitionTracer.logAbortedTransition(this); // Syncengine abort will call through to onTransactionReady() mSyncEngine.abort(mSyncId); mController.dispatchLegacyAppTransitionCancelled(); @@ -1107,12 +1110,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { // needs to be updated for STATE_ABORT. commitVisibleActivities(transaction); + // Fall-back to the default display if there isn't one participating. + final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0) + : mController.mAtm.mRootWindowContainer.getDefaultDisplay(); + if (mState == STATE_ABORT) { mController.abort(this); - // Fall-back to the default display if there isn't one participating. - final DisplayContent dc = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0) - : mController.mAtm.mRootWindowContainer.getDefaultDisplay(); - dc.getPendingTransaction().merge(transaction); + primaryDisplay.getPendingTransaction().merge(transaction); mSyncId = -1; mOverrideOptions = null; cleanUpInternal(); @@ -1124,6 +1128,10 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { mFinishTransaction = mController.mAtm.mWindowManager.mTransactionFactory.get(); mController.moveToPlaying(this); + // Flags must be assigned before calculateTransitionInfo. Otherwise it won't take effect. + if (primaryDisplay.isKeyguardLocked()) { + mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; + } // Check whether the participants were animated from back navigation. final boolean markBackAnimated = mController.mAtm.mBackNavigationController .containsBackAnimationTargets(this); @@ -1137,9 +1145,6 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final DisplayContent dc = mController.mAtm.mRootWindowContainer.getDisplayContent( info.getRoot(i).getDisplayId()); mTargetDisplays.add(dc); - if (dc.isKeyguardLocked()) { - mFlags |= TRANSIT_FLAG_KEYGUARD_LOCKED; - } } if (markBackAnimated) { @@ -1246,8 +1251,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { "Calling onTransitionReady: %s", info); mLogger.mSendTimeNs = SystemClock.elapsedRealtimeNanos(); mLogger.mInfo = info; - mController.mTransitionTracer.logSentTransition( - this, mTargets, mLogger.mCreateTimeNs, mLogger.mSendTimeNs, info); + mController.mTransitionTracer.logSentTransition(this, mTargets, info); mController.getTransitionPlayer().onTransitionReady( mToken, info, transaction, mFinishTransaction); if (Trace.isTagEnabled(TRACE_TAG_WINDOW_MANAGER)) { diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java index 86bb6b58d14c..c74f167c2222 100644 --- a/services/core/java/com/android/server/wm/TransitionController.java +++ b/services/core/java/com/android/server/wm/TransitionController.java @@ -976,6 +976,8 @@ class TransitionController { WindowContainerTransaction mStartWCT; int mSyncId; TransitionInfo mInfo; + ProtoOutputStream mProtoOutputStream = new ProtoOutputStream(); + long mProtoToken; private String buildOnSendLog() { StringBuilder sb = new StringBuilder("Sent Transition #").append(mSyncId) diff --git a/services/core/java/com/android/server/wm/TransitionTracer.java b/services/core/java/com/android/server/wm/TransitionTracer.java index 7b1975d234c4..57c0d65b28ea 100644 --- a/services/core/java/com/android/server/wm/TransitionTracer.java +++ b/services/core/java/com/android/server/wm/TransitionTracer.java @@ -18,14 +18,6 @@ package com.android.server.wm; import static android.os.Build.IS_USER; -import static com.android.server.wm.shell.ChangeInfo.CHANGE_FLAGS; -import static com.android.server.wm.shell.ChangeInfo.HAS_CHANGED; -import static com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE; -import static com.android.server.wm.shell.ChangeInfo.WINDOWING_MODE; -import static com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER; -import static com.android.server.wm.shell.TransitionInfoChange.LAYER_ID; -import static com.android.server.wm.shell.TransitionInfoChange.MODE; -import static com.android.server.wm.shell.TransitionState.CHANGE; import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER; import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_H; import static com.android.server.wm.shell.TransitionTraceProto.MAGIC_NUMBER_L; @@ -66,20 +58,67 @@ public class TransitionTracer { /** * Records key information about a transition that has been sent to Shell to be played. + * More information will be appended to the same proto object once the transition is finished or + * aborted. + * Transition information won't be added to the trace buffer until + * {@link #logFinishedTransition} or {@link #logAbortedTransition} is called for this + * transition. + * * @param transition The transition that has been sent to Shell. * @param targets Information about the target windows of the transition. - * @param createTimeNs System elapsed time (nanoseconds since boot including sleep time) at - * which the transition to be recorded was created. - * @param sendTimeNs System elapsed time (nanoseconds since boot including sleep time) at which - * @param info + * @param info The TransitionInfo send over to Shell to execute the transition. */ public void logSentTransition(Transition transition, ArrayList<ChangeInfo> targets, - long createTimeNs, long sendTimeNs, TransitionInfo info) { - mTraceBuffer.pushSentTransition(transition, targets, createTimeNs, sendTimeNs); + TransitionInfo info) { + // Dump the info to proto that will not be available when the transition finishes or + // is canceled + final ProtoOutputStream outputStream = transition.mLogger.mProtoOutputStream; + transition.mLogger.mProtoToken = outputStream + .start(com.android.server.wm.shell.TransitionTraceProto.FINISHED_TRANSITIONS); + outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID, + transition.getStartTransaction().getId()); + outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID, + transition.getFinishTransaction().getId()); + dumpTransitionTargetsToProto(outputStream, transition, targets); + logTransitionInfo(transition, info); } /** + * Completes the information dumped in {@link #logSentTransition} for a transition + * that has finished or aborted, and add the proto object to the trace buffer. + * + * @param transition The transition that has finished. + */ + public void logFinishedTransition(Transition transition) { + if (transition.mLogger.mProtoToken == 0) { + // Transition finished but never sent, so open token never added + final ProtoOutputStream outputStream = transition.mLogger.mProtoOutputStream; + transition.mLogger.mProtoToken = outputStream + .start(com.android.server.wm.shell.TransitionTraceProto.FINISHED_TRANSITIONS); + } + + // Dump the rest of the transition's info that wasn't dumped during logSentTransition + dumpFinishedTransitionToProto(transition.mLogger.mProtoOutputStream, transition); + transition.mLogger.mProtoOutputStream.end(transition.mLogger.mProtoToken); + mTraceBuffer.pushTransitionProto(transition.mLogger.mProtoOutputStream); + } + + /** + * Same as {@link #logFinishedTransition} but don't add the transition to the trace buffer + * unless actively tracing. + * + * @param transition The transition that has been aborted + */ + public void logAbortedTransition(Transition transition) { + // We don't care about aborted transitions unless actively tracing + if (!mActiveTracingEnabled) { + return; + } + logFinishedTransition(transition); + } + + /** * Records the current state of a transition in the transition trace (if it is running). * @param transition the transition that we want to record the state of. */ @@ -87,7 +126,9 @@ public class TransitionTracer { if (!mActiveTracingEnabled) { return; } - mTraceBuffer.pushTransitionState(transition); + final ProtoOutputStream outputStream = new ProtoOutputStream(); + dumpTransitionStateToProto(outputStream, transition); + mTraceBuffer.pushTransitionState(outputStream); } /** @@ -99,171 +140,180 @@ public class TransitionTracer { if (!mActiveTracingEnabled) { return; } - mTraceBuffer.pushTransitionInfo(transition, info); + final ProtoOutputStream outputStream = new ProtoOutputStream(); + dumpTransitionInfoToProto(outputStream, transition, info); + mTraceBuffer.pushTransitionInfo(outputStream); } - private class TransitionTraceBuffer { - private final TraceBuffer mBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY); - private final TraceBuffer mStateBuffer = new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY); - private final TraceBuffer mTransitionInfoBuffer = - new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY); - - public void pushSentTransition( - Transition transition, - ArrayList<ChangeInfo> targets, - long createTimeNs, - long sendTimeNs - ) { - Trace.beginSection("TransitionTraceBuffer#pushSentTransition"); - final ProtoOutputStream outputStream = new ProtoOutputStream(); - final long transitionToken = outputStream - .start(com.android.server.wm.shell.TransitionTraceProto.SENT_TRANSITIONS); - - if (mActiveTracingEnabled) { - outputStream.write(com.android.server.wm.shell.Transition.ID, - transition.getSyncId()); - } - - outputStream.write(com.android.server.wm.shell.Transition.START_TRANSACTION_ID, - transition.getStartTransaction().getId()); - outputStream.write(com.android.server.wm.shell.Transition.FINISH_TRANSACTION_ID, - transition.getFinishTransaction().getId()); - - outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS, createTimeNs); - outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS, sendTimeNs); + private void dumpTransitionTargetsToProto(ProtoOutputStream outputStream, + Transition transition, ArrayList<ChangeInfo> targets) { + Trace.beginSection("TransitionTracer#dumpTransitionTargetsToProto"); + if (mActiveTracingEnabled) { + outputStream.write(com.android.server.wm.shell.Transition.ID, + transition.getSyncId()); + } - for (int i = 0; i < targets.size(); ++i) { - final long changeToken = outputStream - .start(com.android.server.wm.shell.Transition.TARGETS); + outputStream.write(com.android.server.wm.shell.Transition.TYPE, transition.mType); - final Transition.ChangeInfo target = targets.get(i); + for (int i = 0; i < targets.size(); ++i) { + final long changeToken = outputStream + .start(com.android.server.wm.shell.Transition.TARGETS); - final int mode = target.getTransitMode(target.mContainer); - final int layerId; - if (target.mContainer.mSurfaceControl.isValid()) { - layerId = target.mContainer.mSurfaceControl.getLayerId(); - } else { - layerId = -1; - } + final Transition.ChangeInfo target = targets.get(i); - outputStream.write(com.android.server.wm.shell.Target.MODE, mode); - outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId); + final int mode = target.getTransitMode(target.mContainer); + final int layerId; + if (target.mContainer.mSurfaceControl.isValid()) { + layerId = target.mContainer.mSurfaceControl.getLayerId(); + } else { + layerId = -1; + } - if (mActiveTracingEnabled) { - // What we use in the WM trace - final int windowId = System.identityHashCode(target.mContainer); - outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId); - } + outputStream.write(com.android.server.wm.shell.Target.MODE, mode); + outputStream.write(com.android.server.wm.shell.Target.LAYER_ID, layerId); - outputStream.end(changeToken); + if (mActiveTracingEnabled) { + // What we use in the WM trace + final int windowId = System.identityHashCode(target.mContainer); + outputStream.write(com.android.server.wm.shell.Target.WINDOW_ID, windowId); } - outputStream.end(transitionToken); - mBuffer.add(outputStream); - - Trace.endSection(); + outputStream.end(changeToken); } - private void pushTransitionState(Transition transition) { - Trace.beginSection("TransitionTraceBuffer#pushTransitionState"); - final ProtoOutputStream outputStream = new ProtoOutputStream(); - final long stateToken = outputStream - .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_STATES); + Trace.endSection(); + } - outputStream.write(com.android.server.wm.shell.TransitionState.TIME_NS, - SystemClock.elapsedRealtimeNanos()); - outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_ID, - transition.getSyncId()); - outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_TYPE, - transition.mType); - outputStream.write(com.android.server.wm.shell.TransitionState.STATE, - transition.getState()); - outputStream.write(com.android.server.wm.shell.TransitionState.FLAGS, - transition.getFlags()); - - for (int i = 0; i < transition.mChanges.size(); ++i) { - final WindowContainer window = transition.mChanges.keyAt(i); - final ChangeInfo changeInfo = transition.mChanges.valueAt(i); - writeChange(outputStream, window, changeInfo); - } + private void dumpFinishedTransitionToProto( + ProtoOutputStream outputStream, + Transition transition + ) { + Trace.beginSection("TransitionTracer#dumpFinishedTransitionToProto"); - for (int i = 0; i < transition.mChanges.size(); ++i) { - final WindowContainer window = transition.mChanges.keyAt(i); - final ChangeInfo changeInfo = transition.mChanges.valueAt(i); - writeChange(outputStream, window, changeInfo); - } + outputStream.write(com.android.server.wm.shell.Transition.CREATE_TIME_NS, + transition.mLogger.mCreateTimeNs); + outputStream.write(com.android.server.wm.shell.Transition.SEND_TIME_NS, + transition.mLogger.mSendTimeNs); + outputStream.write(com.android.server.wm.shell.Transition.FINISH_TIME_NS, + transition.mLogger.mFinishTimeNs); - for (int i = 0; i < transition.mParticipants.size(); ++i) { - final WindowContainer window = transition.mParticipants.valueAt(i); - window.writeIdentifierToProto(outputStream, - com.android.server.wm.shell.TransitionState.PARTICIPANTS); - } + Trace.endSection(); + } - outputStream.end(stateToken); + private void dumpTransitionStateToProto(ProtoOutputStream outputStream, Transition transition) { + Trace.beginSection("TransitionTracer#dumpTransitionStateToProto"); + + final long stateToken = outputStream + .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_STATES); + + outputStream.write(com.android.server.wm.shell.TransitionState.TIME_NS, + SystemClock.elapsedRealtimeNanos()); + outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_ID, + transition.getSyncId()); + outputStream.write(com.android.server.wm.shell.TransitionState.TRANSITION_TYPE, + transition.mType); + outputStream.write(com.android.server.wm.shell.TransitionState.STATE, + transition.getState()); + outputStream.write(com.android.server.wm.shell.TransitionState.FLAGS, + transition.getFlags()); + + for (int i = 0; i < transition.mChanges.size(); ++i) { + final WindowContainer window = transition.mChanges.keyAt(i); + final ChangeInfo changeInfo = transition.mChanges.valueAt(i); + dumpChangeInfoToProto(outputStream, window, changeInfo); + } - mStateBuffer.add(outputStream); - Trace.endSection(); + for (int i = 0; i < transition.mParticipants.size(); ++i) { + final WindowContainer window = transition.mParticipants.valueAt(i); + window.writeIdentifierToProto(outputStream, + com.android.server.wm.shell.TransitionState.PARTICIPANTS); } - private void pushTransitionInfo(Transition transition, TransitionInfo info) { - Trace.beginSection("TransitionTraceBuffer#pushTransitionInfo"); - final ProtoOutputStream outputStream = new ProtoOutputStream(); - final long transitionInfoToken = outputStream - .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_INFO); + outputStream.end(stateToken); + Trace.endSection(); + } - outputStream.write(com.android.server.wm.shell.TransitionInfo.TRANSITION_ID, - transition.getSyncId()); - for (int i = 0; i < info.getChanges().size(); ++i) { - TransitionInfo.Change change = info.getChanges().get(i); - writeTransitionInfoChange(outputStream, change); - } + private void dumpChangeInfoToProto(ProtoOutputStream outputStream, WindowContainer window, + ChangeInfo changeInfo) { + Trace.beginSection("TransitionTraceBuffer#writeChange"); + final long changeEntryToken = + outputStream.start(com.android.server.wm.shell.TransitionState.CHANGE); + + final int transitMode = changeInfo.getTransitMode(window); + final boolean hasChanged = changeInfo.hasChanged(); + final int changeFlags = changeInfo.getChangeFlags(window); + final int windowingMode = changeInfo.mWindowingMode; + + outputStream.write(com.android.server.wm.shell.ChangeInfo.TRANSIT_MODE, transitMode); + outputStream.write(com.android.server.wm.shell.ChangeInfo.HAS_CHANGED, hasChanged); + outputStream.write(com.android.server.wm.shell.ChangeInfo.CHANGE_FLAGS, changeFlags); + outputStream.write(com.android.server.wm.shell.ChangeInfo.WINDOWING_MODE, windowingMode); + window.writeIdentifierToProto( + outputStream, com.android.server.wm.shell.ChangeInfo.WINDOW_IDENTIFIER); + + outputStream.end(changeEntryToken); + Trace.endSection(); + } - outputStream.end(transitionInfoToken); - mTransitionInfoBuffer.add(outputStream); - Trace.endSection(); + private void dumpTransitionInfoToProto(ProtoOutputStream outputStream, + Transition transition, TransitionInfo info) { + Trace.beginSection("TransitionTracer#dumpTransitionInfoToProto"); + final long transitionInfoToken = outputStream + .start(com.android.server.wm.shell.TransitionTraceProto.TRANSITION_INFO); + + outputStream.write(com.android.server.wm.shell.TransitionInfo.TRANSITION_ID, + transition.getSyncId()); + for (int i = 0; i < info.getChanges().size(); ++i) { + TransitionInfo.Change change = info.getChanges().get(i); + dumpTransitionInfoChangeToProto(outputStream, change); } - private void writeChange(ProtoOutputStream outputStream, WindowContainer window, - ChangeInfo changeInfo) { - Trace.beginSection("TransitionTraceBuffer#writeChange"); - final long changeEntryToken = outputStream.start(CHANGE); + outputStream.end(transitionInfoToken); + Trace.endSection(); + } - final int transitMode = changeInfo.getTransitMode(window); - final boolean hasChanged = changeInfo.hasChanged(); - final int changeFlags = changeInfo.getChangeFlags(window); - final int windowingMode = changeInfo.mWindowingMode; + private void dumpTransitionInfoChangeToProto( + ProtoOutputStream outputStream, + TransitionInfo.Change change + ) { + Trace.beginSection("TransitionTracer#dumpTransitionInfoChangeToProto"); + final long changeEntryToken = outputStream + .start(com.android.server.wm.shell.TransitionInfo.CHANGE); - outputStream.write(TRANSIT_MODE, transitMode); - outputStream.write(HAS_CHANGED, hasChanged); - outputStream.write(CHANGE_FLAGS, changeFlags); - outputStream.write(WINDOWING_MODE, windowingMode); - window.writeIdentifierToProto(outputStream, WINDOW_IDENTIFIER); + outputStream.write(com.android.server.wm.shell.TransitionInfoChange.LAYER_ID, + change.getLeash().getLayerId()); + outputStream.write(com.android.server.wm.shell.TransitionInfoChange.MODE, change.getMode()); - outputStream.end(changeEntryToken); - Trace.endSection(); - } + outputStream.end(changeEntryToken); + Trace.endSection(); + } + + private class TransitionTraceBuffer { + private final TraceBuffer mTransitionBuffer = new TraceBuffer(ALWAYS_ON_TRACING_CAPACITY); + private final TraceBuffer mStateBuffer = new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY); + private final TraceBuffer mTransitionInfoBuffer = + new TraceBuffer(ACTIVE_TRACING_BUFFER_CAPACITY); - private void writeTransitionInfoChange( - ProtoOutputStream outputStream, - TransitionInfo.Change change - ) { - Trace.beginSection("TransitionTraceBuffer#writeTransitionInfoChange"); - final long changeEntryToken = outputStream - .start(com.android.server.wm.shell.TransitionInfo.CHANGE); + private void pushTransitionProto(ProtoOutputStream outputStream) { + mTransitionBuffer.add(outputStream); + } - outputStream.write(LAYER_ID, change.getLeash().getLayerId()); - outputStream.write(MODE, change.getMode()); + private void pushTransitionState(ProtoOutputStream outputStream) { + mStateBuffer.add(outputStream); + } - outputStream.end(changeEntryToken); - Trace.endSection(); + private void pushTransitionInfo(ProtoOutputStream outputStream) { + mTransitionInfoBuffer.add(outputStream); } public void writeToFile(File file, ProtoOutputStream proto) throws IOException { - mBuffer.writeTraceToFile(file, proto); + mTransitionBuffer.writeTraceToFile(file, proto); } public void reset() { - mBuffer.resetBuffer(); + mTransitionBuffer.resetBuffer(); + mStateBuffer.resetBuffer(); + mTransitionInfoBuffer.resetBuffer(); } } @@ -280,7 +330,7 @@ public class TransitionTracer { LogAndPrintln.i(pw, "Starting shell transition trace."); synchronized (mEnabledLock) { mActiveTracingEnabled = true; - mTraceBuffer.mBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY); + mTraceBuffer.mTransitionBuffer.setCapacity(ACTIVE_TRACING_BUFFER_CAPACITY); mTraceBuffer.reset(); } Trace.endSection(); @@ -309,7 +359,8 @@ public class TransitionTracer { synchronized (mEnabledLock) { mActiveTracingEnabled = false; writeTraceToFileLocked(pw, outputFile); - mTraceBuffer.mBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY); + mTraceBuffer.reset(); + mTraceBuffer.mTransitionBuffer.setCapacity(ALWAYS_ON_TRACING_CAPACITY); } Trace.endSection(); } diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 8582356f4d9e..f7641f5bfb54 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -237,6 +237,7 @@ import android.util.MergedConfiguration; import android.util.Pair; import android.util.Slog; import android.util.SparseBooleanArray; +import android.util.SparseIntArray; import android.util.TimeUtils; import android.util.TypedValue; import android.util.proto.ProtoOutputStream; @@ -594,6 +595,13 @@ public class WindowManagerService extends IWindowManager.Stub /** List of window currently causing non-system overlay windows to be hidden. */ private ArrayList<WindowState> mHidingNonSystemOverlayWindows = new ArrayList<>(); + /** + * In some cases (e.g. when {@link R.bool.config_reverseDefaultRotation} has value + * {@value true}) we need to map some orientation to others. This {@link SparseIntArray} + * contains the relation between the source orientation and the one to use. + */ + private final SparseIntArray mOrientationMapping = new SparseIntArray(); + final AccessibilityController mAccessibilityController; private RecentsAnimationController mRecentsAnimationController; @@ -4111,25 +4119,52 @@ public class WindowManagerService extends IWindowManager.Stub /** * Controls whether ignore orientation request logic in {@link DisplayArea} is disabled - * at runtime. + * at runtime and how to optionally map some requested orientations to others. * * <p>Note: this assumes that {@link #mGlobalLock} is held by the caller. * - * @param isDisabled when {@code true}, the system always ignores the value of {@link - * DisplayArea#getIgnoreOrientationRequest} and app requested orientation is - * respected. + * @param isIgnoreOrientationRequestDisabled when {@code true}, the system always ignores the + * value of {@link DisplayArea#getIgnoreOrientationRequest} and app requested + * orientation is respected. + * @param fromOrientations The orientations we want to map to the correspondent orientations + * in toOrientation. + * @param toOrientations The orientations we map to the ones in fromOrientations at the same + * index */ - void setIsIgnoreOrientationRequestDisabled(boolean isDisabled) { - if (isDisabled == mIsIgnoreOrientationRequestDisabled) { + void setOrientationRequestPolicy(boolean isIgnoreOrientationRequestDisabled, + @Nullable int[] fromOrientations, @Nullable int[] toOrientations) { + mOrientationMapping.clear(); + if (fromOrientations != null && toOrientations != null + && fromOrientations.length == toOrientations.length) { + for (int i = 0; i < fromOrientations.length; i++) { + mOrientationMapping.put(fromOrientations[i], toOrientations[i]); + } + } + if (isIgnoreOrientationRequestDisabled == mIsIgnoreOrientationRequestDisabled) { return; } - mIsIgnoreOrientationRequestDisabled = isDisabled; + mIsIgnoreOrientationRequestDisabled = isIgnoreOrientationRequestDisabled; for (int i = mRoot.getChildCount() - 1; i >= 0; i--) { mRoot.getChildAt(i).onIsIgnoreOrientationRequestDisabledChanged(); } } /** + * When {@link mIsIgnoreOrientationRequestDisabled} is {@value true} this method returns the + * orientation to use in place of the one in input. It returns the same requestedOrientation in + * input otherwise. + * + * @param requestedOrientation The orientation that can be mapped. + * @return The orientation to use in place of requestedOrientation. + */ + int mapOrientationRequest(int requestedOrientation) { + if (!mIsIgnoreOrientationRequestDisabled) { + return requestedOrientation; + } + return mOrientationMapping.get(requestedOrientation, requestedOrientation); + } + + /** * Whether the system ignores the value of {@link DisplayArea#getIgnoreOrientationRequest} and * app requested orientation is respected. * @@ -8571,8 +8606,8 @@ public class WindowManagerService extends IWindowManager.Stub */ void grantInputChannel(Session session, int callingUid, int callingPid, int displayId, SurfaceControl surface, IWindow window, IBinder hostInputToken, - int flags, int privateFlags, int type, IBinder windowToken, IBinder focusGrantToken, - String inputHandleName, InputChannel outInputChannel) { + int flags, int privateFlags, int inputFeatures, int type, IBinder windowToken, + IBinder focusGrantToken, String inputHandleName, InputChannel outInputChannel) { final int sanitizedType = sanitizeWindowType(session, displayId, windowToken, type); final InputApplicationHandle applicationHandle; final String name; @@ -8589,7 +8624,7 @@ public class WindowManagerService extends IWindowManager.Stub } updateInputChannel(clientChannel.getToken(), callingUid, callingPid, displayId, surface, - name, applicationHandle, flags, privateFlags, sanitizedType, + name, applicationHandle, flags, privateFlags, inputFeatures, sanitizedType, null /* region */, window); clientChannel.copyTo(outInputChannel); @@ -8630,13 +8665,14 @@ public class WindowManagerService extends IWindowManager.Stub private void updateInputChannel(IBinder channelToken, int callingUid, int callingPid, int displayId, SurfaceControl surface, String name, InputApplicationHandle applicationHandle, int flags, - int privateFlags, int type, Region region, IWindow window) { + int privateFlags, int inputFeatures, int type, Region region, IWindow window) { final InputWindowHandle h = new InputWindowHandle(applicationHandle, displayId); h.token = channelToken; h.setWindowToken(window); h.name = name; flags = sanitizeFlagSlippery(flags, name, callingUid, callingPid); + inputFeatures = sanitizeSpyWindow(inputFeatures, name, callingUid, callingPid); final int sanitizedLpFlags = (flags & (FLAG_NOT_TOUCHABLE | FLAG_SLIPPERY | LayoutParams.FLAG_NOT_FOCUSABLE)) @@ -8646,7 +8682,7 @@ public class WindowManagerService extends IWindowManager.Stub // Do not allow any input features to be set without sanitizing them first. h.inputConfig = InputConfigAdapter.getInputConfigFromWindowParams( - type, sanitizedLpFlags, 0 /*inputFeatures*/); + type, sanitizedLpFlags, inputFeatures); if ((flags & LayoutParams.FLAG_NOT_FOCUSABLE) != 0) { @@ -8683,7 +8719,7 @@ public class WindowManagerService extends IWindowManager.Stub * is undefined. */ void updateInputChannel(IBinder channelToken, int displayId, SurfaceControl surface, - int flags, int privateFlags, Region region) { + int flags, int privateFlags, int inputFeatures, Region region) { final InputApplicationHandle applicationHandle; final String name; final EmbeddedWindowController.EmbeddedWindow win; @@ -8698,7 +8734,8 @@ public class WindowManagerService extends IWindowManager.Stub } updateInputChannel(channelToken, win.mOwnerUid, win.mOwnerPid, displayId, surface, name, - applicationHandle, flags, privateFlags, win.mWindowType, region, win.mClient); + applicationHandle, flags, privateFlags, inputFeatures, win.mWindowType, region, + win.mClient); } /** Return whether layer tracing is enabled */ diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp index da44da4f2838..a5b1943c6b42 100644 --- a/services/core/jni/com_android_server_input_InputManagerService.cpp +++ b/services/core/jni/com_android_server_input_InputManagerService.cpp @@ -379,7 +379,7 @@ private: jobject mServiceObj; sp<Looper> mLooper; - Mutex mLock; + std::mutex mLock; struct Locked { // Display size information. std::vector<DisplayViewport> viewports{}; @@ -469,7 +469,7 @@ void NativeInputManager::dump(std::string& dump) { dump += StringPrintf(INDENT "Interactive: %s\n", toString(mInteractive.load())); } { - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); dump += StringPrintf(INDENT "System UI Lights Out: %s\n", toString(mLocked.systemUiLightsOut)); dump += StringPrintf(INDENT "Pointer Speed: %" PRId32 "\n", mLocked.pointerSpeed); @@ -532,7 +532,7 @@ void NativeInputManager::setDisplayViewports(JNIEnv* env, jobjectArray viewportO } { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); mLocked.viewports = viewports; std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller != nullptr) { @@ -669,7 +669,7 @@ void NativeInputManager::getReaderConfiguration(InputReaderConfiguration* outCon } { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); outConfig->pointerVelocityControlParameters.scale = exp2f(mLocked.pointerSpeed * POINTER_SPEED_EXPONENT); @@ -717,7 +717,7 @@ std::unordered_map<std::string, T> NativeInputManager::readMapFromInterleavedJav std::shared_ptr<PointerControllerInterface> NativeInputManager::obtainPointerController( int32_t /* deviceId */) { ATRACE_CALL(); - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller == nullptr) { @@ -1065,7 +1065,7 @@ void NativeInputManager::setInputDispatchMode(bool enabled, bool frozen) { } void NativeInputManager::setSystemUiLightsOut(bool lightsOut) { - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.systemUiLightsOut != lightsOut) { mLocked.systemUiLightsOut = lightsOut; @@ -1085,7 +1085,7 @@ void NativeInputManager::updateInactivityTimeoutLocked() REQUIRES(mLock) { void NativeInputManager::setPointerDisplayId(int32_t displayId) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.pointerDisplayId == displayId) { return; @@ -1101,7 +1101,7 @@ void NativeInputManager::setPointerDisplayId(int32_t displayId) { void NativeInputManager::setPointerSpeed(int32_t speed) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.pointerSpeed == speed) { return; @@ -1117,7 +1117,7 @@ void NativeInputManager::setPointerSpeed(int32_t speed) { void NativeInputManager::setPointerAcceleration(float acceleration) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.pointerAcceleration == acceleration) { return; @@ -1133,7 +1133,7 @@ void NativeInputManager::setPointerAcceleration(float acceleration) { void NativeInputManager::setTouchpadPointerSpeed(int32_t speed) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.touchpadPointerSpeed == speed) { return; @@ -1149,7 +1149,7 @@ void NativeInputManager::setTouchpadPointerSpeed(int32_t speed) { void NativeInputManager::setTouchpadNaturalScrollingEnabled(bool enabled) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.touchpadNaturalScrollingEnabled == enabled) { return; @@ -1165,7 +1165,7 @@ void NativeInputManager::setTouchpadNaturalScrollingEnabled(bool enabled) { void NativeInputManager::setTouchpadTapToClickEnabled(bool enabled) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.touchpadTapToClickEnabled == enabled) { return; @@ -1181,7 +1181,7 @@ void NativeInputManager::setTouchpadTapToClickEnabled(bool enabled) { void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.touchpadRightClickZoneEnabled == enabled) { return; @@ -1197,7 +1197,7 @@ void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) { void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); auto it = mLocked.disabledInputDevices.find(deviceId); bool currentlyEnabled = it == mLocked.disabledInputDevices.end(); @@ -1215,7 +1215,7 @@ void NativeInputManager::setInputDeviceEnabled(uint32_t deviceId, bool enabled) void NativeInputManager::setShowTouches(bool enabled) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.showTouches == enabled) { return; @@ -1243,7 +1243,7 @@ void NativeInputManager::reloadCalibration() { } void NativeInputManager::setPointerIconType(PointerIconStyle iconId) { - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller != nullptr) { controller->updatePointerIcon(iconId); @@ -1251,7 +1251,7 @@ void NativeInputManager::setPointerIconType(PointerIconStyle iconId) { } void NativeInputManager::reloadPointerIcons() { - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller != nullptr) { controller->reloadPointerResources(); @@ -1259,7 +1259,7 @@ void NativeInputManager::reloadPointerIcons() { } void NativeInputManager::setCustomPointerIcon(const SpriteIcon& icon) { - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); std::shared_ptr<PointerController> controller = mLocked.pointerController.lock(); if (controller != nullptr) { controller->setCustomPointerIcon(icon); @@ -1522,7 +1522,7 @@ void NativeInputManager::onPointerDownOutsideFocus(const sp<IBinder>& touchedTok void NativeInputManager::setPointerCapture(const PointerCaptureRequest& request) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.pointerCaptureRequest == request) { return; @@ -1633,7 +1633,7 @@ std::optional<std::string> NativeInputManager::getBluetoothAddress(int32_t devic void NativeInputManager::setStylusButtonMotionEventsEnabled(bool enabled) { { // acquire lock - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); if (mLocked.stylusButtonMotionEventsEnabled == enabled) { return; @@ -1657,7 +1657,7 @@ bool NativeInputManager::isPerDisplayTouchModeEnabled() { } FloatPoint NativeInputManager::getMouseCursorPosition() { - AutoMutex _l(mLock); + std::scoped_lock _l(mLock); const auto pc = mLocked.pointerController.lock(); if (!pc) return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION}; diff --git a/services/core/jni/gnss/GnssConfiguration.cpp b/services/core/jni/gnss/GnssConfiguration.cpp index 3677641127f3..b57e451264a4 100644 --- a/services/core/jni/gnss/GnssConfiguration.cpp +++ b/services/core/jni/gnss/GnssConfiguration.cpp @@ -67,7 +67,7 @@ GnssConfiguration::GnssConfiguration(const sp<IGnssConfiguration>& iGnssConfigur : mIGnssConfiguration(iGnssConfiguration) {} jobject GnssConfiguration::getVersion(JNIEnv* env) { - return createHalInterfaceVersionJavaObject(env, 3, 0); + return createHalInterfaceVersionJavaObject(env, 3, mIGnssConfiguration->getInterfaceVersion()); } jboolean GnssConfiguration::setEmergencySuplPdn(jint enable) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 720533f6a389..303de129d004 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -77,6 +77,7 @@ import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WALLPAPER; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIFI; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WINDOWS; import static android.Manifest.permission.MANAGE_DEVICE_POLICY_WIPE_DATA; +import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; import static android.Manifest.permission.QUERY_ADMIN_POLICY; import static android.Manifest.permission.REQUEST_PASSWORD_COMPLEXITY; import static android.Manifest.permission.SET_TIME; @@ -3579,6 +3580,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { mInjector.binderWithCleanCallingIdentity(() -> mInjector.getPackageManagerInternal().setOwnerProtectedPackages( targetUserId, protectedPackages)); + mUsageStatsManagerInternal.setAdminProtectedPackages(new ArraySet(protectedPackages), + targetUserId); } void handleUnlockUser(int userId) { @@ -3965,7 +3968,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } Objects.requireNonNull(adminReceiver, "ComponentName is null"); Preconditions.checkCallAuthorization(isAdb(getCallerIdentity()) - || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS), + || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS), "Caller must be shell or hold MANAGE_PROFILE_AND_DEVICE_OWNERS to call " + "forceRemoveActiveAdmin"); mInjector.binderWithCleanCallingIdentity(() -> { @@ -9479,7 +9482,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Preconditions.checkCallAuthorization( isDefaultDeviceOwner(caller) || canManageUsers(caller) || isFinancedDeviceOwner( caller) || hasCallingOrSelfPermission( - permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mOwners.hasDeviceOwner(); } @@ -9648,7 +9651,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (!callingUserOnly) { Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) - || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); } synchronized (getLockObject()) { if (!mOwners.hasDeviceOwner()) { @@ -9698,7 +9701,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) - || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); synchronized (getLockObject()) { if (!mOwners.hasDeviceOwner()) { @@ -10102,7 +10105,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(canManageUsers(caller) - || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); if (userHandle != caller.getUserId()) { Preconditions.checkCallAuthorization(canManageUsers(caller) @@ -10120,7 +10123,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); final CallerIdentity caller = getCallerIdentity(); final long id = mInjector.binderClearCallingIdentity(); @@ -10433,7 +10436,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) - || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); return getProfileOwnerNameUnchecked(userHandle); } @@ -10640,7 +10643,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); if ((mIsWatch || hasUserSetupCompleted(userHandle))) { Preconditions.checkState(isSystemUid(caller), @@ -10664,7 +10667,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { boolean hasIncompatibleAccountsOrNonAdb) { if (!isAdb(caller)) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); } final int code = checkDeviceOwnerProvisioningPreConditionLocked(owner, @@ -11777,6 +11780,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization(canManageUsers(caller) || canQueryAdminPolicy(caller)); + // Move AccessibilityManager out of lock to prevent potential deadlock + final List<AccessibilityServiceInfo> installedServices; + long id = mInjector.binderClearCallingIdentity(); + try { + UserInfo user = getUserInfo(userId); + if (user.isManagedProfile()) { + userId = user.profileGroupId; + } + installedServices = withAccessibilityManager(userId, + AccessibilityManager::getInstalledAccessibilityServiceList); + } finally { + mInjector.binderRestoreCallingIdentity(id); + } + synchronized (getLockObject()) { List<String> result = null; // If we have multiple profiles we return the intersection of the @@ -11803,27 +11820,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // If we have a permitted list add all system accessibility services. if (result != null) { - long id = mInjector.binderClearCallingIdentity(); - try { - UserInfo user = getUserInfo(userId); - if (user.isManagedProfile()) { - userId = user.profileGroupId; - } - final List<AccessibilityServiceInfo> installedServices = - withAccessibilityManager(userId, - AccessibilityManager::getInstalledAccessibilityServiceList); - - if (installedServices != null) { - for (AccessibilityServiceInfo service : installedServices) { - ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; - ApplicationInfo applicationInfo = serviceInfo.applicationInfo; - if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { - result.add(serviceInfo.packageName); - } + if (installedServices != null) { + for (AccessibilityServiceInfo service : installedServices) { + ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo; + ApplicationInfo applicationInfo = serviceInfo.applicationInfo; + if ((applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + result.add(serviceInfo.packageName); } } - } finally { - mInjector.binderRestoreCallingIdentity(id); } } @@ -15921,7 +15925,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { private boolean canWriteCredentialManagerPolicy(CallerIdentity caller) { return (isProfileOwner(caller) && isManagedProfile(caller.getUserId())) || isDefaultDeviceOwner(caller) - || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS); + || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS); } @Override @@ -16424,7 +16428,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { Objects.requireNonNull(packageName, "packageName is null"); final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); long originalId = mInjector.binderClearCallingIdentity(); try { @@ -17157,7 +17161,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // Only adb or system apps with the right permission can mark a profile owner on // organization-owned device. if (!(isAdb(caller) || hasCallingPermission(permission.MARK_DEVICE_ORGANIZATION_OWNED) - || hasCallingPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS))) { + || hasCallingPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS))) { throw new SecurityException( "Only the system can mark a profile owner of organization-owned device."); } @@ -17758,7 +17762,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void forceUpdateUserSetupComplete(@UserIdInt int userId) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); boolean isUserCompleted = mInjector.settingsSecureGetIntForUser( Settings.Secure.USER_SETUP_COMPLETE, 0, userId) != 0; @@ -18696,7 +18700,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public List<String> getDisallowedSystemApps(ComponentName admin, int userId, String provisioningAction) throws RemoteException { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); return new ArrayList<>( mOverlayPackagesProvider.getNonRequiredApps(admin, userId, provisioningAction)); @@ -19513,7 +19517,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) - || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); long id = mInjector.binderClearCallingIdentity(); try { @@ -19540,7 +19544,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return false; } Preconditions.checkCallAuthorization(canManageUsers(getCallerIdentity()) - || hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + || hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mInjector.binderWithCleanCallingIdentity(() -> isUnattendedManagedKioskUnchecked()); } @@ -20520,7 +20524,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void clearOrganizationIdForUser(int userHandle) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); synchronized (getLockObject()) { final ActiveAdmin owner = getDeviceOrProfileOwnerAdminLocked(userHandle); @@ -20542,7 +20546,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(callerPackage); Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); provisioningParams.logParams(callerPackage); @@ -20640,7 +20644,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void finalizeWorkProfileProvisioning(UserHandle managedProfileUser, Account migratedAccount) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); if (!isManagedProfile(managedProfileUser.getIdentifier())) { throw new IllegalStateException("Given user is not a managed profile"); @@ -20742,15 +20746,65 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } private boolean isCallerDevicePolicyManagementRoleHolder(CallerIdentity caller) { + return doesCallerHoldRole(caller, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT); + } + + private boolean isCallerSystemSupervisionRoleHolder(CallerIdentity caller) { + return doesCallerHoldRole(caller, RoleManager.ROLE_SYSTEM_SUPERVISION); + } + + /** + * Check if the caller is holding the given role on the calling user. + * + * @param caller the caller you wish to check + * @param role the name of the role to check for. + * @return {@code true} if the caller holds the role, {@code false} otherwise. + */ + private boolean doesCallerHoldRole(CallerIdentity caller, String role) { int callerUid = caller.getUid(); - String devicePolicyManagementRoleHolderPackageName = - getRoleHolderPackageName(mContext, RoleManager.ROLE_DEVICE_POLICY_MANAGEMENT); + String roleHolderPackageName = + getRoleHolderPackageNameOnUser(role, caller.getUserId()); int roleHolderUid = mInjector.getPackageManagerInternal().getPackageUid( - devicePolicyManagementRoleHolderPackageName, 0, caller.getUserId()); + roleHolderPackageName, 0, caller.getUserId()); return callerUid == roleHolderUid; } + /** + * Return the package name of the role holder on the given user. + * + * <p>If the userId passed in is {@link UserHandle.USER_ALL} then every user will be checked and + * the package name of the role holder on the first user where there is a role holder is + * returned. + * + * @param role the name of the role to check for. + * @param userId the userId to check for the role holder on. + * @return the package name of the role holder + */ + @Nullable + private String getRoleHolderPackageNameOnUser(String role, int userId) { + RoleManager roleManager = mContext.getSystemService(RoleManager.class); + + // Clear calling identity as the RoleManager APIs require privileged permissions. + return mInjector.binderWithCleanCallingIdentity(() -> { + List<UserInfo> users; + // Interpret USER_ALL as meaning "any" user. + if (userId == UserHandle.USER_ALL) { + users = mInjector.getUserManagerInternal().getUsers(/*excludeDying=*/ true); + } else { + users = List.of(new UserInfo(userId, /*name=*/ null, /*flags=*/ 0)); + } + for (UserInfo user : users) { + List<String> roleHolders = + roleManager.getRoleHoldersAsUser(role, user.getUserHandle()); + if (!roleHolders.isEmpty()) { + return roleHolders.get(0); + } + } + return null; + }); + } + private void resetInteractAcrossProfilesAppOps(@UserIdInt int userId) { mInjector.getCrossProfileApps(userId).clearInteractAcrossProfilesAppOps(); pregrantDefaultInteractAcrossProfilesAppOps(userId); @@ -20977,7 +21031,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { final CallerIdentity caller = getCallerIdentity(); Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS) + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS) || (hasCallingOrSelfPermission(permission.PROVISION_DEMO_DEVICE) && provisioningParams.isDemoDevice())); @@ -21165,7 +21219,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void resetDefaultCrossProfileIntentFilters(@UserIdInt int userId) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); mInjector.binderWithCleanCallingIdentity(() -> { try { @@ -21304,7 +21358,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setDeviceOwnerType(@NonNull ComponentName admin, @DeviceOwnerType int deviceOwnerType) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( - permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + MANAGE_PROFILE_AND_DEVICE_OWNERS)); synchronized (getLockObject()) { setDeviceOwnerTypeLocked(admin, deviceOwnerType); @@ -21692,7 +21746,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public boolean isDpcDownloaded() { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( - android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + MANAGE_PROFILE_AND_DEVICE_OWNERS)); ContentResolver cr = mContext.getContentResolver(); @@ -21704,7 +21758,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setDpcDownloaded(boolean downloaded) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( - android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + MANAGE_PROFILE_AND_DEVICE_OWNERS)); int setTo = downloaded ? 1 : 0; @@ -21882,7 +21936,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } if (!isProfileOwnerOfOrganizationOwnedDevice( admin.info.getComponent(), user.getIdentifier()) - && !isDeviceOwner(admin)) { + && !isDeviceOwner(admin) + && !(isProfileOwner(admin.info.getComponent(), user.getIdentifier()) + && admin.getUserHandle().isSystem())) { continue; } // Don't send the broadcast twice if the DPC is the same package as the @@ -21995,7 +22051,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public List<UserHandle> getPolicyManagedProfiles(@NonNull UserHandle user) { Preconditions.checkCallAuthorization(hasCallingOrSelfPermission( - android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + MANAGE_PROFILE_AND_DEVICE_OWNERS)); int userId = user.getIdentifier(); return mInjector.binderWithCleanCallingIdentity(() -> { List<UserInfo> userProfiles = mUserManager.getProfiles(userId); @@ -22657,7 +22713,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setOverrideKeepProfilesRunning(boolean enabled) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); mKeepProfilesRunning = enabled; Slog.i(LOG_TAG, "Keep profiles running overridden to: " + enabled); } @@ -22924,14 +22980,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public DevicePolicyState getDevicePolicyState() { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mInjector.binderWithCleanCallingIdentity(mDevicePolicyEngine::getDevicePolicyState); } @Override public boolean triggerDevicePolicyEngineMigration(boolean forceMigration) { Preconditions.checkCallAuthorization( - hasCallingOrSelfPermission(permission.MANAGE_PROFILE_AND_DEVICE_OWNERS)); + hasCallingOrSelfPermission(MANAGE_PROFILE_AND_DEVICE_OWNERS)); return mInjector.binderWithCleanCallingIdentity(() -> { boolean canForceMigration = forceMigration && !hasNonTestOnlyActiveAdmins(); if (!canForceMigration && !shouldMigrateToDevicePolicyEngine()) { @@ -23290,4 +23346,28 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { // if the policy engine was ever used? return !mDevicePolicyEngine.hasActivePolicies(); } + + @Override + public boolean isDeviceFinanced(String callerPackageName) { + CallerIdentity caller = getCallerIdentity(callerPackageName); + Preconditions.checkCallAuthorization(isDeviceOwner(caller) + || isProfileOwnerOfOrganizationOwnedDevice(caller) + || isProfileOwnerOnUser0(caller) + || isCallerDevicePolicyManagementRoleHolder(caller) + || isCallerSystemSupervisionRoleHolder(caller)); + return getFinancedDeviceKioskRoleHolderOnAnyUser() != null; + }; + + @Override + public String getFinancedDeviceKioskRoleHolder(String callerPackageName) { + CallerIdentity caller = getCallerIdentity(callerPackageName); + enforcePermission(MANAGE_PROFILE_AND_DEVICE_OWNERS, caller.getPackageName(), + caller.getUserId()); + return getFinancedDeviceKioskRoleHolderOnAnyUser(); + } + + private String getFinancedDeviceKioskRoleHolderOnAnyUser() { + return getRoleHolderPackageNameOnUser( + RoleManager.ROLE_FINANCED_DEVICE_KIOSK, UserHandle.USER_ALL); + } } diff --git a/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java b/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java new file mode 100644 index 000000000000..22d7e7300bba --- /dev/null +++ b/services/tests/mockingservicestests/src/android/service/dreams/DreamOverlayConnectionHandlerTest.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.service.dreams; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.os.RemoteException; +import android.os.test.TestLooper; + +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; + +import com.android.internal.util.ObservableServiceConnection; +import com.android.internal.util.PersistentServiceConnection; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.MockitoAnnotations; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Consumer; + +@SmallTest +@RunWith(AndroidJUnit4.class) +public class DreamOverlayConnectionHandlerTest { + private static final int MIN_CONNECTION_DURATION_MS = 100; + private static final int MAX_RECONNECT_ATTEMPTS = 3; + private static final int BASE_RECONNECT_DELAY_MS = 50; + + @Mock + private Context mContext; + @Mock + private PersistentServiceConnection<IDreamOverlay> mConnection; + @Mock + private Intent mServiceIntent; + @Mock + private IDreamOverlay mOverlayService; + @Mock + private IDreamOverlayClient mOverlayClient; + + private TestLooper mTestLooper; + private DreamOverlayConnectionHandler mDreamOverlayConnectionHandler; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mTestLooper = new TestLooper(); + mDreamOverlayConnectionHandler = new DreamOverlayConnectionHandler( + mContext, + mTestLooper.getLooper(), + mServiceIntent, + MIN_CONNECTION_DURATION_MS, + MAX_RECONNECT_ATTEMPTS, + BASE_RECONNECT_DELAY_MS, + new TestInjector(mConnection)); + } + + @Test + public void consumerShouldRunImmediatelyWhenClientAvailable() throws RemoteException { + mDreamOverlayConnectionHandler.bind(); + connectService(); + provideClient(); + + final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class); + mDreamOverlayConnectionHandler.addConsumer(consumer); + mTestLooper.dispatchAll(); + verify(consumer).accept(mOverlayClient); + } + + @Test + public void consumerShouldRunAfterClientAvailable() throws RemoteException { + mDreamOverlayConnectionHandler.bind(); + connectService(); + + final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class); + mDreamOverlayConnectionHandler.addConsumer(consumer); + mTestLooper.dispatchAll(); + // No client yet, so we shouldn't have executed + verify(consumer, never()).accept(mOverlayClient); + + provideClient(); + mTestLooper.dispatchAll(); + verify(consumer).accept(mOverlayClient); + } + + @Test + public void consumerShouldNeverRunIfClientConnectsAndDisconnects() throws RemoteException { + mDreamOverlayConnectionHandler.bind(); + connectService(); + + final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class); + mDreamOverlayConnectionHandler.addConsumer(consumer); + mTestLooper.dispatchAll(); + // No client yet, so we shouldn't have executed + verify(consumer, never()).accept(mOverlayClient); + + provideClient(); + // Service disconnected before looper could handle the message. + disconnectService(); + mTestLooper.dispatchAll(); + verify(consumer, never()).accept(mOverlayClient); + } + + @Test + public void consumerShouldNeverRunIfUnbindCalled() throws RemoteException { + mDreamOverlayConnectionHandler.bind(); + connectService(); + provideClient(); + + final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class); + mDreamOverlayConnectionHandler.addConsumer(consumer); + mDreamOverlayConnectionHandler.unbind(); + mTestLooper.dispatchAll(); + // We unbinded immediately after adding consumer, so should never have run. + verify(consumer, never()).accept(mOverlayClient); + } + + @Test + public void consumersOnlyRunOnceIfUnbound() throws RemoteException { + mDreamOverlayConnectionHandler.bind(); + connectService(); + provideClient(); + + AtomicInteger counter = new AtomicInteger(); + // Add 10 consumers in a row which call unbind within the consumer. + for (int i = 0; i < 10; i++) { + mDreamOverlayConnectionHandler.addConsumer(client -> { + counter.getAndIncrement(); + mDreamOverlayConnectionHandler.unbind(); + }); + } + mTestLooper.dispatchAll(); + // Only the first consumer should have run, since we unbinded. + assertThat(counter.get()).isEqualTo(1); + } + + @Test + public void consumerShouldRunAgainAfterReconnect() throws RemoteException { + mDreamOverlayConnectionHandler.bind(); + connectService(); + provideClient(); + + final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class); + mDreamOverlayConnectionHandler.addConsumer(consumer); + mTestLooper.dispatchAll(); + verify(consumer, times(1)).accept(mOverlayClient); + + disconnectService(); + mTestLooper.dispatchAll(); + // No new calls should happen when service disconnected. + verify(consumer, times(1)).accept(mOverlayClient); + + connectService(); + provideClient(); + mTestLooper.dispatchAll(); + // We should trigger the consumer again once the server reconnects. + verify(consumer, times(2)).accept(mOverlayClient); + } + + @Test + public void consumerShouldNeverRunIfRemovedImmediately() throws RemoteException { + mDreamOverlayConnectionHandler.bind(); + connectService(); + provideClient(); + + final Consumer<IDreamOverlayClient> consumer = Mockito.mock(Consumer.class); + mDreamOverlayConnectionHandler.addConsumer(consumer); + mDreamOverlayConnectionHandler.removeConsumer(consumer); + mTestLooper.dispatchAll(); + verify(consumer, never()).accept(mOverlayClient); + } + + private void connectService() { + final ObservableServiceConnection.Callback<IDreamOverlay> callback = + captureConnectionCallback(); + callback.onConnected(mConnection, mOverlayService); + } + + private void disconnectService() { + final ObservableServiceConnection.Callback<IDreamOverlay> callback = + captureConnectionCallback(); + callback.onDisconnected(mConnection, /* reason= */ 0); + } + + private void provideClient() throws RemoteException { + final IDreamOverlayClientCallback callback = captureClientCallback(); + callback.onDreamOverlayClient(mOverlayClient); + } + + private ObservableServiceConnection.Callback<IDreamOverlay> captureConnectionCallback() { + ArgumentCaptor<ObservableServiceConnection.Callback<IDreamOverlay>> + callbackCaptor = + ArgumentCaptor.forClass(ObservableServiceConnection.Callback.class); + verify(mConnection).addCallback(callbackCaptor.capture()); + return callbackCaptor.getValue(); + } + + private IDreamOverlayClientCallback captureClientCallback() throws RemoteException { + ArgumentCaptor<IDreamOverlayClientCallback> callbackCaptor = + ArgumentCaptor.forClass(IDreamOverlayClientCallback.class); + verify(mOverlayService, atLeastOnce()).getClient(callbackCaptor.capture()); + return callbackCaptor.getValue(); + } + + static class TestInjector extends DreamOverlayConnectionHandler.Injector { + private final PersistentServiceConnection<IDreamOverlay> mConnection; + + TestInjector(PersistentServiceConnection<IDreamOverlay> connection) { + mConnection = connection; + } + + @Override + public PersistentServiceConnection<IDreamOverlay> buildConnection(Context context, + Handler handler, Intent serviceIntent, int minConnectionDurationMs, + int maxReconnectAttempts, int baseReconnectDelayMs) { + return mConnection; + } + } +} diff --git a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java index ec9e5b50579c..1fbb8dd5ac9d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/CachedAppOptimizerTest.java @@ -19,7 +19,6 @@ package com.android.server.am; import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; import static com.android.server.am.ActivityManagerService.Injector; -import static com.android.server.am.CachedAppOptimizer.compactActionIntToAction; import static com.google.common.truth.Truth.assertThat; @@ -155,12 +154,6 @@ public final class CachedAppOptimizerTest { synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) { assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isEqualTo( CachedAppOptimizer.DEFAULT_USE_COMPACTION); - assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome) - .isEqualTo( - compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1)); - assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull) - .isEqualTo( - compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2)); assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1); assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( @@ -210,12 +203,6 @@ public final class CachedAppOptimizerTest { DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CachedAppOptimizer.KEY_USE_COMPACTION, "true", false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - CachedAppOptimizer.KEY_COMPACT_ACTION_1, - Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - CachedAppOptimizer.KEY_COMPACT_ACTION_2, - Integer.toString((CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1), false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, CachedAppOptimizer.KEY_COMPACT_THROTTLE_1, Long.toString(CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1), false); DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, @@ -266,12 +253,6 @@ public final class CachedAppOptimizerTest { assertThat(mCachedAppOptimizerUnderTest.useCompaction()).isTrue(); assertThat(mCachedAppOptimizerUnderTest.mCachedAppOptimizerThread.isAlive()).isTrue(); - assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome) - .isEqualTo(compactActionIntToAction( - (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + 1 % 4) + 1)); - assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull) - .isEqualTo(compactActionIntToAction( - (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + 1 % 4) + 1)); assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeSome).isEqualTo( CachedAppOptimizer.DEFAULT_COMPACT_THROTTLE_1 + 1); assertThat(mCachedAppOptimizerUnderTest.mCompactThrottleSomeFull).isEqualTo( @@ -404,72 +385,6 @@ public final class CachedAppOptimizerTest { } @Test - public void compactAction_listensToDeviceConfigChanges() throws InterruptedException { - mCachedAppOptimizerUnderTest.init(); - - // When we override new values for the compaction action with reasonable values... - - // There are four possible values for compactAction[Some|Full]. - for (int i = 1; i < 5; i++) { - mCountDown = new CountDownLatch(2); - int expectedSome = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1 + i) % 4 + 1; - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - CachedAppOptimizer.KEY_COMPACT_ACTION_1, Integer.toString(expectedSome), false); - int expectedFull = (CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2 + i) % 4 + 1; - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - CachedAppOptimizer.KEY_COMPACT_ACTION_2, Integer.toString(expectedFull), false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - // Then the updates are reflected in the flags. - synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) { - assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome) - .isEqualTo(compactActionIntToAction(expectedSome)); - assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull) - .isEqualTo(compactActionIntToAction(expectedFull)); - } - } - } - - @Test - public void compactAction_listensToDeviceConfigChangesBadValues() throws InterruptedException { - mCachedAppOptimizerUnderTest.init(); - - // When we override new values for the compaction action with bad values ... - mCountDown = new CountDownLatch(2); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - CachedAppOptimizer.KEY_COMPACT_ACTION_1, "foo", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - CachedAppOptimizer.KEY_COMPACT_ACTION_2, "foo", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) { - // Then the default values are reflected in the flag - assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome) - .isEqualTo( - compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1)); - assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull) - .isEqualTo( - compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2)); - } - - mCountDown = new CountDownLatch(2); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - CachedAppOptimizer.KEY_COMPACT_ACTION_1, "", false); - DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ACTIVITY_MANAGER, - CachedAppOptimizer.KEY_COMPACT_ACTION_2, "", false); - assertThat(mCountDown.await(5, TimeUnit.SECONDS)).isTrue(); - - synchronized (mCachedAppOptimizerUnderTest.mPhenotypeFlagLock) { - assertThat(mCachedAppOptimizerUnderTest.mCompactActionSome) - .isEqualTo( - compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_1)); - assertThat(mCachedAppOptimizerUnderTest.mCompactActionFull) - .isEqualTo( - compactActionIntToAction(CachedAppOptimizer.DEFAULT_COMPACT_ACTION_2)); - } - } - - @Test public void compactThrottle_listensToDeviceConfigChanges() throws InterruptedException { mCachedAppOptimizerUnderTest.init(); @@ -1108,14 +1023,17 @@ public final class CachedAppOptimizerTest { mCachedAppOptimizerUnderTest.mLastCompactionStats.clear(); - // We force a some compaction - mCachedAppOptimizerUnderTest.compactApp(processRecord, - CachedAppOptimizer.CompactProfile.SOME, CachedAppOptimizer.CompactSource.APP, true); - waitForHandler(); - // then process is compacted. - CachedAppOptimizer.CompactProfile executedCompactProfile = - processRecord.mOptRecord.getLastCompactProfile(); - assertThat(executedCompactProfile).isEqualTo(CachedAppOptimizer.CompactProfile.SOME); + if (CachedAppOptimizer.ENABLE_FILE_COMPACT) { + // We force a some compaction + mCachedAppOptimizerUnderTest.compactApp(processRecord, + CachedAppOptimizer.CompactProfile.SOME, CachedAppOptimizer.CompactSource.APP, + true); + waitForHandler(); + // then process is compacted. + CachedAppOptimizer.CompactProfile executedCompactProfile = + processRecord.mOptRecord.getLastCompactProfile(); + assertThat(executedCompactProfile).isEqualTo(CachedAppOptimizer.CompactProfile.SOME); + } } private void setFlag(String key, String value, boolean defaultValue) throws Exception { @@ -1192,7 +1110,7 @@ public final class CachedAppOptimizerTest { } @Override - public void performCompaction(CachedAppOptimizer.CompactAction action, int pid) + public void performCompaction(CachedAppOptimizer.CompactProfile profile, int pid) throws IOException { mRss = mRssAfterCompaction; } diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java index b21478753301..04f6f8b2e9f0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuInfoReaderTest.java @@ -92,6 +92,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000, /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095, + /* normalizedAvailableCpuFreqKHz= */ 2_402_267, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610, /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050, /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810, @@ -101,6 +102,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000, /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380, + /* normalizedAvailableCpuFreqKHz= */ 2_693_525, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280, /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020, /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960, @@ -111,6 +113,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000, /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285, + /* normalizedAvailableCpuFreqKHz= */ 1_901_608, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280, /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020, /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960, @@ -121,6 +124,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000, /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 464_285, + /* normalizedAvailableCpuFreqKHz= */ 1_907_125, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610, /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050, /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810, @@ -139,6 +143,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000, /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 419_354, + /* normalizedAvailableCpuFreqKHz= */ 2_425_919, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000, /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000, /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000, @@ -148,6 +153,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000, /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 429_032, + /* normalizedAvailableCpuFreqKHz= */ 2_403_009, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000, /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000, /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000, @@ -158,6 +164,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000, /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ 403_225, + /* normalizedAvailableCpuFreqKHz= */ 1_688_209, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000, /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0, /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000, @@ -168,6 +175,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, /* isOnline= */ false, /* curCpuFreqKHz= */ MISSING_FREQUENCY, /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ MISSING_FREQUENCY, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000, /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000, /* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000, @@ -189,6 +197,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000, /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ 2_253_713, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610, /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050, /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810, @@ -198,6 +207,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000, /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ 2_492_687, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280, /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020, /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960, @@ -208,6 +218,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000, /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ 1_788_079, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280, /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020, /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960, @@ -218,6 +229,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000, /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ 1_799_962, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610, /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050, /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810, @@ -237,6 +249,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_000_000, /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ 2323347, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000, /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000, /* idleTimeMillis= */ 110_000_000, /* iowaitTimeMillis= */ 1_100_000, @@ -246,6 +259,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_800_000, /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ 209111, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 900_000, /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 10_000_000, /* idleTimeMillis= */ 1_000_000, /* iowaitTimeMillis= */ 90_000, @@ -256,6 +270,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000, /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ 453514, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 10_000_000, /* niceTimeMillis= */ 2_000_000, /* systemTimeMillis= */ 0, /* idleTimeMillis= */ 10_000_000, /* iowaitTimeMillis= */ 1_000_000, @@ -266,6 +281,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, /* isOnline= */ true, /* curCpuFreqKHz= */ 2_000_000, /* maxCpuFreqKHz= */ 2_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + /* normalizedAvailableCpuFreqKHz= */ 37728, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 2_000_000, /* niceTimeMillis= */ 1_000_000, /* systemTimeMillis= */ 1_000_000, /* idleTimeMillis= */ 100_000, /* iowaitTimeMillis= */ 100_000, @@ -323,38 +339,8 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { SparseArray<CpuInfoReader.CpuInfo> actualCpuInfos = cpuInfoReader.readCpuInfos(); SparseArray<CpuInfoReader.CpuInfo> expectedCpuInfos = new SparseArray<>(); - expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, - FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 3_000_000, - /* maxCpuFreqKHz= */ 1_000_000, /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, - new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280, - /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020, - /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960, - /* irqTimeMillis= */ 14_786_940, /* softirqTimeMillis= */ 1_498_130, - /* stealTimeMillis= */ 78_780, /* guestTimeMillis= */ 0, - /* guestNiceTimeMillis= */ 0))); - expectedCpuInfos.append(2, new CpuInfoReader.CpuInfo(/* cpuCore= */ 2, - FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, - /* isOnline= */ true, /* curCpuFreqKHz= */ 9, /* maxCpuFreqKHz= */ 2, - /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, - new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_959_280, - /* niceTimeMillis= */ 7_789_450, /* systemTimeMillis= */ 54_014_020, - /* idleTimeMillis= */ 402_717_120, /* iowaitTimeMillis= */ 1_166_960, - /* irqTimeMillis= */ 14_796_940, /* softirqTimeMillis= */ 1_478_130, - /* stealTimeMillis= */ 88_780, /* guestTimeMillis= */ 0, - /* guestNiceTimeMillis= */ 0))); - expectedCpuInfos.append(3, new CpuInfoReader.CpuInfo(/* cpuCore= */ 3, - FLAG_CPUSET_CATEGORY_TOP_APP | FLAG_CPUSET_CATEGORY_BACKGROUND, - /* isOnline= */ true, /* curCpuFreqKHz= */ 9, /* maxCpuFreqKHz= */ 2, - /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, - new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_349_610, - /* niceTimeMillis= */ 7_850_930, /* systemTimeMillis= */ 52_127_050, - /* idleTimeMillis= */ 409_136_950, /* iowaitTimeMillis= */ 1_332_810, - /* irqTimeMillis= */ 8_136_740, /* softirqTimeMillis= */ 438_970, - /* stealTimeMillis= */ 71_950, /* guestTimeMillis= */ 0, - /* guestNiceTimeMillis= */ 0))); - compareCpuInfos("CPU infos with corrupted CPU frequency", expectedCpuInfos, - actualCpuInfos); + compareCpuInfos("CPU infos with corrupted CPU frequency", expectedCpuInfos, actualCpuInfos); } @Test @@ -368,6 +354,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(0, new CpuInfoReader.CpuInfo(/* cpuCore= */ 0, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_230_000, /* maxCpuFreqKHz= */ 2_500_000, /* avgTimeInStateCpuFreqKHz= */ 488_095, + /* normalizedAvailableCpuFreqKHz= */ 2_402_267, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 32_249_610, /* niceTimeMillis= */ 7_950_930, /* systemTimeMillis= */ 52_227_050, /* idleTimeMillis= */ 409_036_950, /* iowaitTimeMillis= */ 1_322_810, @@ -377,6 +364,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { expectedCpuInfos.append(1, new CpuInfoReader.CpuInfo(/* cpuCore= */ 1, FLAG_CPUSET_CATEGORY_TOP_APP, /* isOnline= */ true, /* curCpuFreqKHz= */ 1_450_000, /* maxCpuFreqKHz= */ 2_800_000, /* avgTimeInStateCpuFreqKHz= */ 502_380, + /* normalizedAvailableCpuFreqKHz= */ 2_693_525, new CpuInfoReader.CpuUsageStats(/* userTimeMillis= */ 28_949_280, /* niceTimeMillis= */ 7_799_450, /* systemTimeMillis= */ 54_004_020, /* idleTimeMillis= */ 402_707_120, /* iowaitTimeMillis= */ 1_186_960, @@ -393,7 +381,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { assertWithMessage("Make empty dir %s", emptyDir).that(emptyDir.mkdir()).isTrue(); CpuInfoReader cpuInfoReader = new CpuInfoReader(emptyDir, getCacheFile( VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), - getCacheFile(VALID_PROC_STAT)); + getCacheFile(VALID_PROC_STAT), /* minReadIntervalMillis= */0); assertWithMessage("Init CPU reader info").that(cpuInfoReader.init()).isFalse(); @@ -406,7 +394,7 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { File emptyDir = getCacheFile(EMPTY_DIR); assertWithMessage("Make empty dir %s", emptyDir).that(emptyDir.mkdir()).isTrue(); CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR), emptyDir, - getCacheFile(VALID_PROC_STAT)); + getCacheFile(VALID_PROC_STAT), /* minReadIntervalMillis= */0); assertWithMessage("Init CPU reader info").that(cpuInfoReader.init()).isFalse(); @@ -420,12 +408,32 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { assertWithMessage("Create empty file %s", emptyFile).that(emptyFile.createNewFile()) .isTrue(); CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR), - getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(EMPTY_FILE)); + getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(EMPTY_FILE), + /* minReadIntervalMillis= */0); assertWithMessage("Cpu infos with empty proc stat").that(cpuInfoReader.readCpuInfos()) .isNull(); } + @Test + public void testReadingTooFrequentlyReturnsLastReadCpuInfos() throws Exception { + CpuInfoReader cpuInfoReader = new CpuInfoReader(getCacheFile(VALID_CPUSET_DIR), + getCacheFile(VALID_CPUFREQ_WITH_TIME_IN_STATE_DIR), getCacheFile(VALID_PROC_STAT), + /* minReadIntervalMillis= */ 60_000); + assertWithMessage("Initialize CPU info reader").that(cpuInfoReader.init()).isTrue(); + + SparseArray<CpuInfoReader.CpuInfo> firstCpuInfos = cpuInfoReader.readCpuInfos(); + assertWithMessage("CPU infos first snapshot").that(firstCpuInfos).isNotNull(); + assertWithMessage("CPU infos first snapshot size").that(firstCpuInfos.size()) + .isGreaterThan(0); + + SparseArray<CpuInfoReader.CpuInfo> secondCpuInfos = cpuInfoReader.readCpuInfos(); + compareCpuInfos("CPU infos second snapshot", firstCpuInfos, secondCpuInfos); + + SparseArray<CpuInfoReader.CpuInfo> thirdCpuInfos = cpuInfoReader.readCpuInfos(); + compareCpuInfos("CPU infos third snapshot", firstCpuInfos, thirdCpuInfos); + } + private void compareCpuInfos(String message, SparseArray<CpuInfoReader.CpuInfo> expected, SparseArray<CpuInfoReader.CpuInfo> actual) { @@ -462,7 +470,8 @@ public final class CpuInfoReaderTest extends ExpectableTestCase { private static CpuInfoReader newCpuInfoReader(File cpusetDir, File cpuFreqDir, File procStatFile) { - CpuInfoReader cpuInfoReader = new CpuInfoReader(cpusetDir, cpuFreqDir, procStatFile); + CpuInfoReader cpuInfoReader = new CpuInfoReader(cpusetDir, cpuFreqDir, procStatFile, + /* minReadIntervalMillis= */ 0); assertWithMessage("Initialize CPU info reader").that(cpuInfoReader.init()).isTrue(); return cpuInfoReader; } diff --git a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java index 49a2cc696744..5a5f5256e37d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/cpu/CpuMonitorServiceTest.java @@ -17,105 +17,659 @@ package com.android.server.cpu; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.server.cpu.CpuAvailabilityInfo.MISSING_CPU_AVAILABILITY_PERCENT; import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_ALL; +import static com.android.server.cpu.CpuAvailabilityMonitoringConfig.CPUSET_BACKGROUND; +import static com.android.server.cpu.CpuInfoReader.CpuInfo.MISSING_FREQUENCY; +import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_BACKGROUND; +import static com.android.server.cpu.CpuInfoReader.FLAG_CPUSET_CATEGORY_TOP_APP; +import static com.android.server.cpu.CpuMonitorService.DEFAULT_MONITORING_INTERVAL_MILLISECONDS; + +import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.when; import android.content.Context; import android.os.Binder; import android.os.Handler; import android.os.HandlerExecutor; +import android.os.HandlerThread; import android.os.Looper; import android.os.ServiceManager; +import android.util.ArraySet; +import android.util.SparseArray; import com.android.server.ExtendedMockitoRule; import com.android.server.LocalServices; +import com.android.server.Watchdog; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.stubbing.OngoingStubbing; + +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; public final class CpuMonitorServiceTest { - private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG = + private static final String TAG = CpuMonitorServiceTest.class.getSimpleName(); + private static final String USER_BUILD_TAG = TAG + "UserBuild"; + private static final long ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS = + TimeUnit.SECONDS.toMillis(1); + private static final long HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS = + TimeUnit.SECONDS.toMillis(5); + private static final long TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS = 100; + private static final long TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS = 150; + private static final long TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS = 300; + private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_ALL_CPUSET = new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL) .addThreshold(30).addThreshold(70).build(); + private static final CpuAvailabilityMonitoringConfig TEST_MONITORING_CONFIG_BG_CPUSET = + new CpuAvailabilityMonitoringConfig.Builder(CPUSET_BACKGROUND) + .addThreshold(50).addThreshold(90).build(); + private static final List<StaticCpuInfo> STATIC_CPU_INFOS = List.of( + new StaticCpuInfo(/* cpuCore= */ 0, + /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP, + /* maxCpuFreqKHz= */ 4000), + new StaticCpuInfo(/* cpuCore= */ 1, + /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP, + /* maxCpuFreqKHz= */ 3000), + new StaticCpuInfo(/* cpuCore= */ 2, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP + | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000), + new StaticCpuInfo(/* cpuCore= */ 3, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP + | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 3000), + new StaticCpuInfo(/* cpuCore= */ 4, /* cpusetCategories= */ FLAG_CPUSET_CATEGORY_TOP_APP + | FLAG_CPUSET_CATEGORY_BACKGROUND, /* maxCpuFreqKHz= */ 2000)); + private static final ArraySet<Integer> NO_OFFLINE_CORES = new ArraySet<>(); - private static final CpuAvailabilityMonitoringConfig TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2 = - new CpuAvailabilityMonitoringConfig.Builder(CPUSET_ALL) - .addThreshold(10).addThreshold(90).build(); - @Mock - private Context mContext; + private Context mMockContext; + @Mock + private CpuInfoReader mMockCpuInfoReader; + @Captor + private ArgumentCaptor<CpuAvailabilityInfo> mCpuAvailabilityInfoCaptor; + private HandlerThread mServiceHandlerThread; + private Handler mServiceHandler; private CpuMonitorService mService; - private HandlerExecutor mHandlerExecutor; private CpuMonitorInternal mLocalService; @Rule public final ExtendedMockitoRule mExtendedMockitoRule = new ExtendedMockitoRule.Builder(this) .mockStatic(ServiceManager.class) + .mockStatic(Watchdog.class) .build(); @Before - public void setUp() { - mService = new CpuMonitorService(mContext); - mHandlerExecutor = new HandlerExecutor(new Handler(Looper.getMainLooper())); + public void setUp() throws Exception { + mServiceHandlerThread = new HandlerThread(TAG); + mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader, mServiceHandlerThread, + /* shouldDebugMonitor= */ true, TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS, + TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS); + doNothing().when(() -> ServiceManager.addService(eq("cpu_monitor"), any(Binder.class), anyBoolean(), anyInt())); - mService.onStart(); - mLocalService = LocalServices.getService(CpuMonitorInternal.class); + doReturn(mock(Watchdog.class)).when(Watchdog::getInstance); + when(mMockCpuInfoReader.init()).thenReturn(true); + when(mMockCpuInfoReader.readCpuInfos()).thenReturn(new SparseArray<>()); + + startService(); } @After - public void tearDown() { - // The CpuMonitorInternal.class service is added by the mService.onStart call. - // Remove the service to ensure the setUp procedure can add this service again. - LocalServices.removeServiceForTest(CpuMonitorInternal.class); + public void tearDown() throws Exception { + terminateService(); } @Test - public void testAddRemoveCpuAvailabilityCallback() { + public void testAddRemoveCpuAvailabilityCallbackOnDebugBuild() throws Exception { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); - mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, - TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback); + mLocalService.addCpuAvailabilityCallback(/* executor= */ null, + TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback); + + assertWithMessage("Monitoring interval after adding a client callback") + .that(mService.getCurrentMonitoringIntervalMillis()) + .isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS); + + // Monitoring interval changed notification is sent asynchronously from the handler thread. + // So, sync with this thread before verifying the client call. + syncWithHandler(mServiceHandler, /* delayMillis= */ 0); + + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS)) + .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS); - // TODO(b/242722241): Verify that {@link mockCallback.onAvailabilityChanged} and - // {@link mockCallback.onMonitoringIntervalChanged} are called when the callback is added. + verify(mockCallback, never()).onAvailabilityChanged(any()); mLocalService.removeCpuAvailabilityCallback(mockCallback); - } + assertWithMessage("Monitoring interval after removing all client callbacks") + .that(mService.getCurrentMonitoringIntervalMillis()) + .isEqualTo(TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS); + } @Test - public void testDuplicateAddCpuAvailabilityCallback() { + public void testAddRemoveCpuAvailabilityCallbackOnUserBuild() throws Exception { + // The default service instantiated during test setUp has the debug monitoring enabled. + // But on a user build, debug monitoring is disabled. So, replace the default service with + // an equivalent user build service. + replaceServiceWithUserBuildService(); + CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); - mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, - TEST_CPU_AVAILABILITY_MONITORING_CONFIG, mockCallback); + mLocalService.addCpuAvailabilityCallback(/* executor= */ null, + TEST_MONITORING_CONFIG_ALL_CPUSET, mockCallback); + + assertWithMessage("Monitoring interval after adding a client callback") + .that(mService.getCurrentMonitoringIntervalMillis()) + .isEqualTo(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS); - mLocalService.addCpuAvailabilityCallback(mHandlerExecutor, - TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2, mockCallback); + // Monitoring interval changed notification is sent asynchronously from the handler thread. + // So, sync with this thread before verifying the client call. + syncWithHandler(mServiceHandler, /* delayMillis= */ 0); - // TODO(b/242722241): Verify that {@link mockCallback} is called only when CPU availability - // thresholds cross the bounds specified in the - // {@link TEST_CPU_AVAILABILITY_MONITORING_CONFIG_2} config. + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS)) + .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS); + + verify(mockCallback, never()).onAvailabilityChanged(any()); mLocalService.removeCpuAvailabilityCallback(mockCallback); + + assertWithMessage("Monitoring interval after removing all client callbacks") + .that(mService.getCurrentMonitoringIntervalMillis()) + .isEqualTo(DEFAULT_MONITORING_INTERVAL_MILLISECONDS); } @Test - public void testRemoveInvalidCpuAvailabilityCallback() { + public void testRemoveInvalidCpuAvailabilityCallback() throws Exception { CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( CpuMonitorInternal.CpuAvailabilityCallback.class); mLocalService.removeCpuAvailabilityCallback(mockCallback); } + + @Test + public void testReceiveCpuAvailabilityCallbackOnAddingFirstCallback() throws Exception { + // Debug monitoring is in progress but the default {@link CpuInfoReader.CpuInfo} returned by + // the {@link CpuInfoReader.readCpuInfos} is empty, so the client won't be notified when + // adding a callback. Inject {@link CpuInfoReader.CpuInfo}, so the client callback is + // notified on adding a callback. + injectCpuInfosAndWait(List.of( + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f, + NO_OFFLINE_CORES))); + + CpuMonitorInternal.CpuAvailabilityCallback mockCallback = + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET); + + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS)) + .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture()); + + List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues(); + + List<CpuAvailabilityInfo> expected = List.of( + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected); + } + + @Test + public void testReceiveCpuAvailabilityCallbackOnAddingMultipleCallbacks() throws Exception { + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET); + + injectCpuInfosAndWait(List.of( + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f, + NO_OFFLINE_CORES))); + + CpuMonitorInternal.CpuAvailabilityCallback mockCallback = + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET); + + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS)) + .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture()); + + List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues(); + + List<CpuAvailabilityInfo> expected = List.of( + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 10, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected); + } + + @Test + public void testCrossCpuAvailabilityThresholdsWithSingleCallback() throws Exception { + CpuMonitorInternal.CpuAvailabilityCallback mockCallback = + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET); + + injectCpuInfosAndWait(List.of( + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f, + NO_OFFLINE_CORES))); + + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4)) + .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture()); + + List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues(); + + List<CpuAvailabilityInfo> expected = List.of( + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 30, + /* pastNMillisAvgAvailabilityPercent= */ 45, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 82, + /* pastNMillisAvgAvailabilityPercent= */ 57, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected); + } + + @Test + public void testCrossCpuAvailabilityThresholdsWithMultipleCallbacks() throws Exception { + CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback = + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET); + + CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback = + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET); + + injectCpuInfosAndWait(List.of( + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f, + NO_OFFLINE_CORES))); + + verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3)) + .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture()); + + List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues(); + List<CpuAvailabilityInfo> expected = List.of( + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 75, + /* pastNMillisAvgAvailabilityPercent= */ 55, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 15, + /* pastNMillisAvgAvailabilityPercent= */ 60, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual) + .isEqualTo(expected); + + ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor = + ArgumentCaptor.forClass(CpuAvailabilityInfo.class); + + verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3)) + .onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture()); + + actual = bgCpusetAvailabilityInfoCaptor.getAllValues(); + expected = List.of( + new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 60, + /* pastNMillisAvgAvailabilityPercent= */ 36, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 90, + /* pastNMillisAvgAvailabilityPercent= */ 75, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 15, + /* pastNMillisAvgAvailabilityPercent= */ 60, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual) + .isEqualTo(expected); + } + + @Test + public void testCrossCpuAvailabilityThresholdsWithOfflineCores() throws Exception { + CpuMonitorInternal.CpuAvailabilityCallback mockAllCpusetCallback = + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET); + + CpuMonitorInternal.CpuAvailabilityCallback mockBgCpusetCallback = + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET); + + // Disable one top-app and one all cpuset core. + ArraySet<Integer> offlineCoresA = new ArraySet<>(); + offlineCoresA.add(1); + offlineCoresA.add(3); + + // Disable two all cpuset cores. + ArraySet<Integer> offlineCoresB = new ArraySet<>(); + offlineCoresB.add(2); + offlineCoresB.add(4); + + injectCpuInfosAndWait(List.of( + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 5.0f, offlineCoresA), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 20.0f, offlineCoresB), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f, offlineCoresA), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, offlineCoresB), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 75.0f, offlineCoresA), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f, offlineCoresB), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f, + offlineCoresA))); + + verify(mockAllCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3)) + .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture()); + + List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues(); + List<CpuAvailabilityInfo> expected = List.of( + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 30, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 75, + /* pastNMillisAvgAvailabilityPercent= */ 55, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 15, + /* pastNMillisAvgAvailabilityPercent= */ 61, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos for CPUSET_ALL callback").that(actual) + .isEqualTo(expected); + + ArgumentCaptor<CpuAvailabilityInfo> bgCpusetAvailabilityInfoCaptor = + ArgumentCaptor.forClass(CpuAvailabilityInfo.class); + + verify(mockBgCpusetCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(3)) + .onAvailabilityChanged(bgCpusetAvailabilityInfoCaptor.capture()); + + actual = bgCpusetAvailabilityInfoCaptor.getAllValues(); + expected = List.of( + new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 60, + /* pastNMillisAvgAvailabilityPercent= */ 35, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 90, + /* pastNMillisAvgAvailabilityPercent= */ 75, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(2).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 15, + /* pastNMillisAvgAvailabilityPercent= */ 55, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos for CPUSET_BACKGROUND callback").that(actual) + .isEqualTo(expected); + } + + @Test + public void testReceiveCpuAvailabilityCallbacksOnExecutorThread() throws Exception { + Handler testHandler = new Handler(Looper.getMainLooper()); + + assertWithMessage("Test main handler").that(testHandler).isNotNull(); + + HandlerExecutor testExecutor = new HandlerExecutor(testHandler); + + assertWithMessage("Test main executor").that(testExecutor).isNotNull(); + + CpuMonitorInternal.CpuAvailabilityCallback mockCallback = + addCpuAvailabilityCallback(testHandler, testExecutor, + TEST_MONITORING_CONFIG_ALL_CPUSET); + + // CPU monitoring is started on the service handler thread. Sync with this thread before + // proceeding. Otherwise, debug monitoring may consume the injected CPU infos and cause + // the test to be flaky. Because the {@link addCpuAvailabilityCallback} syncs only with + // the passed handler, the test must explicitly sync with the service handler. + syncWithHandler(mServiceHandler, /* delayMillis= */ 0); + + injectCpuInfosAndWait(testHandler, List.of( + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 90.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 15.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 30.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 82.0f, + NO_OFFLINE_CORES))); + + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(4)) + .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture()); + + List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues(); + + List<CpuAvailabilityInfo> expected = List.of( + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 90, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(1).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 15, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(2).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 30, + /* pastNMillisAvgAvailabilityPercent= */ 45, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_ALL, actual.get(3).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 82, + /* pastNMillisAvgAvailabilityPercent= */ 57, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected); + } + + @Test + public void testDuplicateAddCpuAvailabilityCallback() throws Exception { + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_ALL_CPUSET); + + CpuMonitorInternal.CpuAvailabilityCallback mockCallback = + addCpuAvailabilityCallback(TEST_MONITORING_CONFIG_BG_CPUSET); + + injectCpuInfosAndWait(List.of( + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 10.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 40.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 60.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 80.0f, + NO_OFFLINE_CORES), + generateCpuInfosForAvailability(/* cpuAvailabilityPercent= */ 95.0f, + NO_OFFLINE_CORES))); + + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS).times(2)) + .onAvailabilityChanged(mCpuAvailabilityInfoCaptor.capture()); + + List<CpuAvailabilityInfo> actual = mCpuAvailabilityInfoCaptor.getAllValues(); + + // Verify that the callback is called for the last added monitoring config. + List<CpuAvailabilityInfo> expected = List.of( + new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(0).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 60, MISSING_CPU_AVAILABILITY_PERCENT, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS), + new CpuAvailabilityInfo(CPUSET_BACKGROUND, actual.get(1).dataTimestampUptimeMillis, + /* latestAvgAvailabilityPercent= */ 95, + /* pastNMillisAvgAvailabilityPercent= */ 78, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS)); + + assertWithMessage("CPU availability infos").that(actual).isEqualTo(expected); + } + + @Test + public void testHeavyCpuLoadMonitoring() throws Exception { + // TODO(b/267500110): Once heavy CPU load detection logic is added, add unittest. + } + + private void startService() { + mService.onStart(); + mServiceHandler = mServiceHandlerThread.getThreadHandler(); + + assertWithMessage("Service thread handler").that(mServiceHandler).isNotNull(); + + mLocalService = LocalServices.getService(CpuMonitorInternal.class); + + assertWithMessage("CpuMonitorInternal local service").that(mLocalService).isNotNull(); + } + + private void terminateService() { + // The CpuMonitorInternal.class service is added by the {@link CpuMonitorService#onStart} + // call. Remove the service to ensure this service can be added again during + // the {@link CpuMonitorService#onStart} call. + LocalServices.removeServiceForTest(CpuMonitorInternal.class); + if (mServiceHandlerThread != null && mServiceHandlerThread.isAlive()) { + mServiceHandlerThread.quitSafely(); + } + } + + private void replaceServiceWithUserBuildService() { + terminateService(); + mServiceHandlerThread = new HandlerThread(USER_BUILD_TAG); + mService = new CpuMonitorService(mMockContext, mMockCpuInfoReader, + mServiceHandlerThread, /* shouldDebugMonitor= */ false, + TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS, + TEST_DEBUG_MONITORING_INTERVAL_MILLISECONDS, + TEST_LATEST_AVAILABILITY_DURATION_MILLISECONDS); + + startService(); + } + + private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback( + CpuAvailabilityMonitoringConfig config) throws Exception { + return addCpuAvailabilityCallback(mServiceHandler, /* executor= */ null, config); + } + + private CpuMonitorInternal.CpuAvailabilityCallback addCpuAvailabilityCallback(Handler handler, + HandlerExecutor executor, CpuAvailabilityMonitoringConfig config) throws Exception { + CpuMonitorInternal.CpuAvailabilityCallback mockCallback = mock( + CpuMonitorInternal.CpuAvailabilityCallback.class); + + mLocalService.addCpuAvailabilityCallback(executor, config, mockCallback); + + // Monitoring interval changed notification is sent asynchronously from the given handler. + // So, sync with this thread before verifying the client call. + syncWithHandler(handler, /* delayMillis= */ 0); + + verify(mockCallback, timeout(ASYNC_CALLBACK_WAIT_TIMEOUT_MILLISECONDS)) + .onMonitoringIntervalChanged(TEST_NORMAL_MONITORING_INTERVAL_MILLISECONDS); + + return mockCallback; + } + + private void injectCpuInfosAndWait(List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) + throws Exception { + injectCpuInfosAndWait(mServiceHandler, cpuInfos); + } + + private void injectCpuInfosAndWait(Handler handler, + List<SparseArray<CpuInfoReader.CpuInfo>> cpuInfos) throws Exception { + assertWithMessage("CPU info configs").that(cpuInfos).isNotEmpty(); + + OngoingStubbing<SparseArray<CpuInfoReader.CpuInfo>> ongoingStubbing = + when(mMockCpuInfoReader.readCpuInfos()); + for (SparseArray<CpuInfoReader.CpuInfo> cpuInfo : cpuInfos) { + ongoingStubbing = ongoingStubbing.thenReturn(cpuInfo); + } + + // CPU infos are read asynchronously on a separate handler thread. So, wait based on + // the current monitoring interval and the number of CPU infos were injected. + syncWithHandler(handler, + /* delayMillis= */ mService.getCurrentMonitoringIntervalMillis() * cpuInfos.size()); + } + + private void syncWithHandler(Handler handler, long delayMillis) throws Exception { + AtomicBoolean didRun = new AtomicBoolean(false); + handler.postDelayed(() -> { + synchronized (didRun) { + didRun.set(true); + didRun.notifyAll(); + } + }, delayMillis); + synchronized (didRun) { + while (!didRun.get()) { + didRun.wait(HANDLER_THREAD_SYNC_TIMEOUT_MILLISECONDS); + } + } + } + + private static SparseArray<CpuInfoReader.CpuInfo> generateCpuInfosForAvailability( + double cpuAvailabilityPercent, ArraySet<Integer> offlineCores) { + SparseArray<CpuInfoReader.CpuInfo> cpuInfos = new SparseArray<>(STATIC_CPU_INFOS.size()); + for (StaticCpuInfo staticCpuInfo : STATIC_CPU_INFOS) { + boolean isOnline = !offlineCores.contains(staticCpuInfo.cpuCore); + cpuInfos.append(staticCpuInfo.cpuCore, constructCpuInfo(staticCpuInfo.cpuCore, + staticCpuInfo.cpusetCategories, isOnline, staticCpuInfo.maxCpuFreqKHz, + cpuAvailabilityPercent)); + } + return cpuInfos; + } + + private static CpuInfoReader.CpuInfo constructCpuInfo(int cpuCore, + @CpuInfoReader.CpusetCategory int cpusetCategories, boolean isOnline, + long maxCpuFreqKHz, double cpuAvailabilityPercent) { + long availCpuFreqKHz = (long) (maxCpuFreqKHz * (cpuAvailabilityPercent / 100.0)); + long curCpuFreqKHz = maxCpuFreqKHz - availCpuFreqKHz; + return new CpuInfoReader.CpuInfo(cpuCore, cpusetCategories, isOnline, + isOnline ? curCpuFreqKHz : MISSING_FREQUENCY, maxCpuFreqKHz, + /* avgTimeInStateCpuFreqKHz= */ MISSING_FREQUENCY, + isOnline ? availCpuFreqKHz : MISSING_FREQUENCY, + /* latestCpuUsageStats= */ null); + } + + private static final class StaticCpuInfo { + public final int cpuCore; + public final int cpusetCategories; + public final int maxCpuFreqKHz; + + StaticCpuInfo(int cpuCore, @CpuInfoReader.CpusetCategory int cpusetCategories, + int maxCpuFreqKHz) { + this.cpuCore = cpuCore; + this.cpusetCategories = cpusetCategories; + this.maxCpuFreqKHz = maxCpuFreqKHz; + } + + @Override + public String toString() { + return "StaticCpuInfo{cpuCore=" + cpuCore + ", cpusetCategories=" + cpusetCategories + + ", maxCpuFreqKHz=" + maxCpuFreqKHz + '}'; + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java index 1ced95b92aa4..51dcc0323a96 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerController2Test.java @@ -148,6 +148,8 @@ public final class DisplayPowerController2Test { mCdsiMock).when(() -> LocalServices.getService( ColorDisplayService.ColorDisplayServiceInternal.class)); doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService); + doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() -> + Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt())); setUpSensors(); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); @@ -287,73 +289,6 @@ public final class DisplayPowerController2Test { eq(mProxSensor), anyInt(), any(Handler.class)); } - /** - * Creates a mock and registers it to {@link LocalServices}. - */ - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - - private void advanceTime(long timeMs) { - mClock.fastForward(timeMs); - mTestLooper.dispatchAll(); - } - - private void setUpSensors() throws Exception { - mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, - PROX_SENSOR_MAX_RANGE); - Sensor screenOffBrightnessSensor = TestUtils.createSensor( - Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); - when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL))) - .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor)); - } - - private SensorEventListener getSensorEventListener(Sensor sensor) { - verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(), - eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class)); - return mSensorEventListenerCaptor.getValue(); - } - - private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock, - DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock, - boolean isEnabled) { - DisplayInfo info = new DisplayInfo(); - DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); - deviceInfo.uniqueId = uniqueId; - - when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); - when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock); - when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); - when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled); - when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false); - when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn( - DisplayDeviceConfig.DEFAULT_ID); - when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); - when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); - when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock); - when(displayDeviceConfigMock.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_PROXIMITY; - name = null; - } - }); - when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); - when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true); - when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn( - new DisplayDeviceConfig.SensorData()); - when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_LIGHT; - name = null; - } - }); - when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux()) - .thenReturn(new int[0]); - } - @Test public void testDisplayBrightnessFollowers_BothDpcsSupportNits() { DisplayPowerControllerHolder followerDpc = @@ -482,6 +417,32 @@ public final class DisplayPowerController2Test { verify(followerDpc.animator).animateTo(eq(brightness), anyFloat(), anyFloat()); } + @Test + public void testDisplayBrightnessFollowers_AutomaticBrightness() { + doAnswer((Answer<Integer>) invocationOnMock -> + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) + .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), + eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), + eq(UserHandle.USER_CURRENT))); + final float brightness = 0.4f; + final float nits = 300; + final float ambientLux = 3000; + when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness()) + .thenReturn(brightness); + when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness()) + .thenReturn(0.3f); + when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); + when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + DisplayPowerController followerDpc = mock(DisplayPowerController.class); + + mHolder.dpc.addDisplayBrightnessFollower(followerDpc); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(followerDpc).setBrightnessToFollow(brightness, nits, ambientLux); + } @Test public void testDisplayBrightnessFollowersRemoval() { @@ -750,6 +711,73 @@ public final class DisplayPowerController2Test { verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat()); } + /** + * Creates a mock and registers it to {@link LocalServices}. + */ + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + + private void setUpSensors() throws Exception { + mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, + PROX_SENSOR_MAX_RANGE); + Sensor screenOffBrightnessSensor = TestUtils.createSensor( + Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); + when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL))) + .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor)); + } + + private SensorEventListener getSensorEventListener(Sensor sensor) { + verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(), + eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class)); + return mSensorEventListenerCaptor.getValue(); + } + + private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock, + DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock, + boolean isEnabled) { + DisplayInfo info = new DisplayInfo(); + DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); + deviceInfo.uniqueId = uniqueId; + + when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); + when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock); + when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); + when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled); + when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false); + when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn( + DisplayDeviceConfig.DEFAULT_ID); + when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); + when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); + when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock); + when(displayDeviceConfigMock.getProximitySensor()).thenReturn( + new DisplayDeviceConfig.SensorData() { + { + type = Sensor.STRING_TYPE_PROXIMITY; + name = null; + } + }); + when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); + when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true); + when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn( + new DisplayDeviceConfig.SensorData()); + when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn( + new DisplayDeviceConfig.SensorData() { + { + type = Sensor.STRING_TYPE_LIGHT; + name = null; + } + }); + when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux()) + .thenReturn(new int[0]); + } + private DisplayPowerControllerHolder createDisplayPowerController(int displayId, String uniqueId) { return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true); diff --git a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java index 53fcdad30ed2..0a1bf1c9ed99 100644 --- a/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -149,6 +149,8 @@ public final class DisplayPowerControllerTest { mCdsiMock).when(() -> LocalServices.getService( ColorDisplayService.ColorDisplayServiceInternal.class)); doAnswer((Answer<Void>) invocationOnMock -> null).when(BatteryStatsService::getService); + doAnswer((Answer<Boolean>) invocationOnMock -> true).when(() -> + Settings.System.putFloatForUser(any(), any(), anyFloat(), anyInt())); setUpSensors(); mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); @@ -290,73 +292,6 @@ public final class DisplayPowerControllerTest { eq(mProxSensor), anyInt(), any(Handler.class)); } - /** - * Creates a mock and registers it to {@link LocalServices}. - */ - private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { - LocalServices.removeServiceForTest(clazz); - LocalServices.addService(clazz, mock); - } - - private void advanceTime(long timeMs) { - mClock.fastForward(timeMs); - mTestLooper.dispatchAll(); - } - - private void setUpSensors() throws Exception { - mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, - PROX_SENSOR_MAX_RANGE); - Sensor screenOffBrightnessSensor = TestUtils.createSensor( - Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); - when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL))) - .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor)); - } - - private SensorEventListener getSensorEventListener(Sensor sensor) { - verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(), - eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class)); - return mSensorEventListenerCaptor.getValue(); - } - - private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock, - DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock, - boolean isEnabled) { - DisplayInfo info = new DisplayInfo(); - DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); - deviceInfo.uniqueId = uniqueId; - - when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); - when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock); - when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); - when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled); - when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false); - when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn( - DisplayDeviceConfig.DEFAULT_ID); - when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); - when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); - when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock); - when(displayDeviceConfigMock.getProximitySensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_PROXIMITY; - name = null; - } - }); - when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); - when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true); - when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn( - new DisplayDeviceConfig.SensorData()); - when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn( - new DisplayDeviceConfig.SensorData() { - { - type = Sensor.STRING_TYPE_LIGHT; - name = null; - } - }); - when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux()) - .thenReturn(new int[0]); - } - @Test public void testDisplayBrightnessFollowers_BothDpcsSupportNits() { DisplayPowerControllerHolder followerDpc = @@ -486,6 +421,33 @@ public final class DisplayPowerControllerTest { } @Test + public void testDisplayBrightnessFollowers_AutomaticBrightness() { + doAnswer((Answer<Integer>) invocationOnMock -> + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC) + .when(() -> Settings.System.getIntForUser(any(ContentResolver.class), + eq(Settings.System.SCREEN_BRIGHTNESS_MODE), anyInt(), + eq(UserHandle.USER_CURRENT))); + final float brightness = 0.4f; + final float nits = 300; + final float ambientLux = 3000; + when(mHolder.automaticBrightnessController.getRawAutomaticScreenBrightness()) + .thenReturn(brightness); + when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness()) + .thenReturn(0.3f); + when(mHolder.automaticBrightnessController.convertToNits(brightness)).thenReturn(nits); + when(mHolder.automaticBrightnessController.getAmbientLux()).thenReturn(ambientLux); + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + DisplayPowerController followerDpc = mock(DisplayPowerController.class); + + mHolder.dpc.addDisplayBrightnessFollower(followerDpc); + DisplayPowerRequest dpr = new DisplayPowerRequest(); + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + + verify(followerDpc).setBrightnessToFollow(brightness, nits, ambientLux); + } + + @Test public void testDisplayBrightnessFollowersRemoval() { DisplayPowerControllerHolder followerHolder = createDisplayPowerController(FOLLOWER_DISPLAY_ID, FOLLOWER_UNIQUE_DISPLAY_ID); @@ -517,7 +479,6 @@ public final class DisplayPowerControllerTest { verify(followerHolder.animator).animateTo(eq(initialFollowerBrightness), anyFloat(), anyFloat()); - mHolder.dpc.addDisplayBrightnessFollower(followerHolder.dpc); mHolder.dpc.addDisplayBrightnessFollower(secondFollowerHolder.dpc); clearInvocations(followerHolder.animator); @@ -754,6 +715,73 @@ public final class DisplayPowerControllerTest { verify(mHolder.animator, times(2)).animateTo(eq(newBrightness), anyFloat(), anyFloat()); } + /** + * Creates a mock and registers it to {@link LocalServices}. + */ + private static <T> void addLocalServiceMock(Class<T> clazz, T mock) { + LocalServices.removeServiceForTest(clazz); + LocalServices.addService(clazz, mock); + } + + private void advanceTime(long timeMs) { + mClock.fastForward(timeMs); + mTestLooper.dispatchAll(); + } + + private void setUpSensors() throws Exception { + mProxSensor = TestUtils.createSensor(Sensor.TYPE_PROXIMITY, Sensor.STRING_TYPE_PROXIMITY, + PROX_SENSOR_MAX_RANGE); + Sensor screenOffBrightnessSensor = TestUtils.createSensor( + Sensor.TYPE_LIGHT, Sensor.STRING_TYPE_LIGHT); + when(mSensorManagerMock.getSensorList(eq(Sensor.TYPE_ALL))) + .thenReturn(List.of(mProxSensor, screenOffBrightnessSensor)); + } + + private SensorEventListener getSensorEventListener(Sensor sensor) { + verify(mSensorManagerMock).registerListener(mSensorEventListenerCaptor.capture(), + eq(sensor), eq(SensorManager.SENSOR_DELAY_NORMAL), isA(Handler.class)); + return mSensorEventListenerCaptor.getValue(); + } + + private void setUpDisplay(int displayId, String uniqueId, LogicalDisplay logicalDisplayMock, + DisplayDevice displayDeviceMock, DisplayDeviceConfig displayDeviceConfigMock, + boolean isEnabled) { + DisplayInfo info = new DisplayInfo(); + DisplayDeviceInfo deviceInfo = new DisplayDeviceInfo(); + deviceInfo.uniqueId = uniqueId; + + when(logicalDisplayMock.getDisplayIdLocked()).thenReturn(displayId); + when(logicalDisplayMock.getPrimaryDisplayDeviceLocked()).thenReturn(displayDeviceMock); + when(logicalDisplayMock.getDisplayInfoLocked()).thenReturn(info); + when(logicalDisplayMock.isEnabledLocked()).thenReturn(isEnabled); + when(logicalDisplayMock.isInTransitionLocked()).thenReturn(false); + when(logicalDisplayMock.getBrightnessThrottlingDataIdLocked()).thenReturn( + DisplayDeviceConfig.DEFAULT_ID); + when(displayDeviceMock.getDisplayDeviceInfoLocked()).thenReturn(deviceInfo); + when(displayDeviceMock.getUniqueId()).thenReturn(uniqueId); + when(displayDeviceMock.getDisplayDeviceConfig()).thenReturn(displayDeviceConfigMock); + when(displayDeviceConfigMock.getProximitySensor()).thenReturn( + new DisplayDeviceConfig.SensorData() { + { + type = Sensor.STRING_TYPE_PROXIMITY; + name = null; + } + }); + when(displayDeviceConfigMock.getNits()).thenReturn(new float[]{2, 500}); + when(displayDeviceConfigMock.isAutoBrightnessAvailable()).thenReturn(true); + when(displayDeviceConfigMock.getAmbientLightSensor()).thenReturn( + new DisplayDeviceConfig.SensorData()); + when(displayDeviceConfigMock.getScreenOffBrightnessSensor()).thenReturn( + new DisplayDeviceConfig.SensorData() { + { + type = Sensor.STRING_TYPE_LIGHT; + name = null; + } + }); + when(displayDeviceConfigMock.getScreenOffBrightnessSensorValueToLux()) + .thenReturn(new int[0]); + } + private DisplayPowerControllerHolder createDisplayPowerController(int displayId, String uniqueId) { return createDisplayPowerController(displayId, uniqueId, /* isEnabled= */ true); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java index e056417811c7..ba70c584f2de 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java @@ -262,9 +262,10 @@ public class JobSchedulerServiceTest { ConnectivityController connectivityController = mService.getConnectivityController(); spyOn(connectivityController); mService.mConstants.RUNTIME_MIN_GUARANTEE_MS = 10 * MINUTE_IN_MILLIS; + mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS = 2 * HOUR_IN_MILLIS; mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR = 1.5f; mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS = HOUR_IN_MILLIS; - mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS = 6 * HOUR_IN_MILLIS; + mService.mConstants.RUNTIME_UI_LIMIT_MS = 6 * HOUR_IN_MILLIS; assertEquals(mService.mConstants.RUNTIME_MIN_EJ_GUARANTEE_MS, mService.getMinJobExecutionGuaranteeMs(ejMax)); @@ -283,9 +284,9 @@ public class JobSchedulerServiceTest { // Permission isn't granted, so it should just be treated as a regular job. assertEquals(mService.mConstants.RUNTIME_MIN_GUARANTEE_MS, mService.getMinJobExecutionGuaranteeMs(jobUIDT)); + grantRunUserInitiatedJobsPermission(true); // With permission - assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, - mService.getMinJobExecutionGuaranteeMs(jobUIDT)); + mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = true; doReturn(ConnectivityController.UNKNOWN_TIME) .when(connectivityController).getEstimatedTransferTimeMs(any()); assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, @@ -294,16 +295,20 @@ public class JobSchedulerServiceTest { .when(connectivityController).getEstimatedTransferTimeMs(any()); assertEquals(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS, mService.getMinJobExecutionGuaranteeMs(jobUIDT)); - doReturn(mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2) + final long estimatedTransferTimeMs = + mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS * 2; + doReturn(estimatedTransferTimeMs) .when(connectivityController).getEstimatedTransferTimeMs(any()); - assertEquals( - (long) (mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_MS - * 2 * mService.mConstants - .RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR), + assertEquals((long) (estimatedTransferTimeMs + * mService.mConstants.RUNTIME_MIN_UI_DATA_TRANSFER_GUARANTEE_BUFFER_FACTOR), mService.getMinJobExecutionGuaranteeMs(jobUIDT)); - doReturn(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS * 2) + doReturn(mService.mConstants.RUNTIME_UI_LIMIT_MS * 2) .when(connectivityController).getEstimatedTransferTimeMs(any()); - assertEquals(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS, + assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS, + mService.getMinJobExecutionGuaranteeMs(jobUIDT)); + + mService.mConstants.RUNTIME_USE_DATA_ESTIMATES_FOR_LIMITS = false; + assertEquals(mService.mConstants.RUNTIME_MIN_UI_GUARANTEE_MS, mService.getMinJobExecutionGuaranteeMs(jobUIDT)); } @@ -325,7 +330,7 @@ public class JobSchedulerServiceTest { .when(quotaController).getMaxJobExecutionTimeMsLocked(any()); grantRunUserInitiatedJobsPermission(true); - assertEquals(mService.mConstants.RUNTIME_UI_DATA_TRANSFER_LIMIT_MS, + assertEquals(mService.mConstants.RUNTIME_UI_LIMIT_MS, mService.getMaxJobExecutionTimeMs(jobUIDT)); grantRunUserInitiatedJobsPermission(false); assertEquals(mService.mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java index 32e5c836ac3d..a3ae83428af5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/ConnectivityControllerTest.java @@ -840,8 +840,9 @@ public class ConnectivityControllerTest { final JobStatus blue = createJobStatus(createJob() .setEstimatedNetworkBytes(DataUnit.MEBIBYTES.toBytes(1), 0) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY), UID_BLUE); + // Unmetered preference is disabled for now. assertFalse(red.getPreferUnmetered()); - assertTrue(blue.getPreferUnmetered()); + assertFalse(blue.getPreferUnmetered()); controller.maybeStartTrackingJobLocked(red, null); controller.maybeStartTrackingJobLocked(blue, null); @@ -895,7 +896,7 @@ public class ConnectivityControllerTest { generalCallback.onLost(meteredNet); assertTrue(red.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); - assertFalse(red.getHasAccessToUnmetered()); + assertTrue(red.getHasAccessToUnmetered()); assertTrue(blue.isConstraintSatisfied(JobStatus.CONSTRAINT_CONNECTIVITY)); assertTrue(blue.getHasAccessToUnmetered()); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java index 6bc552c7f32e..4b19bbb72805 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java @@ -189,7 +189,10 @@ public class FlexibilityControllerTest { } private static JobInfo.Builder createJob(int id) { - return new JobInfo.Builder(id, new ComponentName("foo", "bar")); + return new JobInfo.Builder(id, new ComponentName("foo", "bar")) + .setPrefersBatteryNotLow(true) + .setPrefersCharging(true) + .setPrefersDeviceIdle(true); } private JobStatus createJobStatus(String testTag, JobInfo.Builder job) { @@ -533,12 +536,15 @@ public class FlexibilityControllerTest { jb = createJob(i); if (i > 0) { jb.setRequiresDeviceIdle(true); + jb.setPrefersDeviceIdle(false); } if (i > 1) { jb.setRequiresBatteryNotLow(true); + jb.setPrefersBatteryNotLow(false); } if (i > 2) { jb.setRequiresCharging(true); + jb.setPrefersCharging(false); } jobs[i] = createJobStatus("", jb); flexTracker.add(jobs[i]); @@ -547,53 +553,55 @@ public class FlexibilityControllerTest { synchronized (mFlexibilityController.mLock) { ArrayList<ArraySet<JobStatus>> trackedJobs = flexTracker.getArrayList(); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); - assertEquals(0, trackedJobs.get(2).size()); - assertEquals(3, trackedJobs.get(3).size()); + assertEquals(1, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(2).size()); + assertEquals(1, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); - assertEquals(1, trackedJobs.get(2).size()); - assertEquals(2, trackedJobs.get(3).size()); + assertEquals(1, trackedJobs.get(1).size()); + assertEquals(2, trackedJobs.get(2).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(1, trackedJobs.get(1).size()); - assertEquals(0, trackedJobs.get(2).size()); - assertEquals(2, trackedJobs.get(3).size()); + assertEquals(2, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(2).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); assertEquals(2, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); - assertEquals(0, trackedJobs.get(2).size()); - assertEquals(2, trackedJobs.get(3).size()); + assertEquals(1, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(2).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.remove(jobs[1]); assertEquals(2, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); - assertEquals(1, trackedJobs.get(3).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); - assertEquals(2, trackedJobs.get(3).size()); + assertEquals(1, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(1, trackedJobs.get(1).size()); + assertEquals(2, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); - assertEquals(1, trackedJobs.get(3).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); + // Over halfway through the flex window. The job that prefers all flex constraints + // should have its first flex constraint dropped. final long nowElapsed = ((DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS / 2) + HOUR_IN_MILLIS); JobSchedulerService.sElapsedRealtimeClock = @@ -601,9 +609,9 @@ public class FlexibilityControllerTest { flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed); assertEquals(1, trackedJobs.get(0).size()); - assertEquals(0, trackedJobs.get(1).size()); + assertEquals(1, trackedJobs.get(1).size()); assertEquals(1, trackedJobs.get(2).size()); - assertEquals(1, trackedJobs.get(3).size()); + assertEquals(0, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); } } @@ -618,8 +626,13 @@ public class FlexibilityControllerTest { @Test public void testExceptions_UserInitiated() { - JobInfo.Builder jb = createJob(0); - jb.setUserInitiated(true).setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); + JobInfo.Builder jb = createJob(0) + .setUserInitiated(true) + .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY) + // Attempt to add flex constraints to the job. For now, we will ignore them. + .setPrefersBatteryNotLow(true) + .setPrefersCharging(true) + .setPrefersDeviceIdle(false); JobStatus js = createJobStatus("testExceptions_UserInitiated", jb); assertFalse(js.hasFlexibilityConstraint()); } @@ -635,10 +648,10 @@ public class FlexibilityControllerTest { @Test public void testExceptions_NoFlexibleConstraints() { - JobInfo.Builder jb = createJob(0); - jb.setRequiresDeviceIdle(true); - jb.setRequiresCharging(true); - jb.setRequiresBatteryNotLow(true); + JobInfo.Builder jb = createJob(0) + .setPrefersBatteryNotLow(false) + .setPrefersCharging(false) + .setPrefersDeviceIdle(false); JobStatus js = createJobStatus("testExceptions_NoFlexibleConstraints", jb); assertFalse(js.hasFlexibilityConstraint()); } @@ -697,15 +710,50 @@ public class FlexibilityControllerTest { JobStatus js = createJobStatus("testTopAppBypass", jb); synchronized (mFlexibilityController.mLock) { js.setHasAccessToUnmetered(false); - assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); + assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); js.setHasAccessToUnmetered(true); - assertEquals(1, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); + assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); js.setHasAccessToUnmetered(false); - assertEquals(0, mFlexibilityController.getNumSatisfiedRequiredConstraintsLocked(js)); + assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); } } @Test + public void testGetNumSatisfiedFlexibleConstraints() { + long nowElapsed = FROZEN_TIME; + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_BATTERY_NOT_LOW, true, nowElapsed); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_CHARGING, true, nowElapsed); + mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, true, nowElapsed); + JobInfo.Builder jb = createJob(0) + .setPrefersBatteryNotLow(false) + .setPrefersCharging(false) + .setPrefersDeviceIdle(false); + JobStatus js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb); + assertEquals(0, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); + + jb = createJob(0) + .setPrefersBatteryNotLow(true) + .setPrefersCharging(false) + .setPrefersDeviceIdle(false); + js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb); + assertEquals(1, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); + + jb = createJob(0) + .setPrefersBatteryNotLow(true) + .setPrefersCharging(false) + .setPrefersDeviceIdle(true); + js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb); + assertEquals(2, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); + + jb = createJob(0) + .setPrefersBatteryNotLow(true) + .setPrefersCharging(true) + .setPrefersDeviceIdle(true); + js = createJobStatus("testGetNumSatisfiedFlexibleConstraints", jb); + assertEquals(3, mFlexibilityController.getNumSatisfiedFlexibleConstraintsLocked(js)); + } + + @Test public void testSetConstraintSatisfied_Constraints() { mFlexibilityController.setConstraintSatisfied(CONSTRAINT_IDLE, false, FROZEN_TIME); assertFalse(mFlexibilityController.isConstraintSatisfied(CONSTRAINT_IDLE)); @@ -736,8 +784,11 @@ public class FlexibilityControllerTest { jb = createJob(i); constraints = constraintCombinations[i]; jb.setRequiresDeviceIdle((constraints & CONSTRAINT_IDLE) != 0); + jb.setPrefersDeviceIdle((constraints & CONSTRAINT_IDLE) == 0); jb.setRequiresBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) != 0); + jb.setPrefersBatteryNotLow((constraints & CONSTRAINT_BATTERY_NOT_LOW) == 0); jb.setRequiresCharging((constraints & CONSTRAINT_CHARGING) != 0); + jb.setPrefersCharging((constraints & CONSTRAINT_CHARGING) == 0); synchronized (mFlexibilityController.mLock) { mFlexibilityController.maybeStartTrackingJobLocked( createJobStatus(String.valueOf(i), jb), null); diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java index 5dc8ed58994d..b076ab495d0c 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java @@ -1039,7 +1039,9 @@ public class JobStatusTest { @Test public void testReadinessStatusWithConstraint_FlexibilityConstraint() { final JobStatus job = createJobStatus( - new JobInfo.Builder(101, new ComponentName("foo", "bar")).build()); + new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setPrefersCharging(true) + .build()); job.setConstraintSatisfied(CONSTRAINT_FLEXIBLE, sElapsedRealtimeClock.millis(), false); markImplicitConstraintsSatisfied(job, true); assertTrue(job.readinessStatusWithConstraint(CONSTRAINT_FLEXIBLE, true)); diff --git a/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java new file mode 100644 index 000000000000..fd9dfe869d52 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/location/gnss/GnssMeasurementsProviderTest.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location.gnss; + +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.location.GnssMeasurementRequest; +import android.location.IGnssMeasurementsListener; +import android.location.LocationManager; +import android.location.LocationManagerInternal; +import android.location.util.identity.CallerIdentity; +import android.os.IBinder; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; + +import com.android.server.LocalServices; +import com.android.server.location.gnss.hal.FakeGnssHal; +import com.android.server.location.gnss.hal.GnssNative; +import com.android.server.location.injector.FakeUserInfoHelper; +import com.android.server.location.injector.Injector; +import com.android.server.location.injector.TestInjector; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.Objects; + +@Presubmit +@SmallTest +@RunWith(AndroidJUnit4.class) +public class GnssMeasurementsProviderTest { + private static final int CURRENT_USER = FakeUserInfoHelper.DEFAULT_USERID; + private static final CallerIdentity IDENTITY = CallerIdentity.forTest(CURRENT_USER, 1000, + "mypackage", "attribution", "listener"); + private static final GnssConfiguration.HalInterfaceVersion HIDL_V2_1 = + new GnssConfiguration.HalInterfaceVersion( + 2, 1); + private static final GnssConfiguration.HalInterfaceVersion AIDL_V3 = + new GnssConfiguration.HalInterfaceVersion( + GnssConfiguration.HalInterfaceVersion.AIDL_INTERFACE, 3); + private static final GnssMeasurementRequest ACTIVE_REQUEST = + new GnssMeasurementRequest.Builder().build(); + private static final GnssMeasurementRequest PASSIVE_REQUEST = + new GnssMeasurementRequest.Builder().setIntervalMillis( + GnssMeasurementRequest.PASSIVE_INTERVAL).build(); + private @Mock Context mContext; + private @Mock LocationManagerInternal mInternal; + private @Mock GnssConfiguration mMockConfiguration; + private @Mock GnssNative.GeofenceCallbacks mGeofenceCallbacks; + private @Mock IGnssMeasurementsListener mListener1; + private @Mock IGnssMeasurementsListener mListener2; + private @Mock IBinder mBinder1; + private @Mock IBinder mBinder2; + + private GnssNative mGnssNative; + + private GnssMeasurementsProvider mTestProvider; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + doReturn(mBinder1).when(mListener1).asBinder(); + doReturn(mBinder2).when(mListener2).asBinder(); + doReturn(true).when(mInternal).isProviderEnabledForUser(eq(LocationManager.GPS_PROVIDER), + anyInt()); + LocalServices.addService(LocationManagerInternal.class, mInternal); + FakeGnssHal fakeGnssHal = new FakeGnssHal(); + GnssNative.setGnssHalForTest(fakeGnssHal); + Injector injector = new TestInjector(mContext); + mGnssNative = spy(Objects.requireNonNull( + GnssNative.create(injector, mMockConfiguration))); + mGnssNative.setGeofenceCallbacks(mGeofenceCallbacks); + mTestProvider = new GnssMeasurementsProvider(injector, mGnssNative); + mGnssNative.register(); + } + + @After + public void tearDown() { + LocalServices.removeServiceForTest(LocationManagerInternal.class); + } + + @Test + public void testAddListener_active() { + // add the active request + mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener1); + verify(mGnssNative, times(1)).startMeasurementCollection( + eq(ACTIVE_REQUEST.isFullTracking()), + eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()), + eq(ACTIVE_REQUEST.getIntervalMillis())); + + // remove the active request + mTestProvider.removeListener(mListener1); + verify(mGnssNative, times(1)).stopMeasurementCollection(); + } + + @Test + public void testAddListener_passive() { + // add the passive request + mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1); + verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(), + anyInt()); + + // remove the passive request + mTestProvider.removeListener(mListener1); + verify(mGnssNative, times(1)).stopMeasurementCollection(); + } + + @Test + public void testReregister_aidlV3Plus() { + doReturn(AIDL_V3).when(mMockConfiguration).getHalInterfaceVersion(); + + // add the passive request + mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1); + verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(), + anyInt()); + + // add the active request, reregister with the active request + mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener2); + verify(mGnssNative, never()).stopMeasurementCollection(); + verify(mGnssNative, times(1)).startMeasurementCollection( + eq(ACTIVE_REQUEST.isFullTracking()), + eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()), + eq(ACTIVE_REQUEST.getIntervalMillis())); + + // remove the active request, reregister with the passive request + mTestProvider.removeListener(mListener2); + verify(mGnssNative, times(1)).stopMeasurementCollection(); + + // remove the passive request + mTestProvider.removeListener(mListener1); + verify(mGnssNative, times(2)).stopMeasurementCollection(); + } + + @Test + public void testReregister_preAidlV3() { + doReturn(HIDL_V2_1).when(mMockConfiguration).getHalInterfaceVersion(); + + // add the passive request + mTestProvider.addListener(PASSIVE_REQUEST, IDENTITY, mListener1); + verify(mGnssNative, never()).startMeasurementCollection(anyBoolean(), anyBoolean(), + anyInt()); + + // add the active request, reregister with the active request + mTestProvider.addListener(ACTIVE_REQUEST, IDENTITY, mListener2); + verify(mGnssNative, times(1)).stopMeasurementCollection(); + verify(mGnssNative, times(1)).startMeasurementCollection( + eq(ACTIVE_REQUEST.isFullTracking()), + eq(ACTIVE_REQUEST.isCorrelationVectorOutputsEnabled()), + eq(ACTIVE_REQUEST.getIntervalMillis())); + + // remove the active request, reregister with the passive request + mTestProvider.removeListener(mListener2); + verify(mGnssNative, times(2)).stopMeasurementCollection(); + + // remove the passive request + mTestProvider.removeListener(mListener1); + verify(mGnssNative, times(3)).stopMeasurementCollection(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java new file mode 100644 index 000000000000..5adf391a1c32 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricFrameworkStatsLoggerTest.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.biometrics.log; + +import static com.google.common.truth.Truth.assertThat; + +import android.hardware.biometrics.BiometricsProtoEnums; +import android.hardware.biometrics.common.AuthenticateReason; +import android.hardware.biometrics.common.OperationContext; +import android.hardware.biometrics.common.WakeReason; +import android.platform.test.annotations.Presubmit; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +@Presubmit +@SmallTest +public class BiometricFrameworkStatsLoggerTest { + + @Test + public void testConvertsWakeReason_whenEmpty() { + final OperationContextExt ctx = new OperationContextExt(); + + final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx); + final int[] reasonDetails = BiometricFrameworkStatsLogger + .toProtoWakeReasonDetails(ctx); + + assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN); + assertThat(reasonDetails).isEmpty(); + } + + @Test + public void testConvertsWakeReason_whenPowerReason() { + final OperationContext context = new OperationContext(); + context.wakeReason = WakeReason.WAKE_MOTION; + final OperationContextExt ctx = new OperationContextExt(context); + + final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx); + final int[] reasonDetails = BiometricFrameworkStatsLogger + .toProtoWakeReasonDetails(new OperationContextExt(context)); + + assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_MOTION); + assertThat(reasonDetails).isEmpty(); + } + + @Test + public void testConvertsWakeReason_whenFaceReason() { + final OperationContext context = new OperationContext(); + context.authenticateReason = AuthenticateReason.faceAuthenticateReason( + AuthenticateReason.Face.ASSISTANT_VISIBLE); + final OperationContextExt ctx = new OperationContextExt(context); + + final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx); + final int[] reasonDetails = BiometricFrameworkStatsLogger + .toProtoWakeReasonDetails(ctx); + + assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN); + assertThat(reasonDetails).asList().containsExactly( + BiometricsProtoEnums.DETAILS_FACE_ASSISTANT_VISIBLE); + } + + @Test + public void testConvertsWakeReason_whenVendorReason() { + final OperationContext context = new OperationContext(); + context.authenticateReason = AuthenticateReason.vendorAuthenticateReason( + new AuthenticateReason.Vendor()); + final OperationContextExt ctx = new OperationContextExt(context); + + final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx); + final int[] reasonDetails = BiometricFrameworkStatsLogger + .toProtoWakeReasonDetails(ctx); + + assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_UNKNOWN); + assertThat(reasonDetails).isEmpty(); + } + + + @Test + public void testConvertsWakeReason_whenPowerAndFaceReason() { + final OperationContext context = new OperationContext(); + context.wakeReason = WakeReason.WAKE_KEY; + context.authenticateReason = AuthenticateReason.faceAuthenticateReason( + AuthenticateReason.Face.PRIMARY_BOUNCER_SHOWN); + final OperationContextExt ctx = new OperationContextExt(context); + + final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx); + final int[] reasonDetails = BiometricFrameworkStatsLogger + .toProtoWakeReasonDetails(ctx); + + assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_WAKE_KEY); + assertThat(reasonDetails).asList().containsExactly( + BiometricsProtoEnums.DETAILS_FACE_PRIMARY_BOUNCER_SHOWN); + } + + @Test + public void testConvertsWakeReason_whenPowerAndVendorReason() { + final OperationContext context = new OperationContext(); + context.wakeReason = WakeReason.LID; + context.authenticateReason = AuthenticateReason.vendorAuthenticateReason( + new AuthenticateReason.Vendor()); + final OperationContextExt ctx = new OperationContextExt(context); + + final int reason = BiometricFrameworkStatsLogger.toProtoWakeReason(ctx); + final int[] reasonDetails = BiometricFrameworkStatsLogger + .toProtoWakeReasonDetails(ctx); + + assertThat(reason).isEqualTo(BiometricsProtoEnums.WAKE_REASON_LID); + assertThat(reasonDetails).isEmpty(); + } +} diff --git a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java index 5560b41b164f..236c74fc29e6 100644 --- a/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java +++ b/services/tests/servicestests/src/com/android/server/job/JobStoreTest.java @@ -728,6 +728,66 @@ public class JobStoreTest { } @Test + public void testPersistedPreferredBatteryNotLowConstraint() throws Exception { + JobInfo.Builder b = new Builder(8, mComponent) + .setPrefersBatteryNotLow(true) + .setPersisted(true); + JobStatus taskStatus = + JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); + + mTaskStoreUnderTest.add(taskStatus); + waitForPendingIo(); + + final JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); + JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); + assertEquals("Battery-not-low constraint not persisted correctly.", + taskStatus.getJob().isPreferBatteryNotLow(), + loaded.getJob().isPreferBatteryNotLow()); + } + + @Test + public void testPersistedPreferredChargingConstraint() throws Exception { + JobInfo.Builder b = new Builder(8, mComponent) + .setPrefersCharging(true) + .setPersisted(true); + JobStatus taskStatus = + JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); + + mTaskStoreUnderTest.add(taskStatus); + waitForPendingIo(); + + final JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); + JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); + assertEquals("Charging constraint not persisted correctly.", + taskStatus.getJob().isPreferCharging(), + loaded.getJob().isPreferCharging()); + } + + @Test + public void testPersistedPreferredDeviceIdleConstraint() throws Exception { + JobInfo.Builder b = new Builder(8, mComponent) + .setPrefersDeviceIdle(true) + .setPersisted(true); + JobStatus taskStatus = + JobStatus.createFromJobInfo(b.build(), SOME_UID, null, -1, null, null); + + mTaskStoreUnderTest.add(taskStatus); + waitForPendingIo(); + + final JobSet jobStatusSet = new JobSet(); + mTaskStoreUnderTest.readJobMapFromDisk(jobStatusSet, true); + assertEquals("Incorrect # of persisted tasks.", 1, jobStatusSet.size()); + JobStatus loaded = jobStatusSet.getAllJobs().iterator().next(); + assertEquals("Idle constraint not persisted correctly.", + taskStatus.getJob().isPreferDeviceIdle(), + loaded.getJob().isPreferDeviceIdle()); + } + + @Test public void testJobWorkItems() throws Exception { JobWorkItem item1 = new JobWorkItem.Builder().build(); item1.bumpDeliveryCount(); diff --git a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java index 9570ff6323d2..86878c5384fd 100644 --- a/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java +++ b/services/tests/servicestests/src/com/android/server/usage/AppStandbyControllerTests.java @@ -160,6 +160,9 @@ public class AppStandbyControllerTests { private static final String ADMIN_PKG2 = "com.android.admin2"; private static final String ADMIN_PKG3 = "com.android.admin3"; + private static final String ADMIN_PROTECTED_PKG = "com.android.admin.protected"; + private static final String ADMIN_PROTECTED_PKG2 = "com.android.admin.protected2"; + private static final long MINUTE_MS = 60 * 1000; private static final long HOUR_MS = 60 * MINUTE_MS; private static final long DAY_MS = 24 * HOUR_MS; @@ -1758,6 +1761,19 @@ public class AppStandbyControllerTests { } @Test + public void testSetAdminProtectedPackages() { + assertAdminProtectedPackagesForTest(USER_ID, (String[]) null); + assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null); + + setAdminProtectedPackages(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2); + assertAdminProtectedPackagesForTest(USER_ID, ADMIN_PROTECTED_PKG, ADMIN_PROTECTED_PKG2); + assertAdminProtectedPackagesForTest(USER_ID2, (String[]) null); + + setAdminProtectedPackages(USER_ID, (String[]) null); + assertAdminProtectedPackagesForTest(USER_ID, (String[]) null); + } + + @Test @FlakyTest(bugId = 185169504) public void testUserInteraction_CrossProfile() throws Exception { mInjector.mRunningUsers = new int[] {USER_ID, USER_ID2, USER_ID3}; @@ -2195,6 +2211,28 @@ public class AppStandbyControllerTests { mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId); } + private void setAdminProtectedPackages(int userId, String... packageNames) { + Set<String> adminProtectedPackages = packageNames != null ? new ArraySet<>( + Arrays.asList(packageNames)) : null; + mController.setAdminProtectedPackages(adminProtectedPackages, userId); + } + + private void assertAdminProtectedPackagesForTest(int userId, String... packageNames) { + final Set<String> actualAdminProtectedPackages = + mController.getAdminProtectedPackagesForTest(userId); + if (packageNames == null) { + if (actualAdminProtectedPackages != null && !actualAdminProtectedPackages.isEmpty()) { + fail("Admin protected packages should be null; " + getAdminAppsStr(userId, + actualAdminProtectedPackages)); + } + return; + } + assertEquals(packageNames.length, actualAdminProtectedPackages.size()); + for (String adminProtectedPackage : packageNames) { + assertTrue(actualAdminProtectedPackages.contains(adminProtectedPackage)); + } + } + private void setAndAssertBucket(String pkg, int user, int bucket, int reason) throws Exception { rearmLatch(pkg); mController.setAppStandbyBucket(pkg, user, bucket, reason); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java index 2f7a5f4e0453..7a55143ba13f 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationListenerServiceTest.java @@ -21,6 +21,8 @@ import static android.service.notification.NotificationListenerService.Ranking.U import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL; import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE; +import static com.google.common.truth.Truth.assertThat; + import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -35,10 +37,12 @@ import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.app.PendingIntent; +import android.app.Person; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.ParceledListSlice; import android.content.pm.ShortcutInfo; import android.graphics.Bitmap; import android.graphics.drawable.Icon; @@ -46,11 +50,14 @@ import android.os.Binder; import android.os.Build; import android.os.IBinder; import android.os.Parcel; +import android.os.RemoteException; +import android.os.UserHandle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationListenerService.Ranking; import android.service.notification.NotificationListenerService.RankingMap; import android.service.notification.NotificationRankingUpdate; import android.service.notification.SnoozeCriterion; +import android.service.notification.StatusBarNotification; import android.test.suitebuilder.annotation.SmallTest; import androidx.test.runner.AndroidJUnit4; @@ -63,6 +70,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; @SmallTest @@ -95,6 +103,31 @@ public class NotificationListenerServiceTest extends UiServiceTestCase { } @Test + public void testGetActiveNotifications_preP_mapsExtraPeople() throws RemoteException { + TestListenerService service = new TestListenerService(); + service.attachBaseContext(mContext); + service.targetSdk = Build.VERSION_CODES.O_MR1; + + Notification notification = new Notification(); + ArrayList<Person> people = new ArrayList<>(); + people.add(new Person.Builder().setUri("uri1").setName("P1").build()); + people.add(new Person.Builder().setUri("uri2").setName("P2").build()); + notification.extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, people); + when(service.getNoMan().getActiveNotificationsFromListener(any(), any(), anyInt())) + .thenReturn(new ParceledListSlice<StatusBarNotification>(Arrays.asList( + new StatusBarNotification("pkg", "opPkg", 1, "tag", 123, 1234, + notification, UserHandle.of(0), null, 0)))); + + StatusBarNotification[] sbns = service.getActiveNotifications(); + + assertThat(sbns).hasLength(1); + String[] mappedPeople = sbns[0].getNotification().extras.getStringArray( + Notification.EXTRA_PEOPLE); + assertThat(mappedPeople).isNotNull(); + assertThat(mappedPeople).asList().containsExactly("uri1", "uri2"); + } + + @Test public void testRanking() { TestListenerService service = new TestListenerService(); service.applyUpdateLocked(generateUpdate()); diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 06bcb9134c07..50e5bbf4ba9b 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -679,10 +679,6 @@ public class PreferencesHelperTest extends UiServiceTestCase { compareChannels(ido, mHelper.getNotificationChannel(PKG_O, UID_O, ido.getId(), false)); compareChannels(idp, mHelper.getNotificationChannel(PKG_P, UID_P, idp.getId(), false)); - verify(mPermissionHelper).setNotificationPermission(nMr1Expected); - verify(mPermissionHelper).setNotificationPermission(oExpected); - verify(mPermissionHelper).setNotificationPermission(pExpected); - // verify that we also write a state for review_permissions_notification to eventually // show a notification assertEquals(NotificationManagerService.REVIEW_NOTIF_STATE_SHOULD_SHOW, diff --git a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java index 7cb7c79d63a0..43fc1c43d6ba 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/LaunchParamsPersisterTests.java @@ -111,6 +111,7 @@ public class LaunchParamsPersisterTests extends WindowTestsBase { mDisplayUniqueId = "test:" + sNextUniqueId++; mTestDisplay = new TestDisplayContent.Builder(mAtm, 1000, 1500) .setUniqueId(mDisplayUniqueId).build(); + mTestDisplay.getDefaultTaskDisplayArea().setWindowingMode(TEST_WINDOWING_MODE); when(mRootWindowContainer.getDisplayContent(eq(mDisplayUniqueId))) .thenReturn(mTestDisplay); diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java index da078a22c5ff..9b4cb134e427 100644 --- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java @@ -55,6 +55,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; import android.annotation.Nullable; import android.compat.testing.PlatformCompatChangeRule; @@ -526,6 +527,16 @@ public class LetterboxUiControllerTest extends WindowTestsBase { // overrideOrientationIfNeeded @Test + public void testOverrideOrientationIfNeeded_mapInvokedOnRequest() throws Exception { + mController = new LetterboxUiController(mWm, mActivity); + spyOn(mWm); + + mController.overrideOrientationIfNeeded(SCREEN_ORIENTATION_PORTRAIT); + + verify(mWm).mapOrientationRequest(SCREEN_ORIENTATION_PORTRAIT); + } + + @Test @EnableCompatChanges({OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT}) public void testOverrideOrientationIfNeeded_portraitOverrideEnabled_returnsPortrait() throws Exception { diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java index 5208e5a2dc2f..28241d30e168 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TaskTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/TaskTests.java @@ -1328,17 +1328,16 @@ public class TaskTests extends WindowTestsBase { spyOn(persister); final Task task = getTestTask(); - task.setHasBeenVisible(false); + task.setHasBeenVisible(true); task.getDisplayContent() .getDefaultTaskDisplayArea() - .setWindowingMode(WindowConfiguration.WINDOWING_MODE_FREEFORM); - task.getRootTask().setWindowingMode(WINDOWING_MODE_FULLSCREEN); + .setWindowingMode(WINDOWING_MODE_FREEFORM); + task.getRootTask().setWindowingMode(WINDOWING_MODE_FREEFORM); final DisplayContent oldDisplay = task.getDisplayContent(); LaunchParamsController.LaunchParams params = new LaunchParamsController.LaunchParams(); - params.mWindowingMode = WINDOWING_MODE_UNDEFINED; persister.getLaunchParams(task, null, params); - assertEquals(WINDOWING_MODE_UNDEFINED, params.mWindowingMode); + assertEquals(WINDOWING_MODE_FREEFORM, params.mWindowingMode); task.setHasBeenVisible(true); task.removeImmediately(); @@ -1346,7 +1345,7 @@ public class TaskTests extends WindowTestsBase { verify(persister).saveTask(task, oldDisplay); persister.getLaunchParams(task, null, params); - assertEquals(WINDOWING_MODE_FULLSCREEN, params.mWindowingMode); + assertEquals(WINDOWING_MODE_FREEFORM, params.mWindowingMode); } @Test diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 7d6cf4a63c43..ba6b3b6c9378 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -26,7 +26,10 @@ import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLI import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_TRUSTED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.FLAG_OWN_FOCUS; +import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY; import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -49,11 +52,13 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -67,12 +72,16 @@ import android.hardware.display.VirtualDisplay; import android.os.Binder; import android.os.Bundle; import android.os.IBinder; +import android.os.InputConfig; +import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; import android.platform.test.annotations.Presubmit; import android.util.DisplayMetrics; import android.util.MergedConfiguration; +import android.view.IWindow; import android.view.IWindowSessionCallback; +import android.view.InputChannel; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.Surface; @@ -108,6 +117,20 @@ public class WindowManagerServiceTests extends WindowTestsBase { ADD_TRUSTED_DISPLAY); @Test + public void testIsRequestedOrientationMapped() { + mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true, + /* fromOrientations */ new int[]{1}, /* toOrientations */ new int[]{2}); + assertThat(mWm.mapOrientationRequest(1)).isEqualTo(2); + assertThat(mWm.mapOrientationRequest(3)).isEqualTo(3); + + // Mapping disabled + mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ false, + /* fromOrientations */ null, /* toOrientations */ null); + assertThat(mWm.mapOrientationRequest(1)).isEqualTo(1); + assertThat(mWm.mapOrientationRequest(3)).isEqualTo(3); + } + + @Test public void testAddWindowToken() { IBinder token = mock(IBinder.class); mWm.addWindowToken(token, TYPE_TOAST, mDisplayContent.getDisplayId(), null /* options */); @@ -690,6 +713,102 @@ public class WindowManagerServiceTests extends WindowTestsBase { assertEquals(validRect, resultingArgs.mSourceCrop); } + @Test + public void testGrantInputChannel_sanitizeSpyWindowForApplications() { + final Session session = mock(Session.class); + final int callingUid = Process.FIRST_APPLICATION_UID; + final int callingPid = 1234; + final SurfaceControl surfaceControl = mock(SurfaceControl.class); + final IWindow window = mock(IWindow.class); + final IBinder windowToken = mock(IBinder.class); + when(window.asBinder()).thenReturn(windowToken); + final IBinder focusGrantToken = mock(IBinder.class); + + final InputChannel inputChannel = new InputChannel(); + assertThrows(IllegalArgumentException.class, () -> + mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, + surfaceControl, window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, + PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY, TYPE_APPLICATION, + null /* windowToken */, focusGrantToken, "TestInputChannel", + inputChannel)); + } + + @Test + public void testGrantInputChannel_allowSpyWindowForInputMonitorPermission() { + final Session session = mock(Session.class); + final int callingUid = Process.SYSTEM_UID; + final int callingPid = 1234; + final SurfaceControl surfaceControl = mock(SurfaceControl.class); + final IWindow window = mock(IWindow.class); + final IBinder windowToken = mock(IBinder.class); + when(window.asBinder()).thenReturn(windowToken); + final IBinder focusGrantToken = mock(IBinder.class); + + final InputChannel inputChannel = new InputChannel(); + mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl, + window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, + INPUT_FEATURE_SPY, TYPE_APPLICATION, null /* windowToken */, focusGrantToken, + "TestInputChannel", inputChannel); + + verify(mTransaction).setInputWindowInfo( + eq(surfaceControl), + argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY)); + } + + @Test + public void testUpdateInputChannel_sanitizeSpyWindowForApplications() { + final Session session = mock(Session.class); + final int callingUid = Process.FIRST_APPLICATION_UID; + final int callingPid = 1234; + final SurfaceControl surfaceControl = mock(SurfaceControl.class); + final IWindow window = mock(IWindow.class); + final IBinder windowToken = mock(IBinder.class); + when(window.asBinder()).thenReturn(windowToken); + final IBinder focusGrantToken = mock(IBinder.class); + + final InputChannel inputChannel = new InputChannel(); + mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl, + window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, + 0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, focusGrantToken, + "TestInputChannel", inputChannel); + verify(mTransaction).setInputWindowInfo( + eq(surfaceControl), + argThat(h -> (h.inputConfig & InputConfig.SPY) == 0)); + + assertThrows(IllegalArgumentException.class, () -> + mWm.updateInputChannel(inputChannel.getToken(), DEFAULT_DISPLAY, surfaceControl, + FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY, + null /* region */)); + } + + @Test + public void testUpdateInputChannel_allowSpyWindowForInputMonitorPermission() { + final Session session = mock(Session.class); + final int callingUid = Process.SYSTEM_UID; + final int callingPid = 1234; + final SurfaceControl surfaceControl = mock(SurfaceControl.class); + final IWindow window = mock(IWindow.class); + final IBinder windowToken = mock(IBinder.class); + when(window.asBinder()).thenReturn(windowToken); + final IBinder focusGrantToken = mock(IBinder.class); + + final InputChannel inputChannel = new InputChannel(); + mWm.grantInputChannel(session, callingUid, callingPid, DEFAULT_DISPLAY, surfaceControl, + window, null /* hostInputToken */, FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, + 0 /* inputFeatures */, TYPE_APPLICATION, null /* windowToken */, focusGrantToken, + "TestInputChannel", inputChannel); + verify(mTransaction).setInputWindowInfo( + eq(surfaceControl), + argThat(h -> (h.inputConfig & InputConfig.SPY) == 0)); + + mWm.updateInputChannel(inputChannel.getToken(), DEFAULT_DISPLAY, surfaceControl, + FLAG_NOT_FOCUSABLE, PRIVATE_FLAG_TRUSTED_OVERLAY, INPUT_FEATURE_SPY, + null /* region */); + verify(mTransaction).setInputWindowInfo( + eq(surfaceControl), + argThat(h -> (h.inputConfig & InputConfig.SPY) == InputConfig.SPY)); + } + private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) { final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class); when(remoteToken.toWindowContainerToken()).thenReturn(wct); diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 7ff5b4a28f1b..8948e494d851 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -3129,6 +3129,11 @@ public class UsageStatsService extends SystemService implements } @Override + public void setAdminProtectedPackages(Set<String> packageNames, int userId) { + mAppStandby.setAdminProtectedPackages(packageNames, userId); + } + + @Override public void onAdminDataAvailable() { mAppStandby.onAdminDataAvailable(); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 212dc41b3de3..7aa1334fd1c3 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -8654,6 +8654,16 @@ public class CarrierConfigManager { public static final String KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY = KEY_PREFIX + "epdg_address_priority_int_array"; + /** + * A priority list of PLMN to be used in EPDG_ADDRESS_PLMN. Possible values are {@link + * #EPDG_PLMN_RPLMN}, {@link #EPDG_PLMN_HPLMN}, {@link #EPDG_PLMN_EHPLMN_ALL}, {@link + * #EPDG_PLMN_EHPLMN_FIRST} + * + * @hide + */ + public static final String KEY_EPDG_PLMN_PRIORITY_INT_ARRAY = + KEY_PREFIX + "epdg_plmn_priority_int_array"; + /** Epdg static IP address or FQDN */ public static final String KEY_EPDG_STATIC_ADDRESS_STRING = KEY_PREFIX + "epdg_static_address_string"; @@ -8854,6 +8864,36 @@ public class CarrierConfigManager { public static final int EPDG_ADDRESS_VISITED_COUNTRY = 4; /** @hide */ + @IntDef({ + EPDG_PLMN_RPLMN, + EPDG_PLMN_HPLMN, + EPDG_PLMN_EHPLMN_ALL, + EPDG_PLMN_EHPLMN_FIRST + }) + public @interface EpdgAddressPlmnType {} + + /** + * Use the Registered PLMN + * @hide + */ + public static final int EPDG_PLMN_RPLMN = 0; + /** + * Use the PLMN derived from IMSI + * @hide + */ + public static final int EPDG_PLMN_HPLMN = 1; + /** + * Use all EHPLMN from SIM EF files + * @hide + */ + public static final int EPDG_PLMN_EHPLMN_ALL = 2; + /** + * Use the first EHPLMN from SIM EF files + * @hide + */ + public static final int EPDG_PLMN_EHPLMN_FIRST = 3; + + /** @hide */ @IntDef({ID_TYPE_FQDN, ID_TYPE_RFC822_ADDR, ID_TYPE_KEY_ID}) public @interface IkeIdType {} @@ -8988,6 +9028,12 @@ public class CarrierConfigManager { defaults.putIntArray( KEY_EPDG_ADDRESS_PRIORITY_INT_ARRAY, new int[] {EPDG_ADDRESS_PLMN, EPDG_ADDRESS_STATIC}); + defaults.putIntArray( + KEY_EPDG_PLMN_PRIORITY_INT_ARRAY, + new int[]{ + EPDG_PLMN_RPLMN, + EPDG_PLMN_HPLMN, + EPDG_PLMN_EHPLMN_ALL}); defaults.putStringArray(KEY_MCC_MNCS_STRING_ARRAY, new String[0]); defaults.putInt(KEY_IKE_LOCAL_ID_TYPE_INT, ID_TYPE_RFC822_ADDR); defaults.putInt(KEY_IKE_REMOTE_ID_TYPE_INT, ID_TYPE_FQDN); diff --git a/tests/Input/Android.bp b/tests/Input/Android.bp index 83893ba46885..a4c48fd5f342 100644 --- a/tests/Input/Android.bp +++ b/tests/Input/Android.bp @@ -13,6 +13,9 @@ android_test { "src/**/*.java", "src/**/*.kt", ], + kotlincflags: [ + "-Werror", + ], platform_apis: true, certificate: "platform", static_libs: [ diff --git a/tests/Input/src/com/android/test/input/AnrTest.kt b/tests/Input/src/com/android/test/input/AnrTest.kt index 1d65cc35c3bc..0246426c1d33 100644 --- a/tests/Input/src/com/android/test/input/AnrTest.kt +++ b/tests/Input/src/com/android/test/input/AnrTest.kt @@ -73,7 +73,7 @@ class AnrTest { val contentResolver = instrumentation.targetContext.contentResolver hideErrorDialogs = Settings.Global.getInt(contentResolver, HIDE_ERROR_DIALOGS, 0) Settings.Global.putInt(contentResolver, HIDE_ERROR_DIALOGS, 0) - PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage().getName() + PACKAGE_NAME = UnresponsiveGestureMonitorActivity::class.java.getPackage()!!.getName() } @After diff --git a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt index d83a4570fedc..3a24406e2b73 100644 --- a/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt +++ b/tests/Input/src/com/android/test/input/UnresponsiveGestureMonitorActivity.kt @@ -45,7 +45,8 @@ class UnresponsiveGestureMonitorActivity : Activity() { private lateinit var mInputMonitor: InputMonitor override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - mInputMonitor = InputManager.getInstance().monitorGestureInput(MONITOR_NAME, displayId) + val inputManager = getSystemService(InputManager::class.java) + mInputMonitor = inputManager.monitorGestureInput(MONITOR_NAME, displayId) mInputEventReceiver = UnresponsiveReceiver( mInputMonitor.getInputChannel(), Looper.myLooper()) } diff --git a/tools/locked_region_code_injection/Android.bp b/tools/locked_region_code_injection/Android.bp index a0cc446cd42d..954b816a52bf 100644 --- a/tools/locked_region_code_injection/Android.bp +++ b/tools/locked_region_code_injection/Android.bp @@ -19,3 +19,20 @@ java_binary_host { "ow2-asm-tree", ], } + +java_library_host { + name: "lockedregioncodeinjection_input", + manifest: "test/manifest.txt", + srcs: ["test/*/*.java"], + static_libs: [ + "guava", + "ow2-asm", + "ow2-asm-analysis", + "ow2-asm-commons", + "ow2-asm-tree", + "hamcrest-library", + "hamcrest", + "platform-test-annotations", + "junit", + ], +} diff --git a/tools/locked_region_code_injection/OWNERS b/tools/locked_region_code_injection/OWNERS new file mode 100644 index 000000000000..bd43f1736ca5 --- /dev/null +++ b/tools/locked_region_code_injection/OWNERS @@ -0,0 +1,4 @@ +# Everyone in frameworks/base is included by default +shayba@google.com +shombert@google.com +timmurray@google.com diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java index 81a077324e6c..2067bb4ef2fe 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockFindingClassVisitor.java @@ -13,37 +13,51 @@ */ package lockedregioncodeinjection; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedList; -import java.util.List; +import static com.google.common.base.Preconditions.checkElementIndex; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.commons.TryCatchBlockSorter; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.TryCatchBlockNode; import org.objectweb.asm.tree.analysis.Analyzer; import org.objectweb.asm.tree.analysis.AnalyzerException; import org.objectweb.asm.tree.analysis.BasicValue; import org.objectweb.asm.tree.analysis.Frame; -import static com.google.common.base.Preconditions.checkElementIndex; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.google.common.base.Preconditions.checkState; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; /** - * This visitor does two things: + * This visitor operates on two kinds of targets. For a legacy target, it does the following: * - * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and insert the corresponding pre + * 1. Finds all the MONITOR_ENTER / MONITOR_EXIT in the byte code and inserts the corresponding pre * and post methods calls should it matches one of the given target type in the Configuration. * * 2. Find all methods that are synchronized and insert pre method calls in the beginning and post * method calls just before all return instructions. + * + * For a scoped target, it does the following: + * + * 1. Finds all the MONITOR_ENTER instructions in the byte code. If the target of the opcode is + * named in a --scope switch, then the pre method is invoked ON THE TARGET immediately after + * MONITOR_ENTER opcode completes. + * + * 2. Finds all the MONITOR_EXIT instructions in the byte code. If the target of the opcode is + * named in a --scope switch, then the post method is invoked ON THE TARGET immediately before + * MONITOR_EXIT opcode completes. */ class LockFindingClassVisitor extends ClassVisitor { private String className = null; @@ -73,12 +87,16 @@ class LockFindingClassVisitor extends ClassVisitor { class LockFindingMethodVisitor extends MethodVisitor { private String owner; private MethodVisitor chain; + private final String className; + private final String methodName; public LockFindingMethodVisitor(String owner, MethodNode mn, MethodVisitor chain) { super(Utils.ASM_VERSION, mn); assert owner != null; this.owner = owner; this.chain = chain; + className = owner; + methodName = mn.name; } @SuppressWarnings("unchecked") @@ -93,6 +111,12 @@ class LockFindingClassVisitor extends ClassVisitor { for (LockTarget t : targets) { if (t.getTargetDesc().equals("L" + owner + ";")) { ownerMonitor = t; + if (ownerMonitor.getScoped()) { + final String emsg = String.format( + "scoped targets do not support synchronized methods in %s.%s()", + className, methodName); + throw new RuntimeException(emsg); + } } } } @@ -118,9 +142,11 @@ class LockFindingClassVisitor extends ClassVisitor { AbstractInsnNode s = instructions.getFirst(); MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPreOwner(), ownerMonitor.getPreMethod(), "()V", false); - insertMethodCallBefore(mn, frameMap, handlersMap, s, 0, call); + insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, 0, call); } + boolean anyDup = false; + for (int i = 0; i < instructions.size(); i++) { AbstractInsnNode s = instructions.get(i); @@ -131,9 +157,15 @@ class LockFindingClassVisitor extends ClassVisitor { LockTargetState state = (LockTargetState) operand; for (int j = 0; j < state.getTargets().size(); j++) { LockTarget target = state.getTargets().get(j); - MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, - target.getPreOwner(), target.getPreMethod(), "()V", false); - insertMethodCallAfter(mn, frameMap, handlersMap, s, i, call); + MethodInsnNode call = methodCall(target, true); + if (target.getScoped()) { + TypeInsnNode cast = typeCast(target); + i += insertInvokeAcquire(mn, frameMap, handlersMap, s, i, + call, cast); + anyDup = true; + } else { + i += insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call); + } } } } @@ -144,8 +176,9 @@ class LockFindingClassVisitor extends ClassVisitor { if (operand instanceof LockTargetState) { LockTargetState state = (LockTargetState) operand; for (int j = 0; j < state.getTargets().size(); j++) { - // The instruction after a monitor_exit should be a label for the end of the implicit - // catch block that surrounds the synchronized block to call monitor_exit when an exception + // The instruction after a monitor_exit should be a label for + // the end of the implicit catch block that surrounds the + // synchronized block to call monitor_exit when an exception // occurs. checkState(instructions.get(i + 1).getType() == AbstractInsnNode.LABEL, "Expected to find label after monitor exit"); @@ -161,9 +194,16 @@ class LockFindingClassVisitor extends ClassVisitor { "Expected label to be the end of monitor exit's try block"); LockTarget target = state.getTargets().get(j); - MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, - target.getPostOwner(), target.getPostMethod(), "()V", false); - insertMethodCallAfter(mn, frameMap, handlersMap, label, labelIndex, call); + MethodInsnNode call = methodCall(target, false); + if (target.getScoped()) { + TypeInsnNode cast = typeCast(target); + i += insertInvokeRelease(mn, frameMap, handlersMap, s, i, + call, cast); + anyDup = true; + } else { + insertMethodCallAfter(mn, frameMap, handlersMap, label, + labelIndex, call); + } } } } @@ -174,16 +214,116 @@ class LockFindingClassVisitor extends ClassVisitor { MethodInsnNode call = new MethodInsnNode(Opcodes.INVOKESTATIC, ownerMonitor.getPostOwner(), ownerMonitor.getPostMethod(), "()V", false); - insertMethodCallBefore(mn, frameMap, handlersMap, s, i, call); + insertMethodCallBeforeSync(mn, frameMap, handlersMap, s, i, call); i++; // Skip ahead. Otherwise, we will revisit this instruction again. } } + + if (anyDup) { + mn.maxStack++; + } + super.visitEnd(); mn.accept(chain); } + + // Insert a call to a monitor pre handler. The node and the index identify the + // monitorenter call itself. Insert DUP immediately prior to the MONITORENTER. + // Insert the typecast and call (in that order) after the MONITORENTER. + public int insertInvokeAcquire(MethodNode mn, List<Frame> frameMap, + List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index, + MethodInsnNode call, TypeInsnNode cast) { + InsnList instructions = mn.instructions; + + // Insert a DUP right before MONITORENTER, to capture the object being locked. + // Note that the object will be typed as java.lang.Object. + instructions.insertBefore(node, new InsnNode(Opcodes.DUP)); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + // Insert the call right after the MONITORENTER. These entries are pushed after + // MONITORENTER so they are inserted in reverse order. MONITORENTER should be + // the target of a try/catch block, which means it must be immediately + // followed by a label (which is part of the try/catch block definition). + // Move forward past the label so the invocation in inside the proper block. + // Throw an error if the next instruction is not a label. + node = node.getNext(); + if (!(node instanceof LabelNode)) { + throw new RuntimeException(String.format("invalid bytecode sequence in %s.%s()", + className, methodName)); + } + node = node.getNext(); + index = instructions.indexOf(node); + + instructions.insertBefore(node, cast); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + instructions.insertBefore(node, call); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + return 3; + } + + // Insert instructions completely before the current opcode. This is slightly + // different from insertMethodCallBefore(), which inserts the call before MONITOREXIT + // but inserts the start and end labels after MONITOREXIT. + public int insertInvokeRelease(MethodNode mn, List<Frame> frameMap, + List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index, + MethodInsnNode call, TypeInsnNode cast) { + InsnList instructions = mn.instructions; + + instructions.insertBefore(node, new InsnNode(Opcodes.DUP)); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + instructions.insertBefore(node, cast); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + instructions.insertBefore(node, call); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + return 3; + } + } + + public static MethodInsnNode methodCall(LockTarget target, boolean pre) { + String spec = "()V"; + if (!target.getScoped()) { + if (pre) { + return new MethodInsnNode( + Opcodes.INVOKESTATIC, target.getPreOwner(), target.getPreMethod(), spec); + } else { + return new MethodInsnNode( + Opcodes.INVOKESTATIC, target.getPostOwner(), target.getPostMethod(), spec); + } + } else { + if (pre) { + return new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, target.getPreOwner(), target.getPreMethod(), spec); + } else { + return new MethodInsnNode( + Opcodes.INVOKEVIRTUAL, target.getPostOwner(), target.getPostMethod(), spec); + } + } } - public static void insertMethodCallBefore(MethodNode mn, List<Frame> frameMap, + public static TypeInsnNode typeCast(LockTarget target) { + if (!target.getScoped()) { + return null; + } else { + // preOwner and postOwner return the same string for scoped targets. + return new TypeInsnNode(Opcodes.CHECKCAST, target.getPreOwner()); + } + } + + /** + * Insert a method call before the beginning or end of a synchronized method. + */ + public static void insertMethodCallBeforeSync(MethodNode mn, List<Frame> frameMap, List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index, MethodInsnNode call) { List<TryCatchBlockNode> handlers = handlersMap.get(index); @@ -226,6 +366,22 @@ class LockFindingClassVisitor extends ClassVisitor { updateCatchHandler(mn, handlers, start, end, handlersMap); } + // Insert instructions completely before the current opcode. This is slightly different from + // insertMethodCallBeforeSync(), which inserts the call before MONITOREXIT but inserts the + // start and end labels after MONITOREXIT. + public int insertMethodCallBefore(MethodNode mn, List<Frame> frameMap, + List<List<TryCatchBlockNode>> handlersMap, AbstractInsnNode node, int index, + MethodInsnNode call) { + InsnList instructions = mn.instructions; + + instructions.insertBefore(node, call); + frameMap.add(index, frameMap.get(index)); + handlersMap.add(index, handlersMap.get(index)); + + return 1; + } + + @SuppressWarnings("unchecked") public static void updateCatchHandler(MethodNode mn, List<TryCatchBlockNode> handlers, LabelNode start, LabelNode end, List<List<TryCatchBlockNode>> handlersMap) { diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java index c5e59e3dc64d..5f6240327a7f 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTarget.java @@ -21,14 +21,28 @@ package lockedregioncodeinjection; public class LockTarget { public static final LockTarget NO_TARGET = new LockTarget("", null, null); + // The lock which must be instrumented, in Java internal form (L<path>;). private final String targetDesc; + // The methods to be called when the lock is taken (released). For non-scoped locks, + // these are fully qualified static methods. For scoped locks, these are the + // unqualified names of a member method of the target lock. private final String pre; private final String post; + // If true, the pre and post methods are virtual on the target class. The pre and post methods + // are both called while the lock is held. If this field is false then the pre and post methods + // take no parameters and the post method is called after the lock is released. This is legacy + // behavior. + private final boolean scoped; - public LockTarget(String targetDesc, String pre, String post) { + public LockTarget(String targetDesc, String pre, String post, boolean scoped) { this.targetDesc = targetDesc; this.pre = pre; this.post = post; + this.scoped = scoped; + } + + public LockTarget(String targetDesc, String pre, String post) { + this(targetDesc, pre, post, false); } public String getTargetDesc() { @@ -40,7 +54,11 @@ public class LockTarget { } public String getPreOwner() { - return pre.substring(0, pre.lastIndexOf('.')); + if (scoped) { + return targetDesc.substring(1, targetDesc.length() - 1); + } else { + return pre.substring(0, pre.lastIndexOf('.')); + } } public String getPreMethod() { @@ -52,10 +70,23 @@ public class LockTarget { } public String getPostOwner() { - return post.substring(0, post.lastIndexOf('.')); + if (scoped) { + return targetDesc.substring(1, targetDesc.length() - 1); + } else { + return post.substring(0, post.lastIndexOf('.')); + } } public String getPostMethod() { return post.substring(post.lastIndexOf('.') + 1); } + + public boolean getScoped() { + return scoped; + } + + @Override + public String toString() { + return targetDesc + ":" + pre + ":" + post; + } } diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java index 99d841829132..5df0160abd8c 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/LockTargetState.java @@ -13,10 +13,11 @@ */ package lockedregioncodeinjection; -import java.util.List; import org.objectweb.asm.Type; import org.objectweb.asm.tree.analysis.BasicValue; +import java.util.List; + public class LockTargetState extends BasicValue { private final List<LockTarget> lockTargets; @@ -31,4 +32,10 @@ public class LockTargetState extends BasicValue { public List<LockTarget> getTargets() { return lockTargets; } + + @Override + public String toString() { + return "LockTargetState(" + getType().getDescriptor() + + ", " + lockTargets.size() + ")"; + } } diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java index 828cce72dda9..d22ea2338ff7 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Main.java @@ -21,7 +21,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.util.Collections; +import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.zip.ZipEntry; @@ -36,6 +36,7 @@ public class Main { String legacyTargets = null; String legacyPreMethods = null; String legacyPostMethods = null; + List<LockTarget> targets = new ArrayList<>(); for (int i = 0; i < args.length; i++) { if ("-i".equals(args[i].trim())) { i++; @@ -52,23 +53,25 @@ public class Main { } else if ("--post".equals(args[i].trim())) { i++; legacyPostMethods = args[i].trim(); + } else if ("--scoped".equals(args[i].trim())) { + i++; + targets.add(Utils.getScopedTarget(args[i].trim())); } - } - // TODO(acleung): Better help message than asserts. - assert inJar != null; - assert outJar != null; + if (inJar == null) { + throw new RuntimeException("missing input jar path"); + } + if (outJar == null) { + throw new RuntimeException("missing output jar path"); + } assert legacyTargets == null || (legacyPreMethods != null && legacyPostMethods != null); ZipFile zipSrc = new ZipFile(inJar); ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(outJar)); - List<LockTarget> targets = null; if (legacyTargets != null) { - targets = Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods, - legacyPostMethods); - } else { - targets = Collections.emptyList(); + targets.addAll(Utils.getTargetsFromLegacyJackConfig(legacyTargets, legacyPreMethods, + legacyPostMethods)); } Enumeration<? extends ZipEntry> srcEntries = zipSrc.entries(); diff --git a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java index b44e8b42f052..bfef10611e6c 100644 --- a/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java +++ b/tools/locked_region_code_injection/src/lockedregioncodeinjection/Utils.java @@ -44,4 +44,27 @@ public class Utils { return config; } + + /** + * Returns a single {@link LockTarget} from a string. The target is a comma-separated list of + * the target class, the request method, the release method, and a boolean which is true if this + * is a scoped target and false if this is a legacy target. The boolean is optional and + * defaults to true. + */ + public static LockTarget getScopedTarget(String arg) { + String[] c = arg.split(","); + if (c.length == 3) { + return new LockTarget(c[0], c[1], c[2], true); + } else if (c.length == 4) { + if (c[3].equals("true")) { + return new LockTarget(c[0], c[1], c[2], true); + } else if (c[3].equals("false")) { + return new LockTarget(c[0], c[1], c[2], false); + } else { + System.err.println("illegal target parameter \"" + c[3] + "\""); + } + } + // Fall through + throw new RuntimeException("invalid scoped target format"); + } } diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java index 31fa0bf63416..28f00b9c897a 100644 --- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestMain.java @@ -17,7 +17,10 @@ import org.junit.Assert; import org.junit.Test; /** - * To run the unit tests: + * To run the unit tests, first build the two necessary artifacts. Do this explicitly as they are + * not generally retained by a normal "build all". After lunching a target: + * m lockedregioncodeinjection + * m lockedregioncodeinjection_input * * <pre> * <code> @@ -29,31 +32,25 @@ import org.junit.Test; * mkdir -p out * rm -fr out/* * - * # Make booster - * javac -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar src/*/*.java -d out/ - * pushd out - * jar cfe lockedregioncodeinjection.jar lockedregioncodeinjection.Main */*.class - * popd - * - * # Make unit tests. - * javac -cp lib/junit-4.12.jar test/*/*.java -d out/ - * - * pushd out - * jar cfe test_input.jar lockedregioncodeinjection.Test */*.class - * popd + * # Paths to the build artifacts. These assume linux-x86; YMMV. + * ROOT=$TOP/out/host/linux-x86 + * EXE=$ROOT/bin/lockedregioncodeinjection + * INPUT=$ROOT/frameworkd/lockedregioncodeinjection_input.jar * * # Run tool on unit tests. - * java -ea -cp lib/asm-7.0_BETA.jar:lib/asm-commons-7.0_BETA.jar:lib/asm-tree-7.0_BETA.jar:lib/asm-analysis-7.0_BETA.jar:lib/guava-21.0.jar:out/lockedregioncodeinjection.jar \ - * lockedregioncodeinjection.Main \ - * -i out/test_input.jar -o out/test_output.jar \ + * $EXE -i $INPUT -o out/test_output.jar \ * --targets 'Llockedregioncodeinjection/TestTarget;' \ * --pre 'lockedregioncodeinjection/TestTarget.boost' \ * --post 'lockedregioncodeinjection/TestTarget.unboost' * * # Run unit tests. - * java -ea -cp lib/hamcrest-core-1.3.jar:lib/junit-4.12.jar:out/test_output.jar \ + * java -ea -cp out/test_output.jar \ * org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain * </code> + * OR + * <code> + * bash test/unit-test.sh + * </code> * </pre> */ public class TestMain { @@ -64,7 +61,9 @@ public class TestMain { Assert.assertEquals(TestTarget.boostCount, 0); Assert.assertEquals(TestTarget.unboostCount, 0); - Assert.assertEquals(TestTarget.unboostCount, 0); + Assert.assertEquals(TestTarget.invokeCount, 0); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); synchronized (t) { Assert.assertEquals(TestTarget.boostCount, 1); @@ -75,6 +74,8 @@ public class TestMain { Assert.assertEquals(TestTarget.boostCount, 1); Assert.assertEquals(TestTarget.unboostCount, 1); Assert.assertEquals(TestTarget.invokeCount, 1); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); } @Test @@ -84,12 +85,16 @@ public class TestMain { Assert.assertEquals(TestTarget.boostCount, 0); Assert.assertEquals(TestTarget.unboostCount, 0); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); t.synchronizedCall(); Assert.assertEquals(TestTarget.boostCount, 1); Assert.assertEquals(TestTarget.unboostCount, 1); Assert.assertEquals(TestTarget.invokeCount, 1); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); } @Test @@ -99,12 +104,16 @@ public class TestMain { Assert.assertEquals(TestTarget.boostCount, 0); Assert.assertEquals(TestTarget.unboostCount, 0); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); t.synchronizedCallReturnInt(); Assert.assertEquals(TestTarget.boostCount, 1); Assert.assertEquals(TestTarget.unboostCount, 1); Assert.assertEquals(TestTarget.invokeCount, 1); + Assert.assertEquals(TestTarget.boostCountLocked, 0); + Assert.assertEquals(TestTarget.unboostCountLocked, 0); } @Test @@ -253,4 +262,125 @@ public class TestMain { Assert.assertEquals(TestTarget.invokeCount, 1); } + @Test + public void testScopedTarget() { + TestScopedTarget target = new TestScopedTarget(); + Assert.assertEquals(0, target.scopedLock().mLevel); + + synchronized (target.scopedLock()) { + Assert.assertEquals(1, target.scopedLock().mLevel); + } + Assert.assertEquals(0, target.scopedLock().mLevel); + + synchronized (target.scopedLock()) { + synchronized (target.scopedLock()) { + Assert.assertEquals(2, target.scopedLock().mLevel); + } + } + Assert.assertEquals(0, target.scopedLock().mLevel); + } + + @Test + public void testScopedExceptionHandling() { + TestScopedTarget target = new TestScopedTarget(); + Assert.assertEquals(0, target.scopedLock().mLevel); + + boolean handled; + + // 1: an exception inside the block properly releases the lock. + handled = false; + try { + synchronized (target.scopedLock()) { + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + Assert.assertEquals(1, target.scopedLock().mLevel); + throw new RuntimeException(); + } + } catch (RuntimeException e) { + Assert.assertEquals(0, target.scopedLock().mLevel); + handled = true; + } + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, handled); + // Just verify that the lock can still be taken + Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); + + // 2: An exception inside the monitor enter function + handled = false; + target.throwOnEnter(true); + try { + synchronized (target.scopedLock()) { + // The exception was thrown inside monitorEnter(), so the code should + // never reach this point. + Assert.assertEquals(0, 1); + } + } catch (RuntimeException e) { + Assert.assertEquals(0, target.scopedLock().mLevel); + handled = true; + } + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, handled); + // Just verify that the lock can still be taken + Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); + + // 3: An exception inside the monitor exit function + handled = false; + target.throwOnEnter(true); + try { + synchronized (target.scopedLock()) { + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + Assert.assertEquals(1, target.scopedLock().mLevel); + } + } catch (RuntimeException e) { + Assert.assertEquals(0, target.scopedLock().mLevel); + handled = true; + } + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, handled); + // Just verify that the lock can still be taken + Assert.assertEquals(false, Thread.holdsLock(target.scopedLock())); + } + + // Provide an in-class type conversion for the scoped target. + private Object untypedLock(TestScopedTarget target) { + return target.scopedLock(); + } + + @Test + public void testScopedLockTyping() { + TestScopedTarget target = new TestScopedTarget(); + Assert.assertEquals(target.scopedLock().mLevel, 0); + + // Scoped lock injection works on the static type of an object. In general, it is + // a very bad idea to do type conversion on scoped locks, but the general rule is + // that conversions within a single method are recognized by the lock injection + // tool and injection occurs. Conversions outside a single method are not + // recognized and injection does not occur. + + // 1. Conversion occurs outside the class. The visible type of the lock is Object + // in this block, so no injection takes place on 'untypedLock', even though the + // dynamic type is TestScopedLock. + synchronized (target.untypedLock()) { + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock); + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + } + + // 2. Conversion occurs inside the class but in another method. The visible type + // of the lock is Object in this block, so no injection takes place on + // 'untypedLock', even though the dynamic type is TestScopedLock. + synchronized (untypedLock(target)) { + Assert.assertEquals(0, target.scopedLock().mLevel); + Assert.assertEquals(true, target.untypedLock() instanceof TestScopedLock); + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + } + + // 3. Conversion occurs inside the method. The compiler can determine the type of + // the lock within a single function, so injection does take place here. + Object untypedLock = target.scopedLock(); + synchronized (untypedLock) { + Assert.assertEquals(1, target.scopedLock().mLevel); + Assert.assertEquals(true, untypedLock instanceof TestScopedLock); + Assert.assertEquals(true, Thread.holdsLock(target.scopedLock())); + } + } } diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java new file mode 100644 index 000000000000..7441d2b1da48 --- /dev/null +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedLock.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2017 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 lockedregioncodeinjection; + +public class TestScopedLock { + public int mEntered = 0; + public int mExited = 0; + + public int mLevel = 0; + private final TestScopedTarget mTarget; + + TestScopedLock(TestScopedTarget target) { + mTarget = target; + } + + void monitorEnter() { + mLevel++; + mEntered++; + mTarget.enter(mLevel); + } + + void monitorExit() { + mLevel--; + mExited++; + mTarget.exit(mLevel); + } +} diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java new file mode 100644 index 000000000000..c80975e9c6b3 --- /dev/null +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestScopedTarget.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2017 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 lockedregioncodeinjection; + +public class TestScopedTarget { + + public final TestScopedLock mLock;; + + private boolean mNextEnterThrows = false; + private boolean mNextExitThrows = false; + + TestScopedTarget() { + mLock = new TestScopedLock(this); + } + + TestScopedLock scopedLock() { + return mLock; + } + + Object untypedLock() { + return mLock; + } + + void enter(int level) { + if (mNextEnterThrows) { + mNextEnterThrows = false; + throw new RuntimeException(); + } + } + + void exit(int level) { + if (mNextExitThrows) { + mNextExitThrows = false; + throw new RuntimeException(); + } + } + + void throwOnEnter(boolean b) { + mNextEnterThrows = b; + } + + void throwOnExit(boolean b) { + mNextExitThrows = b; + } +} diff --git a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java index d1c8f340a598..e3ba6a77e3b7 100644 --- a/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java +++ b/tools/locked_region_code_injection/test/lockedregioncodeinjection/TestTarget.java @@ -19,8 +19,17 @@ public class TestTarget { public static int invokeCount = 0; public static boolean nextUnboostThrows = false; + // If this is not null, then this is the lock under test. The lock must not be held when boost() + // or unboost() are called. + public static Object mLock = null; + public static int boostCountLocked = 0; + public static int unboostCountLocked = 0; + public static void boost() { boostCount++; + if (mLock != null && Thread.currentThread().holdsLock(mLock)) { + boostCountLocked++; + } } public static void unboost() { @@ -29,6 +38,9 @@ public class TestTarget { throw new RuntimeException(); } unboostCount++; + if (mLock != null && Thread.currentThread().holdsLock(mLock)) { + unboostCountLocked++; + } } public static void invoke() { diff --git a/tools/locked_region_code_injection/test/manifest.txt b/tools/locked_region_code_injection/test/manifest.txt new file mode 100644 index 000000000000..2314c188721c --- /dev/null +++ b/tools/locked_region_code_injection/test/manifest.txt @@ -0,0 +1 @@ +Main-Class: org.junit.runner.JUnitCore diff --git a/tools/locked_region_code_injection/test/unit-test.sh b/tools/locked_region_code_injection/test/unit-test.sh new file mode 100755 index 000000000000..9fa6f39af14a --- /dev/null +++ b/tools/locked_region_code_injection/test/unit-test.sh @@ -0,0 +1,98 @@ +#! /bin/bash +# + +# Copyright (C) 2023 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. + +# This script runs the tests for the lockedregioninjectioncode. See +# TestMain.java for the invocation. The script expects that a full build has +# already been done and artifacts are in $TOP/out. + +# Compute the default top of the workspace. The following code copies the +# strategy of croot. (croot cannot be usd directly because it is a function and +# functions are not carried over into subshells.) This gives the correct answer +# if run from inside a workspace. If run from outside a workspace, supply TOP +# on the command line. +TOPFILE=build/make/core/envsetup.mk +TOP=$(dirname $(realpath $0)) +while [[ ! $TOP = / && ! -f $TOP/$TOPFILE ]]; do + TOP=$(dirname $TOP) +done +# TOP is "/" if this script is located outside a workspace. + +# If the user supplied a top directory, use it instead +if [[ -n $1 ]]; then + TOP=$1 + shift +fi +if [[ -z $TOP || $TOP = / ]]; then + echo "usage: $0 <workspace-root>" + exit 1 +elif [[ ! -d $TOP ]]; then + echo "$TOP is not a directory" + exit 1 +elif [[ ! -d $TOP/prebuilts/misc/common ]]; then + echo "$TOP does not look like w workspace" + exit 1 +fi +echo "Using workspace $TOP" + +# Pick up the current java compiler. The lunch target is not very important, +# since most, if not all, will use the same host binaries. +pushd $TOP > /dev/null +. build/envsetup.sh > /dev/null 2>&1 +lunch redfin-userdebug > /dev/null 2>&1 +popd > /dev/null + +# Bail on any error +set -o pipefail +trap 'exit 1' ERR + +# Create the two sources +pushd $TOP > /dev/null +m lockedregioncodeinjection +m lockedregioncodeinjection_input +popd > /dev/null + +# Create a temporary directory outside of the workspace. +OUT=$TOP/out/host/test/lockedregioncodeinjection +echo + +# Clean the directory +if [[ -d $OUT ]]; then rm -r $OUT; fi +mkdir -p $OUT + +ROOT=$TOP/out/host/linux-x86 +EXE=$ROOT/bin/lockedregioncodeinjection +INP=$ROOT/framework/lockedregioncodeinjection_input.jar + +# Run tool on unit tests. +$EXE \ + -i $INP -o $OUT/test_output.jar \ + --targets 'Llockedregioncodeinjection/TestTarget;' \ + --pre 'lockedregioncodeinjection/TestTarget.boost' \ + --post 'lockedregioncodeinjection/TestTarget.unboost' \ + --scoped 'Llockedregioncodeinjection/TestScopedLock;,monitorEnter,monitorExit' + +# Run unit tests. +java -ea -cp $OUT/test_output.jar \ + org.junit.runner.JUnitCore lockedregioncodeinjection.TestMain + +# Extract the class files and decompile them for possible post-analysis. +pushd $OUT > /dev/null +jar -x --file test_output.jar lockedregioncodeinjection +for class in lockedregioncodeinjection/*.class; do + javap -c -v $class > ${class%.class}.asm +done +popd > /dev/null + +echo "artifacts are in $OUT" diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java index 9bfeb6308d13..fe397d9c2662 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetwork.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; @@ -86,6 +87,7 @@ public final class HotspotNetwork implements Parcelable { @Nullable @SecurityType private final ArraySet<Integer> mHotspotSecurityTypes; + private final Bundle mExtras; /** * Builder class for {@link HotspotNetwork}. @@ -102,8 +104,8 @@ public final class HotspotNetwork implements Parcelable { private String mHotspotBssid; @Nullable @SecurityType - private final ArraySet<Integer> mHotspotSecurityTypes = - new ArraySet<>(); + private final ArraySet<Integer> mHotspotSecurityTypes = new ArraySet<>(); + private Bundle mExtras = Bundle.EMPTY; /** * Set the remote device ID. @@ -190,6 +192,17 @@ public final class HotspotNetwork implements Parcelable { } /** + * Sets the extras bundle + * + * @return Returns the Builder object. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** * Builds the {@link HotspotNetwork} object. * * @return Returns the built {@link HotspotNetwork} object. @@ -203,7 +216,8 @@ public final class HotspotNetwork implements Parcelable { mNetworkName, mHotspotSsid, mHotspotBssid, - mHotspotSecurityTypes); + mHotspotSecurityTypes, + mExtras); } } @@ -231,7 +245,8 @@ public final class HotspotNetwork implements Parcelable { @NonNull String networkName, @Nullable String hotspotSsid, @Nullable String hotspotBssid, - @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes) { + @Nullable @SecurityType ArraySet<Integer> hotspotSecurityTypes, + @NonNull Bundle extras) { validate(deviceId, networkType, networkName, @@ -243,6 +258,7 @@ public final class HotspotNetwork implements Parcelable { mHotspotSsid = hotspotSsid; mHotspotBssid = hotspotBssid; mHotspotSecurityTypes = new ArraySet<>(hotspotSecurityTypes); + mExtras = extras; } /** @@ -315,6 +331,16 @@ public final class HotspotNetwork implements Parcelable { return mHotspotSecurityTypes; } + /** + * Gets the extras Bundle. + * + * @return Returns a Bundle object. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof HotspotNetwork)) return false; @@ -348,6 +374,7 @@ public final class HotspotNetwork implements Parcelable { dest.writeString(mHotspotSsid); dest.writeString(mHotspotBssid); dest.writeArraySet(mHotspotSecurityTypes); + dest.writeBundle(mExtras); } /** @@ -359,7 +386,7 @@ public final class HotspotNetwork implements Parcelable { public static HotspotNetwork readFromParcel(@NonNull Parcel in) { return new HotspotNetwork(in.readLong(), NetworkProviderInfo.readFromParcel(in), in.readInt(), in.readString(), in.readString(), in.readString(), - (ArraySet<Integer>) in.readArraySet(null)); + (ArraySet<Integer>) in.readArraySet(null), in.readBundle()); } @NonNull @@ -385,6 +412,7 @@ public final class HotspotNetwork implements Parcelable { .append(", hotspotSsid=").append(mHotspotSsid) .append(", hotspotBssid=").append(mHotspotBssid) .append(", hotspotSecurityTypes=").append(mHotspotSecurityTypes.toString()) + .append(", extras=").append(mExtras.toString()) .append("]").toString(); } } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java index 69767f302bd8..72acf2ccab0c 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkConnectionStatus.java @@ -117,7 +117,7 @@ public final class HotspotNetworkConnectionStatus implements Parcelable { @ConnectionStatus private int mStatus; private HotspotNetwork mHotspotNetwork; - private Bundle mExtras; + private Bundle mExtras = Bundle.EMPTY; /** * Sets the status of the connection @@ -179,7 +179,7 @@ public final class HotspotNetworkConnectionStatus implements Parcelable { } private HotspotNetworkConnectionStatus(@ConnectionStatus int status, - HotspotNetwork hotspotNetwork, Bundle extras) { + HotspotNetwork hotspotNetwork, @NonNull Bundle extras) { validate(status); mStatus = status; mHotspotNetwork = hotspotNetwork; diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java index 64412bc55fb2..c390e42f348e 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetwork.java @@ -22,6 +22,7 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; @@ -73,6 +74,7 @@ public final class KnownNetwork implements Parcelable { @SecurityType private final ArraySet<Integer> mSecurityTypes; private final NetworkProviderInfo mNetworkProviderInfo; + private final Bundle mExtras; /** * Builder class for {@link KnownNetwork}. @@ -84,6 +86,7 @@ public final class KnownNetwork implements Parcelable { @SecurityType private final ArraySet<Integer> mSecurityTypes = new ArraySet<>(); private NetworkProviderInfo mNetworkProviderInfo; + private Bundle mExtras = Bundle.EMPTY; /** * Sets the indicated source of the known network. @@ -135,6 +138,17 @@ public final class KnownNetwork implements Parcelable { } /** + * Sets the extras bundle + * + * @return Returns the Builder object. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** * Builds the {@link KnownNetwork} object. * * @return Returns the built {@link KnownNetwork} object. @@ -145,7 +159,8 @@ public final class KnownNetwork implements Parcelable { mNetworkSource, mSsid, mSecurityTypes, - mNetworkProviderInfo); + mNetworkProviderInfo, + mExtras); } } @@ -173,12 +188,14 @@ public final class KnownNetwork implements Parcelable { @NetworkSource int networkSource, @NonNull String ssid, @NonNull @SecurityType ArraySet<Integer> securityTypes, - @Nullable NetworkProviderInfo networkProviderInfo) { + @Nullable NetworkProviderInfo networkProviderInfo, + @NonNull Bundle extras) { validate(networkSource, ssid, securityTypes, networkProviderInfo); mNetworkSource = networkSource; mSsid = ssid; mSecurityTypes = new ArraySet<>(securityTypes); mNetworkProviderInfo = networkProviderInfo; + mExtras = extras; } /** @@ -223,6 +240,16 @@ public final class KnownNetwork implements Parcelable { return mNetworkProviderInfo; } + /** + * Gets the extras Bundle. + * + * @return Returns a Bundle object. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof KnownNetwork)) return false; @@ -249,6 +276,7 @@ public final class KnownNetwork implements Parcelable { dest.writeString(mSsid); dest.writeArraySet(mSecurityTypes); mNetworkProviderInfo.writeToParcel(dest, flags); + dest.writeBundle(mExtras); } /** @@ -260,7 +288,7 @@ public final class KnownNetwork implements Parcelable { public static KnownNetwork readFromParcel(@NonNull Parcel in) { return new KnownNetwork(in.readInt(), in.readString(), (ArraySet<Integer>) in.readArraySet(null), - NetworkProviderInfo.readFromParcel(in)); + NetworkProviderInfo.readFromParcel(in), in.readBundle()); } @NonNull @@ -283,6 +311,7 @@ public final class KnownNetwork implements Parcelable { .append(", ssid=").append(mSsid) .append(", securityTypes=").append(mSecurityTypes.toString()) .append(", networkProviderInfo=").append(mNetworkProviderInfo.toString()) + .append(", extras=").append(mExtras.toString()) .append("]").toString(); } } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java index 6bd0a5ecd4c8..b30dc3f6b530 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/KnownNetworkConnectionStatus.java @@ -72,7 +72,7 @@ public final class KnownNetworkConnectionStatus implements Parcelable { public static final class Builder { @ConnectionStatus private int mStatus; private KnownNetwork mKnownNetwork; - private Bundle mExtras; + private Bundle mExtras = Bundle.EMPTY; public Builder() {} @@ -128,7 +128,7 @@ public final class KnownNetworkConnectionStatus implements Parcelable { } private KnownNetworkConnectionStatus(@ConnectionStatus int status, KnownNetwork knownNetwork, - Bundle extras) { + @NonNull Bundle extras) { validate(status); mStatus = status; mKnownNetwork = knownNetwork; diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java index ed4d699ad4de..25fbabce71ae 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfo.java @@ -21,6 +21,7 @@ import android.annotation.IntRange; import android.annotation.NonNull; import android.annotation.SystemApi; import android.net.wifi.sharedconnectivity.service.SharedConnectivityService; +import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; @@ -89,6 +90,7 @@ public final class NetworkProviderInfo implements Parcelable { private final String mModelName; private final int mBatteryPercentage; private final int mConnectionStrength; + private final Bundle mExtras; /** * Builder class for {@link NetworkProviderInfo}. @@ -99,6 +101,7 @@ public final class NetworkProviderInfo implements Parcelable { private String mModelName; private int mBatteryPercentage; private int mConnectionStrength; + private Bundle mExtras = Bundle.EMPTY; public Builder(@NonNull String deviceName, @NonNull String modelName) { Objects.requireNonNull(deviceName); @@ -170,6 +173,17 @@ public final class NetworkProviderInfo implements Parcelable { } /** + * Sets the extras bundle + * + * @return Returns the Builder object. + */ + @NonNull + public Builder setExtras(@NonNull Bundle extras) { + mExtras = extras; + return this; + } + + /** * Builds the {@link NetworkProviderInfo} object. * * @return Returns the built {@link NetworkProviderInfo} object. @@ -177,7 +191,7 @@ public final class NetworkProviderInfo implements Parcelable { @NonNull public NetworkProviderInfo build() { return new NetworkProviderInfo(mDeviceType, mDeviceName, mModelName, mBatteryPercentage, - mConnectionStrength); + mConnectionStrength, mExtras); } } @@ -197,13 +211,15 @@ public final class NetworkProviderInfo implements Parcelable { } private NetworkProviderInfo(@DeviceType int deviceType, @NonNull String deviceName, - @NonNull String modelName, int batteryPercentage, int connectionStrength) { + @NonNull String modelName, int batteryPercentage, int connectionStrength, + @NonNull Bundle extras) { validate(deviceType, deviceName, modelName, batteryPercentage, connectionStrength); mDeviceType = deviceType; mDeviceName = deviceName; mModelName = modelName; mBatteryPercentage = batteryPercentage; mConnectionStrength = connectionStrength; + mExtras = extras; } /** @@ -256,6 +272,16 @@ public final class NetworkProviderInfo implements Parcelable { return mConnectionStrength; } + /** + * Gets the extras Bundle. + * + * @return Returns a Bundle object. + */ + @NonNull + public Bundle getExtras() { + return mExtras; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof NetworkProviderInfo)) return false; @@ -280,6 +306,7 @@ public final class NetworkProviderInfo implements Parcelable { dest.writeString(mModelName); dest.writeInt(mBatteryPercentage); dest.writeInt(mConnectionStrength); + dest.writeBundle(mExtras); } @Override @@ -295,7 +322,7 @@ public final class NetworkProviderInfo implements Parcelable { @NonNull public static NetworkProviderInfo readFromParcel(@NonNull Parcel in) { return new NetworkProviderInfo(in.readInt(), in.readString(), in.readString(), in.readInt(), - in.readInt()); + in.readInt(), in.readBundle()); } @NonNull @@ -319,6 +346,7 @@ public final class NetworkProviderInfo implements Parcelable { .append(", modelName=").append(mModelName) .append(", batteryPercentage=").append(mBatteryPercentage) .append(", connectionStrength=").append(mConnectionStrength) + .append(", extras=").append(mExtras.toString()) .append("]").toString(); } } diff --git a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java index 63e471b25ffb..30bb98962b3c 100644 --- a/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java +++ b/wifi/java/src/android/net/wifi/sharedconnectivity/app/SharedConnectivitySettingsState.java @@ -51,7 +51,7 @@ public final class SharedConnectivitySettingsState implements Parcelable { private boolean mInstantTetherEnabled; private Intent mInstantTetherSettingsIntent; private final Context mContext; - private Bundle mExtras; + private Bundle mExtras = Bundle.EMPTY; public Builder(@NonNull Context context) { mContext = context; @@ -112,8 +112,7 @@ public final class SharedConnectivitySettingsState implements Parcelable { } private SharedConnectivitySettingsState(boolean instantTetherEnabled, - PendingIntent pendingIntent, Bundle extras) { - + PendingIntent pendingIntent, @NonNull Bundle extras) { mInstantTetherEnabled = instantTetherEnabled; mInstantTetherSettingsPendingIntent = pendingIntent; mExtras = extras; diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java index 8302094b1c34..0827ffaea482 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/HotspotNetworkTest.java @@ -28,6 +28,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertThrows; +import android.os.Bundle; import android.os.Parcel; import android.util.ArraySet; @@ -55,6 +56,8 @@ public class HotspotNetworkTest { private static final String HOTSPOT_SSID = "TEST_SSID"; private static final String HOTSPOT_BSSID = "TEST _BSSID"; private static final int[] HOTSPOT_SECURITY_TYPES = {SECURITY_TYPE_WEP, SECURITY_TYPE_EAP}; + private static final String BUNDLE_KEY = "INT-KEY"; + private static final int BUNDLE_VALUE = 1; private static final long DEVICE_ID_1 = 111L; private static final NetworkProviderInfo NETWORK_PROVIDER_INFO1 = @@ -138,6 +141,7 @@ public class HotspotNetworkTest { assertThat(network.getHotspotSsid()).isEqualTo(HOTSPOT_SSID); assertThat(network.getHotspotBssid()).isEqualTo(HOTSPOT_BSSID); assertThat(network.getHotspotSecurityTypes()).containsExactlyElementsIn(securityTypes); + assertThat(network.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE); } @Test @@ -161,11 +165,18 @@ public class HotspotNetworkTest { .setHostNetworkType(NETWORK_TYPE) .setNetworkName(NETWORK_NAME) .setHotspotSsid(HOTSPOT_SSID) - .setHotspotBssid(HOTSPOT_BSSID); + .setHotspotBssid(HOTSPOT_BSSID) + .setExtras(buildBundle()); Arrays.stream(HOTSPOT_SECURITY_TYPES).forEach(builder::addHotspotSecurityType); if (withNetworkProviderInfo) { builder.setNetworkProviderInfo(NETWORK_PROVIDER_INFO); } return builder; } + + private Bundle buildBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE); + return bundle; + } } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java index 1ecba7644cf9..81d7b44382e0 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/KnownNetworkTest.java @@ -25,6 +25,7 @@ import static android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.DEVICE import static com.google.common.truth.Truth.assertThat; +import android.os.Bundle; import android.os.Parcel; import android.util.ArraySet; @@ -47,6 +48,9 @@ public class KnownNetworkTest { new NetworkProviderInfo.Builder("TEST_NAME", "TEST_MODEL") .setDeviceType(DEVICE_TYPE_TABLET).setConnectionStrength(2) .setBatteryPercentage(50).build(); + private static final String BUNDLE_KEY = "INT-KEY"; + private static final int BUNDLE_VALUE = 1; + private static final int NETWORK_SOURCE_1 = NETWORK_SOURCE_CLOUD_SELF; private static final String SSID_1 = "TEST_SSID1"; private static final int[] SECURITY_TYPES_1 = {SECURITY_TYPE_PSK}; @@ -113,6 +117,7 @@ public class KnownNetworkTest { assertThat(network.getSsid()).isEqualTo(SSID); assertThat(network.getSecurityTypes()).containsExactlyElementsIn(securityTypes); assertThat(network.getNetworkProviderInfo()).isEqualTo(NETWORK_PROVIDER_INFO); + assertThat(network.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE); } @Test @@ -125,8 +130,15 @@ public class KnownNetworkTest { private KnownNetwork.Builder buildKnownNetworkBuilder() { KnownNetwork.Builder builder = new KnownNetwork.Builder().setNetworkSource(NETWORK_SOURCE) - .setSsid(SSID).setNetworkProviderInfo(NETWORK_PROVIDER_INFO); + .setSsid(SSID).setNetworkProviderInfo(NETWORK_PROVIDER_INFO) + .setExtras(buildBundle()); Arrays.stream(SECURITY_TYPES).forEach(builder::addSecurityType); return builder; } + + private Bundle buildBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE); + return bundle; + } } diff --git a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java index 8f35d8d94a8b..4aa9ca684ad0 100644 --- a/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java +++ b/wifi/tests/src/android/net/wifi/sharedconnectivity/app/NetworkProviderInfoTest.java @@ -21,6 +21,7 @@ import static android.net.wifi.sharedconnectivity.app.NetworkProviderInfo.DEVICE import static com.google.common.truth.Truth.assertThat; +import android.os.Bundle; import android.os.Parcel; import androidx.test.filters.SmallTest; @@ -38,6 +39,8 @@ public class NetworkProviderInfoTest { private static final String DEVICE_MODEL = "TEST_MODEL"; private static final int BATTERY_PERCENTAGE = 50; private static final int CONNECTION_STRENGTH = 2; + private static final String BUNDLE_KEY = "INT-KEY"; + private static final int BUNDLE_VALUE = 1; private static final int DEVICE_TYPE_1 = DEVICE_TYPE_LAPTOP; private static final String DEVICE_NAME_1 = "TEST_NAME1"; @@ -105,6 +108,7 @@ public class NetworkProviderInfoTest { assertThat(info.getModelName()).isEqualTo(DEVICE_MODEL); assertThat(info.getBatteryPercentage()).isEqualTo(BATTERY_PERCENTAGE); assertThat(info.getConnectionStrength()).isEqualTo(CONNECTION_STRENGTH); + assertThat(info.getExtras().getInt(BUNDLE_KEY)).isEqualTo(BUNDLE_VALUE); } @Test @@ -118,6 +122,13 @@ public class NetworkProviderInfoTest { private NetworkProviderInfo.Builder buildNetworkProviderInfoBuilder() { return new NetworkProviderInfo.Builder(DEVICE_NAME, DEVICE_MODEL).setDeviceType(DEVICE_TYPE) .setBatteryPercentage(BATTERY_PERCENTAGE) - .setConnectionStrength(CONNECTION_STRENGTH); + .setConnectionStrength(CONNECTION_STRENGTH) + .setExtras(buildBundle()); + } + + private Bundle buildBundle() { + Bundle bundle = new Bundle(); + bundle.putInt(BUNDLE_KEY, BUNDLE_VALUE); + return bundle; } } |