diff options
273 files changed, 8029 insertions, 2803 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index 904109b569b8..22f736e367c3 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -27,6 +27,7 @@ aconfig_srcjars = [ ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}", ":android.security.flags-aconfig-java{.generated_srcjars}", ":android.service.chooser.flags-aconfig-java{.generated_srcjars}", + ":android.service.dreams.flags-aconfig-java{.generated_srcjars}", ":android.service.notification.flags-aconfig-java{.generated_srcjars}", ":android.view.flags-aconfig-java{.generated_srcjars}", ":android.view.accessibility.flags-aconfig-java{.generated_srcjars}", @@ -231,7 +232,6 @@ java_aconfig_library { name: "android.security.flags-aconfig-java-host", aconfig_declarations: "android.security.flags-aconfig", host_supported: true, - mode: "test", defaults: ["framework-minus-apex-aconfig-java-defaults"], } @@ -742,6 +742,19 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Dreams +aconfig_declarations { + name: "android.service.dreams.flags-aconfig", + package: "android.service.dreams", + srcs: ["core/java/android/service/dreams/flags.aconfig"], +} + +java_aconfig_library { + name: "android.service.dreams.flags-aconfig-java", + aconfig_declarations: "android.service.dreams.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Notifications aconfig_declarations { name: "android.service.notification.flags-aconfig", diff --git a/Android.bp b/Android.bp index fa7c97d3d21a..676a0f51d3f6 100644 --- a/Android.bp +++ b/Android.bp @@ -106,7 +106,7 @@ filegroup { ":android.hardware.radio.voice-V3-java-source", ":android.hardware.security.keymint-V3-java-source", ":android.hardware.security.secureclock-V1-java-source", - ":android.hardware.thermal-V1-java-source", + ":android.hardware.thermal-V2-java-source", ":android.hardware.tv.tuner-V2-java-source", ":android.security.apc-java-source", ":android.security.authorization-java-source", 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 e06006f25d3f..f40508302ee3 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 @@ -1769,7 +1769,8 @@ public final class ConnectivityController extends RestrictingController implemen @VisibleForTesting class CcConfig { - private boolean mFlexIsEnabled = FlexibilityController.FcConfig.DEFAULT_FLEXIBILITY_ENABLED; + private boolean mFlexIsEnabled = + FlexibilityController.FcConfig.DEFAULT_APPLIED_CONSTRAINTS != 0; private boolean mShouldReprocessNetworkCapabilities = false; /** 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 13a474ccf451..0e67b9ac944f 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 @@ -24,7 +24,6 @@ import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY; -import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; import android.annotation.ElapsedRealtimeLong; @@ -74,17 +73,10 @@ public final class FlexibilityController extends StateController { private static final int JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = CONSTRAINT_CONNECTIVITY; /** List of all flexible constraints. */ - private static final int FLEXIBLE_CONSTRAINTS = + @VisibleForTesting + static final int FLEXIBLE_CONSTRAINTS = JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS | SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; - private static final int NUM_JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS = - Integer.bitCount(JOB_SPECIFIC_FLEXIBLE_CONSTRAINTS); - - static final int NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS = - Integer.bitCount(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS); - - static final int NUM_FLEXIBLE_CONSTRAINTS = Integer.bitCount(FLEXIBLE_CONSTRAINTS); - private static final long NO_LIFECYCLE_END = Long.MAX_VALUE; /** @@ -100,9 +92,15 @@ public final class FlexibilityController extends StateController { private long mUnseenConstraintGracePeriodMs = FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; - @VisibleForTesting + /** Set of constraints supported on this device for flex scheduling. */ + private final int mSupportedFlexConstraints; + @GuardedBy("mLock") - boolean mFlexibilityEnabled = FcConfig.DEFAULT_FLEXIBILITY_ENABLED; + private boolean mFlexibilityEnabled; + + /** Set of constraints that will be used in the flex policy. */ + @GuardedBy("mLock") + private int mAppliedConstraints = FcConfig.DEFAULT_APPLIED_CONSTRAINTS; private long mMinTimeBetweenFlexibilityAlarmsMs = FcConfig.DEFAULT_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS; @@ -118,9 +116,6 @@ public final class FlexibilityController extends StateController { */ private int[] mPercentToDropConstraints; - @VisibleForTesting - boolean mDeviceSupportsFlexConstraints; - /** * Keeps track of what flexible constraints are satisfied at the moment. * Is updated by the other controllers. @@ -178,7 +173,7 @@ public final class FlexibilityController extends StateController { if (!js.hasFlexibilityConstraint()) { continue; } - mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); + mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); } } @@ -186,15 +181,23 @@ public final class FlexibilityController extends StateController { }; private static final int MSG_UPDATE_JOBS = 0; + private static final int MSG_UPDATE_JOB = 1; public FlexibilityController( JobSchedulerService service, PrefetchController prefetchController) { super(service); mHandler = new FcHandler(AppSchedulingModuleThread.get().getLooper()); - mDeviceSupportsFlexConstraints = !mContext.getPackageManager().hasSystemFeature( - PackageManager.FEATURE_AUTOMOTIVE); - mFlexibilityEnabled &= mDeviceSupportsFlexConstraints; - mFlexibilityTracker = new FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS); + if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) + || mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_EMBEDDED)) { + // Embedded devices have no user-installable apps. Assume all jobs are critical + // and can't be flexed. + mSupportedFlexConstraints = 0; + } else { + // TODO(236261941): handle devices without a battery + mSupportedFlexConstraints = FLEXIBLE_CONSTRAINTS; + } + mFlexibilityEnabled = (mAppliedConstraints & mSupportedFlexConstraints) != 0; + mFlexibilityTracker = new FlexibilityTracker(Integer.bitCount(mSupportedFlexConstraints)); mFcConfig = new FcConfig(); mFlexibilityAlarmQueue = new FlexibilityAlarmQueue( mContext, AppSchedulingModuleThread.get().getLooper()); @@ -218,10 +221,12 @@ public final class FlexibilityController extends StateController { public void maybeStartTrackingJobLocked(JobStatus js, JobStatus lastJob) { if (js.hasFlexibilityConstraint()) { final long nowElapsed = sElapsedRealtimeClock.millis(); - if (!mDeviceSupportsFlexConstraints) { + if (mSupportedFlexConstraints == 0) { js.setFlexibilityConstraintSatisfied(nowElapsed, true); return; } + js.setNumAppliedFlexibleConstraints( + Integer.bitCount(getRelevantAppliedConstraintsLocked(js))); js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); mFlexibilityTracker.add(js); js.setTrackingController(JobStatus.TRACKING_FLEXIBILITY); @@ -266,6 +271,14 @@ public final class FlexibilityController extends StateController { || mService.isCurrentlyRunningLocked(js); } + @VisibleForTesting + @GuardedBy("mLock") + int getRelevantAppliedConstraintsLocked(@NonNull JobStatus js) { + final int relevantConstraints = SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0); + return mAppliedConstraints & relevantConstraints; + } + /** * Returns whether there are enough constraints satisfied to allow running the job from flex's * perspective. This takes into account unseen constraint combinations and expectations around @@ -274,7 +287,7 @@ public final class FlexibilityController extends StateController { @VisibleForTesting @GuardedBy("mLock") boolean hasEnoughSatisfiedConstraintsLocked(@NonNull JobStatus js) { - final int satisfiedConstraints = mSatisfiedFlexibleConstraints + final int satisfiedConstraints = mSatisfiedFlexibleConstraints & mAppliedConstraints & (SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS | (js.areTransportAffinitiesSatisfied() ? CONSTRAINT_CONNECTIVITY : 0)); final int numSatisfied = Integer.bitCount(satisfiedConstraints); @@ -296,8 +309,7 @@ public final class FlexibilityController extends StateController { // count have not been seen recently enough, then assume they won't be seen anytime soon, // so don't force the job to wait longer. If any combinations with a higher count have been // seen recently, then the job can potentially wait for those combinations. - final int irrelevantConstraints = ~(SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS - | (js.canApplyTransportAffinities() ? CONSTRAINT_CONNECTIVITY : 0)); + final int irrelevantConstraints = ~getRelevantAppliedConstraintsLocked(js); for (int i = mLastSeenConstraintTimesElapsed.size() - 1; i >= 0; --i) { final int constraints = mLastSeenConstraintTimesElapsed.keyAt(i); if ((constraints & irrelevantConstraints) != 0) { @@ -515,9 +527,9 @@ public final class FlexibilityController extends StateController { for (int j = 0; j < mFlexibilityTracker.size(); j++) { final ArraySet<JobStatus> jobs = mFlexibilityTracker .getJobsByNumRequiredConstraints(j); - for (int i = 0; i < jobs.size(); i++) { + for (int i = jobs.size() - 1; i >= 0; --i) { JobStatus js = jobs.valueAt(i); - mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); + mFlexibilityTracker.updateFlexibleConstraints(js, nowElapsed); mFlexibilityAlarmQueue.scheduleDropNumConstraintsAlarm(js, nowElapsed); if (js.setFlexibilityConstraintSatisfied( nowElapsed, isFlexibilitySatisfiedLocked(js))) { @@ -579,18 +591,46 @@ public final class FlexibilityController extends StateController { mTrackedJobs.get(js.getNumRequiredFlexibleConstraints()).remove(js); } - public void resetJobNumDroppedConstraints(JobStatus js, long nowElapsed) { + /** + * Updates applied and dropped constraints for the job. + */ + public void updateFlexibleConstraints(JobStatus js, long nowElapsed) { + final int prevNumRequired = js.getNumRequiredFlexibleConstraints(); + + final int numAppliedConstraints = + Integer.bitCount(getRelevantAppliedConstraintsLocked(js)); + js.setNumAppliedFlexibleConstraints(numAppliedConstraints); + final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); int toDrop = 0; - final int jsMaxFlexibleConstraints = NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS - + (js.canApplyTransportAffinities() ? 1 : 0); + for (int i = 0; i < numAppliedConstraints; i++) { + if (curPercent >= mPercentToDropConstraints[i]) { + toDrop++; + } + } + js.setNumDroppedFlexibleConstraints(toDrop); + + if (prevNumRequired == js.getNumRequiredFlexibleConstraints()) { + return; + } + mTrackedJobs.get(prevNumRequired).remove(js); + add(js); + } + + /** + * Calculates the number of constraints that should be dropped for the job, based on how + * far along the job is into its lifecycle. + */ + public void calculateNumDroppedConstraints(JobStatus js, long nowElapsed) { + final int curPercent = getCurPercentOfLifecycleLocked(js, nowElapsed); + int toDrop = 0; + final int jsMaxFlexibleConstraints = js.getNumAppliedFlexibleConstraints(); for (int i = 0; i < jsMaxFlexibleConstraints; i++) { if (curPercent >= mPercentToDropConstraints[i]) { toDrop++; } } - adjustJobsRequiredConstraints( - js, js.getNumDroppedFlexibleConstraints() - toDrop, nowElapsed); + setNumDroppedFlexibleConstraints(js, toDrop); } /** Returns all tracked jobs. */ @@ -599,17 +639,14 @@ public final class FlexibilityController extends StateController { } /** - * Adjusts number of required flexible constraints and sorts it into the tracker. - * Returns false if the job status's number of flexible constraints is now 0. + * Updates the number of dropped flexible constraints and sorts it into the tracker. */ - public boolean adjustJobsRequiredConstraints(JobStatus js, int adjustBy, long nowElapsed) { - if (adjustBy != 0) { + public void setNumDroppedFlexibleConstraints(JobStatus js, int numDropped) { + if (numDropped != js.getNumDroppedFlexibleConstraints()) { remove(js); - js.adjustNumRequiredFlexibleConstraints(adjustBy); - js.setFlexibilityConstraintSatisfied(nowElapsed, isFlexibilitySatisfiedLocked(js)); + js.setNumDroppedFlexibleConstraints(numDropped); add(js); } - return js.getNumRequiredFlexibleConstraints() > 0; } public int size() { @@ -658,8 +695,10 @@ public final class FlexibilityController extends StateController { if (DEBUG) { Slog.d(TAG, "scheduleDropNumConstraintsAlarm: " + js.getSourcePackageName() + " " + js.getSourceUserId() + + " numApplied: " + js.getNumAppliedFlexibleConstraints() + " numRequired: " + js.getNumRequiredFlexibleConstraints() - + " numSatisfied: " + Integer.bitCount(mSatisfiedFlexibleConstraints) + + " numSatisfied: " + Integer.bitCount( + mSatisfiedFlexibleConstraints & getRelevantAppliedConstraintsLocked(js)) + " curTime: " + nowElapsed + " earliest: " + earliest + " latest: " + latest @@ -669,8 +708,9 @@ public final class FlexibilityController extends StateController { if (DEBUG) { Slog.d(TAG, "deadline proximity met: " + js); } - mFlexibilityTracker.adjustJobsRequiredConstraints(js, - -js.getNumRequiredFlexibleConstraints(), nowElapsed); + mFlexibilityTracker.setNumDroppedFlexibleConstraints(js, + js.getNumAppliedFlexibleConstraints()); + mHandler.obtainMessage(MSG_UPDATE_JOB, js).sendToTarget(); return; } if (nextTimeElapsed == NO_LIFECYCLE_END) { @@ -696,12 +736,15 @@ public final class FlexibilityController extends StateController { final long nowElapsed = sElapsedRealtimeClock.millis(); for (int i = 0; i < expired.size(); i++) { JobStatus js = expired.valueAt(i); - boolean wasFlexibilitySatisfied = js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE); - - if (mFlexibilityTracker.adjustJobsRequiredConstraints(js, -1, nowElapsed)) { + if (DEBUG) { + Slog.d(TAG, "Alarm fired for " + js.toShortString()); + } + mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); + if (js.getNumRequiredFlexibleConstraints() > 0) { scheduleDropNumConstraintsAlarm(js, nowElapsed); } - if (wasFlexibilitySatisfied != js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)) { + if (js.setFlexibilityConstraintSatisfied(nowElapsed, + isFlexibilitySatisfiedLocked(js))) { changedJobs.add(js); } } @@ -725,7 +768,9 @@ public final class FlexibilityController extends StateController { final long nowElapsed = sElapsedRealtimeClock.millis(); final ArraySet<JobStatus> changedJobs = new ArraySet<>(); - for (int o = 0; o <= NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; ++o) { + final int numAppliedSystemWideConstraints = Integer.bitCount( + mAppliedConstraints & SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS); + for (int o = 0; o <= numAppliedSystemWideConstraints; ++o) { final ArraySet<JobStatus> jobsByNumConstraints = mFlexibilityTracker .getJobsByNumRequiredConstraints(o); @@ -744,6 +789,23 @@ public final class FlexibilityController extends StateController { } } break; + + case MSG_UPDATE_JOB: + synchronized (mLock) { + final JobStatus js = (JobStatus) msg.obj; + if (DEBUG) { + Slog.d("blah", "Checking on " + js.toShortString()); + } + final long nowElapsed = sElapsedRealtimeClock.millis(); + if (js.setFlexibilityConstraintSatisfied( + nowElapsed, isFlexibilitySatisfiedLocked(js))) { + // TODO(141645789): add method that will take a single job + ArraySet<JobStatus> changedJob = new ArraySet<>(); + changedJob.add(js); + mStateChangedListener.onControllerStateChanged(changedJob); + } + } + break; } } } @@ -754,7 +816,8 @@ public final class FlexibilityController extends StateController { /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ private static final String FC_CONFIG_PREFIX = "fc_"; - static final String KEY_FLEXIBILITY_ENABLED = FC_CONFIG_PREFIX + "enable_flexibility"; + @VisibleForTesting + static final String KEY_APPLIED_CONSTRAINTS = FC_CONFIG_PREFIX + "applied_constraints"; static final String KEY_DEADLINE_PROXIMITY_LIMIT = FC_CONFIG_PREFIX + "flexibility_deadline_proximity_limit_ms"; static final String KEY_FALLBACK_FLEXIBILITY_DEADLINE = @@ -770,7 +833,7 @@ public final class FlexibilityController extends StateController { static final String KEY_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = FC_CONFIG_PREFIX + "unseen_constraint_grace_period_ms"; - static final boolean DEFAULT_FLEXIBILITY_ENABLED = false; + static final int DEFAULT_APPLIED_CONSTRAINTS = 0; @VisibleForTesting static final long DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS = 15 * MINUTE_IN_MILLIS; @VisibleForTesting @@ -783,11 +846,8 @@ public final class FlexibilityController extends StateController { @VisibleForTesting static final long DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS = 3 * DAY_IN_MILLIS; - /** - * If false the controller will not track new jobs - * and the flexibility constraint will always be satisfied. - */ - public boolean FLEXIBILITY_ENABLED = DEFAULT_FLEXIBILITY_ENABLED; + /** Which constraints to apply/consider in flex policy. */ + public int APPLIED_CONSTRAINTS = DEFAULT_APPLIED_CONSTRAINTS; /** How close to a jobs' deadline all flexible constraints will be dropped. */ public long DEADLINE_PROXIMITY_LIMIT_MS = DEFAULT_DEADLINE_PROXIMITY_LIMIT_MS; /** For jobs that lack a deadline, the time that will be used to drop all constraints by. */ @@ -811,16 +871,19 @@ public final class FlexibilityController extends StateController { public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @NonNull String key) { switch (key) { - case KEY_FLEXIBILITY_ENABLED: - FLEXIBILITY_ENABLED = properties.getBoolean(key, DEFAULT_FLEXIBILITY_ENABLED) - && mDeviceSupportsFlexConstraints; - if (mFlexibilityEnabled != FLEXIBILITY_ENABLED) { - mFlexibilityEnabled = FLEXIBILITY_ENABLED; + case KEY_APPLIED_CONSTRAINTS: + APPLIED_CONSTRAINTS = + properties.getInt(key, DEFAULT_APPLIED_CONSTRAINTS) + & mSupportedFlexConstraints; + if (mAppliedConstraints != APPLIED_CONSTRAINTS) { + mAppliedConstraints = APPLIED_CONSTRAINTS; mShouldReevaluateConstraints = true; - if (mFlexibilityEnabled) { + if (mAppliedConstraints != 0) { + mFlexibilityEnabled = true; mPrefetchController .registerPrefetchChangedListener(mPrefetchChangedListener); } else { + mFlexibilityEnabled = false; mPrefetchController .unRegisterPrefetchChangedListener(mPrefetchChangedListener); } @@ -893,7 +956,7 @@ public final class FlexibilityController extends StateController { private int[] parsePercentToDropString(String s) { String[] dropPercentString = s.split(","); - int[] dropPercentInt = new int[NUM_FLEXIBLE_CONSTRAINTS]; + int[] dropPercentInt = new int[Integer.bitCount(FLEXIBLE_CONSTRAINTS)]; if (dropPercentInt.length != dropPercentString.length) { return DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS; } @@ -922,7 +985,7 @@ public final class FlexibilityController extends StateController { pw.println(":"); pw.increaseIndent(); - pw.print(KEY_FLEXIBILITY_ENABLED, FLEXIBILITY_ENABLED).println(); + pw.print(KEY_APPLIED_CONSTRAINTS, APPLIED_CONSTRAINTS).println(); pw.print(KEY_DEADLINE_PROXIMITY_LIMIT, DEADLINE_PROXIMITY_LIMIT_MS).println(); pw.print(KEY_FALLBACK_FLEXIBILITY_DEADLINE, FALLBACK_FLEXIBILITY_DEADLINE_MS).println(); pw.print(KEY_MIN_TIME_BETWEEN_FLEXIBILITY_ALARMS_MS, 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 d1f575ef40c8..0d5d11e98860 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 @@ -24,7 +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; @@ -155,7 +154,7 @@ public final class JobStatus { /** * Keeps track of how many flexible constraints must be satisfied for the job to execute. */ - private final int mNumRequiredFlexibleConstraints; + private int mNumAppliedFlexibleConstraints; /** * Number of required flexible constraints that have been dropped. @@ -697,11 +696,7 @@ public final class JobStatus { && satisfiesMinWindowException && (numFailures + numSystemStops) != 1 && lacksSomeFlexibleConstraints) { - mNumRequiredFlexibleConstraints = - NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mCanApplyTransportAffinities ? 1 : 0); requiredConstraints |= CONSTRAINT_FLEXIBLE; - } else { - mNumRequiredFlexibleConstraints = 0; } this.requiredConstraints = requiredConstraints; @@ -1527,9 +1522,14 @@ public final class JobStatus { return (requiredConstraints & CONSTRAINT_FLEXIBLE) != 0; } + /** Returns the number of flexible job constraints being applied to the job. */ + public int getNumAppliedFlexibleConstraints() { + return mNumAppliedFlexibleConstraints; + } + /** Returns the number of flexible job constraints required to be satisfied to execute */ public int getNumRequiredFlexibleConstraints() { - return mNumRequiredFlexibleConstraints - mNumDroppedFlexibleConstraints; + return mNumAppliedFlexibleConstraints - mNumDroppedFlexibleConstraints; } /** @@ -2112,9 +2112,14 @@ public final class JobStatus { } /** Adjusts the number of required flexible constraints by the given number */ - public void adjustNumRequiredFlexibleConstraints(int adjustment) { - mNumDroppedFlexibleConstraints = Math.max(0, Math.min(mNumRequiredFlexibleConstraints, - mNumDroppedFlexibleConstraints - adjustment)); + public void setNumAppliedFlexibleConstraints(int count) { + mNumAppliedFlexibleConstraints = count; + } + + /** Sets the number of dropped flexible constraints to the given number */ + public void setNumDroppedFlexibleConstraints(int count) { + mNumDroppedFlexibleConstraints = Math.max(0, + Math.min(mNumAppliedFlexibleConstraints, count)); } /** diff --git a/api/coverage/tools/ExtractFlaggedApis.kt b/api/coverage/tools/ExtractFlaggedApis.kt index 9ffb70496c59..43caaecebdaf 100644 --- a/api/coverage/tools/ExtractFlaggedApis.kt +++ b/api/coverage/tools/ExtractFlaggedApis.kt @@ -23,35 +23,43 @@ import java.io.FileWriter /** Usage: extract-flagged-apis <api text file> <output .pb file> */ fun main(args: Array<String>) { var cb = ApiFile.parseApi(listOf(File(args[0]))) - val flagToApi = mutableMapOf<String, MutableList<String>>() - cb.getPackages() - .allClasses() - .filter { it.methods().size > 0 } - .forEach { - for (method in it.methods()) { - val flagValue = - method.modifiers - .findAnnotation("android.annotation.FlaggedApi") - ?.findAttribute("value") - ?.value - ?.value() - if (flagValue != null && flagValue is String) { - val methodQualifiedName = "${it.qualifiedName()}.${method.name()}" - if (flagToApi.containsKey(flagValue)) { - flagToApi.get(flagValue)?.add(methodQualifiedName) - } else { - flagToApi.put(flagValue, mutableListOf(methodQualifiedName)) + var builder = FlagApiMap.newBuilder() + for (pkg in cb.getPackages().packages) { + var packageName = pkg.qualifiedName() + pkg.allClasses() + .filter { it.methods().size > 0 } + .forEach { + for (method in it.methods()) { + val flagValue = + method.modifiers + .findAnnotation("android.annotation.FlaggedApi") + ?.findAttribute("value") + ?.value + ?.value() + if (flagValue != null && flagValue is String) { + var api = + JavaMethod.newBuilder() + .setPackageName(packageName) + .setClassName(it.fullName()) + .setMethodName(method.name()) + for (param in method.parameters()) { + api.addParameterTypes(param.type().toTypeString()) + } + if (builder.containsFlagToApi(flagValue)) { + var updatedApis = + builder + .getFlagToApiOrThrow(flagValue) + .toBuilder() + .addJavaMethods(api) + .build() + builder.putFlagToApi(flagValue, updatedApis) + } else { + var apis = FlaggedApis.newBuilder().addJavaMethods(api).build() + builder.putFlagToApi(flagValue, apis) + } } } } - } - var builder = FlagApiMap.newBuilder() - for (flag in flagToApi.keys) { - var flaggedApis = FlaggedApis.newBuilder() - for (method in flagToApi.get(flag).orEmpty()) { - flaggedApis.addFlaggedApi(FlaggedApi.newBuilder().setQualifiedName(method)) - } - builder.putFlagToApi(flag, flaggedApis.build()) } val flagApiMap = builder.build() FileWriter(args[1]).use { it.write(flagApiMap.toString()) } diff --git a/api/coverage/tools/extract_flagged_apis.proto b/api/coverage/tools/extract_flagged_apis.proto index a858108a27b2..031d621b178f 100644 --- a/api/coverage/tools/extract_flagged_apis.proto +++ b/api/coverage/tools/extract_flagged_apis.proto @@ -25,10 +25,13 @@ message FlagApiMap { } message FlaggedApis { - repeated FlaggedApi flagged_api = 1; + repeated JavaMethod java_methods = 1; } -message FlaggedApi { - string qualified_name = 1; +message JavaMethod { + string package_name = 1; + string class_name = 2; + string method_name = 3; + repeated string parameter_types = 4; } diff --git a/core/api/current.txt b/core/api/current.txt index a761674f5f0b..8e5c4c417af2 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -1216,6 +1216,7 @@ package android { field public static final int opticalInsetLeft = 16844168; // 0x1010588 field public static final int opticalInsetRight = 16844170; // 0x101058a field public static final int opticalInsetTop = 16844169; // 0x1010589 + field @FlaggedApi("android.content.pm.sdk_lib_independence") public static final int optional; field public static final int order = 16843242; // 0x10101ea field public static final int orderInCategory = 16843231; // 0x10101df field public static final int ordering = 16843490; // 0x10102e2 @@ -18812,6 +18813,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<int[]> CONTROL_AWB_AVAILABLE_MODES; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Boolean> CONTROL_AWB_LOCK_AVAILABLE; + field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<android.util.Range<java.lang.Float>> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AE; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AF; field @NonNull public static final android.hardware.camera2.CameraCharacteristics.Key<java.lang.Integer> CONTROL_MAX_REGIONS_AWB; @@ -19100,6 +19102,7 @@ package android.hardware.camera2 { field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH = 2; // 0x2 field public static final int CONTROL_AE_MODE_ON_AUTO_FLASH_REDEYE = 4; // 0x4 field public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5; // 0x5 + field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY = 6; // 0x6 field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_CANCEL = 2; // 0x2 field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_IDLE = 0; // 0x0 field public static final int CONTROL_AE_PRECAPTURE_TRIGGER_START = 1; // 0x1 @@ -19165,6 +19168,8 @@ package android.hardware.camera2 { field public static final int CONTROL_EXTENDED_SCENE_MODE_BOKEH_CONTINUOUS = 2; // 0x2 field public static final int CONTROL_EXTENDED_SCENE_MODE_BOKEH_STILL_CAPTURE = 1; // 0x1 field public static final int CONTROL_EXTENDED_SCENE_MODE_DISABLED = 0; // 0x0 + field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE = 1; // 0x1 + field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") public static final int CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE = 0; // 0x0 field public static final int CONTROL_MODE_AUTO = 1; // 0x1 field public static final int CONTROL_MODE_OFF = 0; // 0x0 field public static final int CONTROL_MODE_OFF_KEEP_STATE = 3; // 0x3 @@ -19485,6 +19490,7 @@ package android.hardware.camera2 { field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EFFECT_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Boolean> CONTROL_ENABLE_ZSL; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_EXTENDED_SCENE_MODE; + field @FlaggedApi("com.android.internal.camera.flags.camera_ae_mode_low_light_boost") @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_LOW_LIGHT_BOOST_STATE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_MODE; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_POST_RAW_SENSITIVITY_BOOST; field @NonNull public static final android.hardware.camera2.CaptureResult.Key<java.lang.Integer> CONTROL_SCENE_MODE; @@ -36905,6 +36911,7 @@ package android.provider { field public static final String ACTION_REGIONAL_PREFERENCES_SETTINGS = "android.settings.REGIONAL_PREFERENCES_SETTINGS"; field public static final String ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = "android.settings.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"; field public static final String ACTION_REQUEST_MANAGE_MEDIA = "android.settings.REQUEST_MANAGE_MEDIA"; + field @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = "android.settings.REQUEST_MEDIA_ROUTING_CONTROL"; field public static final String ACTION_REQUEST_SCHEDULE_EXACT_ALARM = "android.settings.REQUEST_SCHEDULE_EXACT_ALARM"; field public static final String ACTION_REQUEST_SET_AUTOFILL_SERVICE = "android.settings.REQUEST_SET_AUTOFILL_SERVICE"; field public static final String ACTION_SEARCH_SETTINGS = "android.search.action.SEARCH_SETTINGS"; @@ -43360,6 +43367,9 @@ package android.telephony { field public static final String KEY_MMS_UA_PROF_URL_STRING = "uaProfUrl"; field public static final String KEY_MMS_USER_AGENT_STRING = "userAgent"; field public static final String KEY_MONTHLY_DATA_CYCLE_DAY_INT = "monthly_data_cycle_day_int"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY = "ntn_lte_rsrp_thresholds_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY = "ntn_lte_rsrq_thresholds_int_array"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY = "ntn_lte_rssnr_thresholds_int_array"; field public static final String KEY_ONLY_AUTO_SELECT_IN_HOME_NETWORK_BOOL = "only_auto_select_in_home_network"; field public static final String KEY_ONLY_SINGLE_DC_ALLOWED_INT_ARRAY = "only_single_dc_allowed_int_array"; field public static final String KEY_OPERATOR_SELECTION_EXPAND_BOOL = "operator_selection_expand_bool"; @@ -43374,6 +43384,7 @@ package android.telephony { field public static final String KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSSNR_INT = "opportunistic_network_exit_threshold_rssnr_int"; field public static final String KEY_OPPORTUNISTIC_NETWORK_MAX_BACKOFF_TIME_LONG = "opportunistic_network_max_backoff_time_long"; field public static final String KEY_OPPORTUNISTIC_NETWORK_PING_PONG_TIME_LONG = "opportunistic_network_ping_pong_time_long"; + field @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = "parameters_used_for_ntn_lte_signal_bar_int"; field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool"; field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool"; field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_DAILY_NOTIFICATION_COUNT_INT = "premium_capability_maximum_daily_notification_count_int"; @@ -53870,9 +53881,11 @@ package android.view { method @Deprecated public android.view.Display getDefaultDisplay(); method @NonNull public default android.view.WindowMetrics getMaximumWindowMetrics(); method public default boolean isCrossWindowBlurEnabled(); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void registerTrustedPresentationListener(@NonNull android.os.IBinder, @NonNull android.window.TrustedPresentationThresholds, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Boolean>); method public default void removeCrossWindowBlurEnabledListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); method public default void removeProposedRotationListener(@NonNull java.util.function.IntConsumer); method public void removeViewImmediate(android.view.View); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public default void unregisterTrustedPresentationListener(@NonNull java.util.function.Consumer<java.lang.Boolean>); field public static final String PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE = "android.window.PROPERTY_ACTIVITY_EMBEDDING_ALLOW_SYSTEM_OVERRIDE"; field public static final String PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED = "android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"; field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_FORCE_ROTATION"; @@ -60853,6 +60866,16 @@ package android.window { method public void markSyncReady(); } + @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable { + ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents(); + method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public void writeToParcel(@NonNull android.os.Parcel, int); + field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @NonNull public static final android.os.Parcelable.Creator<android.window.TrustedPresentationThresholds> CREATOR; + field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minAlpha; + field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) public final float minFractionRendered; + field @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") @IntRange(from=1) public final int stabilityRequirementMs; + } + } package javax.microedition.khronos.egl { diff --git a/core/api/system-current.txt b/core/api/system-current.txt index a1465df963ad..46d7d76ce67d 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -3232,6 +3232,7 @@ package android.companion.virtual { method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName); method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener); method @FlaggedApi("android.companion.virtual.flags.dynamic_policy") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int); + method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDisplayImePolicy(int, int); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean); method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void unregisterIntentInterceptor(@NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback); } @@ -4089,7 +4090,8 @@ package android.content.pm { field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1 field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff field public static final int MATCH_ANY_USER = 4194304; // 0x400000 - field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 + field @Deprecated public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000 + field @FlaggedApi("android.content.pm.fix_duplicated_flags") public static final long MATCH_CLONE_PROFILE_LONG = 17179869184L; // 0x400000000L field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000 field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000 field public static final int MATCH_INSTANT = 8388608; // 0x800000 @@ -4195,12 +4197,15 @@ package android.content.pm { public final class UserProperties implements android.os.Parcelable { method public int describeContents(); + method public int getCrossProfileContentSharingStrategy(); method public int getShowInQuietMode(); method public int getShowInSharingSurfaces(); method public boolean isCredentialShareableWithParent(); method public boolean isMediaSharedWithParent(); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.UserProperties> CREATOR; + field public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; // 0x1 + field public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; // 0x0 field public static final int SHOW_IN_QUIET_MODE_DEFAULT = 2; // 0x2 field public static final int SHOW_IN_QUIET_MODE_HIDDEN = 1; // 0x1 field public static final int SHOW_IN_QUIET_MODE_PAUSED = 0; // 0x0 @@ -4536,6 +4541,7 @@ package android.hardware.camera2.extension { @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static final class RequestProcessor.Request { ctor @FlaggedApi("com.android.internal.camera.flags.concert_mode") public RequestProcessor.Request(@NonNull java.util.List<java.lang.Integer>, @NonNull java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>>, int); + method @FlaggedApi("com.android.internal.camera.flags.concert_mode") @NonNull public java.util.List<android.util.Pair<android.hardware.camera2.CaptureRequest.Key,java.lang.Object>> getParameters(); } @FlaggedApi("com.android.internal.camera.flags.concert_mode") public static interface RequestProcessor.RequestCallback { @@ -17173,6 +17179,9 @@ package android.view { method @NonNull public default java.util.List<android.content.ComponentName> notifyScreenshotListeners(int); method public default void registerTaskFpsCallback(@IntRange(from=0) int, @NonNull java.util.concurrent.Executor, @NonNull android.window.TaskFpsCallback); method public default void unregisterTaskFpsCallback(@NonNull android.window.TaskFpsCallback); + field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1 + field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2 + field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0 } public static class WindowManager.LayoutParams extends android.view.ViewGroup.LayoutParams implements android.os.Parcelable { diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 7417137b3725..eaabda882da5 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1166,6 +1166,7 @@ package android.content.pm { public static final class UserProperties.Builder { ctor public UserProperties.Builder(); method @NonNull public android.content.pm.UserProperties build(); + method @NonNull public android.content.pm.UserProperties.Builder setCrossProfileContentSharingStrategy(int); method @NonNull public android.content.pm.UserProperties.Builder setShowInQuietMode(int); method @NonNull public android.content.pm.UserProperties.Builder setShowInSharingSurfaces(int); } @@ -3618,9 +3619,6 @@ package android.view { method public default void setShouldShowWithInsecureKeyguard(int, boolean); method public default boolean shouldShowSystemDecors(int); method @Nullable public default android.graphics.Bitmap snapshotTaskForRecents(@IntRange(from=0) int); - field public static final int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; // 0x1 - field public static final int DISPLAY_IME_POLICY_HIDE = 2; // 0x2 - field public static final int DISPLAY_IME_POLICY_LOCAL = 0; // 0x0 field public static final int LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP = 600; // 0x258 } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index adaaee200895..8b39ed6fb411 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1544,11 +1544,12 @@ public final class ActivityThread extends ClientTransactionHandler @Override public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin, boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, - boolean dumpUnreachable, String[] args) { + boolean dumpUnreachable, boolean dumpAllocatorStats, String[] args) { FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor()); PrintWriter pw = new FastPrintWriter(fout); try { - dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable); + dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, + dumpUnreachable, dumpAllocatorStats); } finally { pw.flush(); IoUtils.closeQuietly(pfd); @@ -1557,7 +1558,8 @@ public final class ActivityThread extends ClientTransactionHandler @NeverCompile private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin, - boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable) { + boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, + boolean dumpUnreachable, boolean dumpAllocatorStats) { long nativeMax = Debug.getNativeHeapSize() / 1024; long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; long nativeFree = Debug.getNativeHeapFreeSize() / 1024; @@ -1710,6 +1712,9 @@ public final class ActivityThread extends ClientTransactionHandler pw.println(" Unreachable memory"); pw.print(Debug.getUnreachableMemory(100, showContents)); } + if (dumpAllocatorStats) { + Debug.logAllocatorStats(); + } } @NeverCompile diff --git a/core/java/android/app/IApplicationThread.aidl b/core/java/android/app/IApplicationThread.aidl index 75d8c1012e27..5541e7aef160 100644 --- a/core/java/android/app/IApplicationThread.aidl +++ b/core/java/android/app/IApplicationThread.aidl @@ -129,7 +129,7 @@ oneway interface IApplicationThread { void scheduleTrimMemory(int level); void dumpMemInfo(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean checkin, boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable, - in String[] args); + boolean dumpAllocatorLogs, in String[] args); void dumpMemInfoProto(in ParcelFileDescriptor fd, in Debug.MemoryInfo mem, boolean dumpInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable, in String[] args); diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 1e538c52e635..90a265937082 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -16640,6 +16640,7 @@ public class DevicePolicyManager { == DEVICE_OWNER_TYPE_FINANCED; } + // TODO(b/315298076): revert ag/25574027 and update the doc /** * Called by a device owner or profile owner of an organization-owned managed profile to enable * or disable USB data signaling for the device. When disabled, USB data connections @@ -16649,12 +16650,11 @@ public class DevicePolicyManager { * {@link #canUsbDataSignalingBeDisabled()} to check whether enabling or disabling USB data * signaling is supported on the device. * - * Starting from {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, after the USB data signaling + * Starting from Android 15, after the USB data signaling * policy has been set, {@link PolicyUpdateReceiver#onPolicySetResult(Context, String, * Bundle, TargetUser, PolicyUpdateResult)} will notify the admin on whether the policy was * successfully set or not. This callback will contain: * <ul> - * li> The policy identifier {@link DevicePolicyIdentifiers#USB_DATA_SIGNALING_POLICY} * <li> The {@link TargetUser} that this policy relates to * <li> The {@link PolicyUpdateResult}, which will be * {@link PolicyUpdateResult#RESULT_POLICY_SET} if the policy was successfully set or the diff --git a/core/java/android/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl index 3520c0b4d724..12229b12fb16 100644 --- a/core/java/android/companion/virtual/IVirtualDevice.aidl +++ b/core/java/android/companion/virtual/IVirtualDevice.aidl @@ -219,6 +219,10 @@ interface IVirtualDevice { @EnforcePermission("CREATE_VIRTUAL_DEVICE") void setShowPointerIcon(boolean showPointerIcon); + /** Sets an IME policy for the given display. */ + @EnforcePermission("CREATE_VIRTUAL_DEVICE") + void setDisplayImePolicy(int displayId, int policy); + /** * Registers an intent interceptor that will intercept an intent attempting to launch * when matching the provided IntentFilter and calls the callback with the intercepted diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java index 003dffb1f9c1..2abeeeecc1c6 100644 --- a/core/java/android/companion/virtual/VirtualDeviceInternal.java +++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java @@ -53,6 +53,7 @@ import android.os.Looper; import android.os.RemoteException; import android.os.ResultReceiver; import android.util.ArrayMap; +import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; @@ -361,6 +362,14 @@ public class VirtualDeviceInternal { } } + void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { + try { + mVirtualDevice.setDisplayImePolicy(displayId, policy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + void addActivityListener( @CallbackExecutor @NonNull Executor executor, @NonNull VirtualDeviceManager.ActivityListener listener) { diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java index 41c90b96dc84..eef60f11fb1c 100644 --- a/core/java/android/companion/virtual/VirtualDeviceManager.java +++ b/core/java/android/companion/virtual/VirtualDeviceManager.java @@ -63,6 +63,7 @@ import android.os.Looper; import android.os.RemoteException; import android.util.Log; import android.view.Surface; +import android.view.WindowManager; import com.android.internal.annotations.GuardedBy; @@ -914,6 +915,24 @@ public final class VirtualDeviceManager { } /** + * Specifies the IME behavior on the given display. By default, all displays created by + * virtual devices have {@link WindowManager#DISPLAY_IME_POLICY_LOCAL}. + * + * @param displayId the ID of the display to change the IME policy for. It must be owned by + * this virtual device. + * @param policy the IME policy to use on that display + * @throws SecurityException if the display is not owned by this device or is not + * {@link DisplayManager#VIRTUAL_DISPLAY_FLAG_TRUSTED trusted} + */ + @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) + public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { + if (Flags.vdmCustomIme()) { + mVirtualDeviceInternal.setDisplayImePolicy(displayId, policy); + } + } + + /** * Adds an activity listener to listen for events such as top activity change or virtual * display task stack became empty. * diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 51f3d8ebab01..e395127dfaf3 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -1244,7 +1244,7 @@ public class PackageInstaller { * {@code statusReceiver} if timeout happens before commit. * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable * {@link android.app.PendingIntent} when caller has a target SDK of API - * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above. + * 35 or above. */ public void commitSessionAfterInstallConstraintsAreMet(int sessionId, @NonNull IntentSender statusReceiver, @NonNull InstallConstraints constraints, @@ -1954,7 +1954,7 @@ public class PackageInstaller { * {@link #openWrite(String, long, long)} are still open. * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable * {@link android.app.PendingIntent} when caller has a target SDK of API - * version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above. + * version 35 or above. * * @see android.app.admin.DevicePolicyManager * @see #requestUserPreapproval @@ -1985,7 +1985,7 @@ public class PackageInstaller { * individual status codes on how to handle them. * @throws IllegalArgumentException if the {@code statusReceiver} from an immutable * {@link android.app.PendingIntent} when caller has a target SDK of API - * {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} or above. + * 35 or above. * * @hide */ diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 607e9043e9bf..f865a36ea544 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -919,6 +919,10 @@ public abstract class PackageManager { @Retention(RetentionPolicy.SOURCE) public @interface InstrumentationInfoFlags {} + //------------------------------------------------------------------------- + // Beginning of GET_ and MATCH_ flags + //------------------------------------------------------------------------- + /** * {@link PackageInfo} flag: return information about * activities in the package in {@link PackageInfo#activities}. @@ -1216,30 +1220,21 @@ public abstract class PackageManager { */ public static final int MATCH_DIRECT_BOOT_AUTO = 0x10000000; + /** @hide */ + @Deprecated + public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO; + /** - * {@link ResolveInfo} flag: allow matching components across clone profile - * <p> - * This flag is used only for query and not resolution, the default behaviour would be to - * restrict querying across clone profile. This flag would be honored only if caller have - * permission {@link Manifest.permission.QUERY_CLONED_APPS}. + * @deprecated Use {@link #MATCH_CLONE_PROFILE_LONG} instead. * - * @hide - * <p> + * @hide */ + @SuppressLint("UnflaggedApi") // Just adding the @Deprecated annotation + @Deprecated @SystemApi public static final int MATCH_CLONE_PROFILE = 0x20000000; /** - * @deprecated Use {@link #GET_ATTRIBUTIONS_LONG} to avoid unintended sign extension. - */ - @Deprecated - public static final int GET_ATTRIBUTIONS = 0x80000000; - - /** @hide */ - @Deprecated - public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO; - - /** * {@link PackageInfo} flag: include system apps that are in the uninstalled state and have * been set to be hidden until installed via {@link #setSystemAppState}. * @hide @@ -1257,6 +1252,12 @@ public abstract class PackageManager { public static final int MATCH_APEX = 0x40000000; /** + * @deprecated Use {@link #GET_ATTRIBUTIONS_LONG} to avoid unintended sign extension. + */ + @Deprecated + public static final int GET_ATTRIBUTIONS = 0x80000000; + + /** * {@link PackageInfo} flag: return all attributions declared in the package manifest */ public static final long GET_ATTRIBUTIONS_LONG = 0x80000000L; @@ -1282,6 +1283,23 @@ public abstract class PackageManager { public static final long MATCH_QUARANTINED_COMPONENTS = 1L << 33; /** + * {@link ResolveInfo} flag: allow matching components across clone profile + * <p> + * This flag is used only for query and not resolution, the default behaviour would be to + * restrict querying across clone profile. This flag would be honored only if caller have + * permission {@link Manifest.permission.QUERY_CLONED_APPS}. + * + * @hide + */ + @FlaggedApi(android.content.pm.Flags.FLAG_FIX_DUPLICATED_FLAGS) + @SystemApi + public static final long MATCH_CLONE_PROFILE_LONG = 1L << 34; + + //------------------------------------------------------------------------- + // End of GET_ and MATCH_ flags + //------------------------------------------------------------------------- + + /** * Flag for {@link #addCrossProfileIntentFilter}: if this flag is set: when * resolving an intent that matches the {@code CrossProfileIntentFilter}, * the current profile will be skipped. Only activities in the target user diff --git a/core/java/android/content/pm/SharedLibraryInfo.java b/core/java/android/content/pm/SharedLibraryInfo.java index fdd2aa1fe5fa..25ba72551b04 100644 --- a/core/java/android/content/pm/SharedLibraryInfo.java +++ b/core/java/android/content/pm/SharedLibraryInfo.java @@ -142,8 +142,10 @@ public final class SharedLibraryInfo implements Parcelable { mName = parcel.readString8(); mVersion = parcel.readLong(); mType = parcel.readInt(); - mDeclaringPackage = parcel.readParcelable(null, android.content.pm.VersionedPackage.class); - mDependentPackages = parcel.readArrayList(null, android.content.pm.VersionedPackage.class); + mDeclaringPackage = + parcel.readParcelable(null, android.content.pm.VersionedPackage.class); + mDependentPackages = + parcel.readArrayList(null, android.content.pm.VersionedPackage.class); mDependencies = parcel.createTypedArrayList(SharedLibraryInfo.CREATOR); mIsNative = parcel.readBoolean(); } diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java index 56e8291f25e9..57749d43eb37 100644 --- a/core/java/android/content/pm/UserProperties.java +++ b/core/java/android/content/pm/UserProperties.java @@ -68,6 +68,11 @@ public final class UserProperties implements Parcelable { "authAlwaysRequiredToDisableQuietMode"; private static final String ATTR_DELETE_APP_WITH_PARENT = "deleteAppWithParent"; private static final String ATTR_ALWAYS_VISIBLE = "alwaysVisible"; + private static final String ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = + "allowStoppingUserWithDelayedLocking"; + + private static final String ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = + "crossProfileContentSharingStrategy"; /** Index values of each property (to indicate whether they are present in this object). */ @IntDef(prefix = "INDEX_", value = { @@ -86,6 +91,8 @@ public final class UserProperties implements Parcelable { INDEX_SHOW_IN_QUIET_MODE, INDEX_SHOW_IN_SHARING_SURFACES, INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE, + INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY, + INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING, }) @Retention(RetentionPolicy.SOURCE) private @interface PropertyIndex { @@ -105,6 +112,8 @@ public final class UserProperties implements Parcelable { private static final int INDEX_SHOW_IN_QUIET_MODE = 12; private static final int INDEX_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE = 13; private static final int INDEX_SHOW_IN_SHARING_SURFACES = 14; + private static final int INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY = 15; + private static final int INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING = 16; /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */ private long mPropertiesPresent = 0; @@ -365,6 +374,45 @@ public final class UserProperties implements Parcelable { */ @SuppressLint("UnflaggedApi") // b/306636213 public static final int SHOW_IN_SHARING_SURFACES_NO = SHOW_IN_LAUNCHER_NO; + /** + * Possible values for cross profile content sharing strategy for this profile. + * + * @hide + */ + @IntDef(prefix = {"CROSS_PROFILE_CONTENT_SHARING_STRATEGY_"}, value = { + CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION, + CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CrossProfileContentSharingStrategy { + } + + /** + * Signifies that cross-profile content sharing strategy, both to and from this profile, should + * not be delegated to any other user/profile. + * For ex: + * If this property is set for a profile, content sharing applications (such as Android + * Sharesheet), should not delegate the decision to share content between that profile and + * another profile to whether content sharing is allowed between any other profile/user related + * to those profiles. They should instead decide, based upon whether content sharing is + * specifically allowed between the two profiles in question. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION = 0; + + /** + * Signifies that cross-profile content sharing strategy, both to and from this profile, should + * be based upon the strategy used by the parent user of the profile. + * For ex: + * If this property is set for a profile A, content sharing applications (such as Android + * Sharesheet), should share content between profile A and profile B, based upon whether content + * sharing is allowed between the parent of profile A and profile B. + * If it's also set for profile B, then decision should, in turn be made by considering content + * sharing strategy between the parents of both profiles. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public static final int CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT = 1; + /** * Creates a UserProperties (intended for the SystemServer) that stores a reference to the given @@ -406,6 +454,7 @@ public final class UserProperties implements Parcelable { setCrossProfileIntentResolutionStrategy(orig.getCrossProfileIntentResolutionStrategy()); setDeleteAppWithParent(orig.getDeleteAppWithParent()); setAlwaysVisible(orig.getAlwaysVisible()); + setAllowStoppingUserWithDelayedLocking(orig.getAllowStoppingUserWithDelayedLocking()); } if (hasManagePermission) { // Add items that require MANAGE_USERS or stronger. @@ -423,6 +472,7 @@ public final class UserProperties implements Parcelable { setCredentialShareableWithParent(orig.isCredentialShareableWithParent()); setShowInQuietMode(orig.getShowInQuietMode()); setShowInSharingSurfaces(orig.getShowInSharingSurfaces()); + setCrossProfileContentSharingStrategy(orig.getCrossProfileContentSharingStrategy()); } /** @@ -680,6 +730,11 @@ public final class UserProperties implements Parcelable { this.mUpdateCrossProfileIntentFiltersOnOTA = val; setPresent(INDEX_UPDATE_CROSS_PROFILE_INTENT_FILTERS_ON_OTA); } + /** + Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during + OTA update between user-parent + */ + private boolean mUpdateCrossProfileIntentFiltersOnOTA; /** * Returns whether a profile shares media with its parent user. @@ -741,12 +796,38 @@ public final class UserProperties implements Parcelable { } private boolean mAuthAlwaysRequiredToDisableQuietMode; - /* - Indicate if {@link com.android.server.pm.CrossProfileIntentFilter}s need to be updated during - OTA update between user-parent + /** + * Returns whether a user (usually a profile) is allowed to leave the CE storage unlocked when + * stopped. + * + * <p> Setting this property to true will enable the user's CE storage to remain unlocked when + * the user is stopped using + * {@link com.android.server.am.ActivityManagerService#stopUserWithDelayedLocking(int, + * boolean, IStopUserCallback)}. + * + * <p> When this property is false, delayed locking may still be applicable at a global + * level for all users via the {@code config_multiuserDelayUserDataLocking}. That is, delayed + * locking for a user can happen if either the device configuration is set or if this property + * is set. When both, the config and the property value is false, the user storage is always + * locked when the user is stopped. + * @hide */ - private boolean mUpdateCrossProfileIntentFiltersOnOTA; - + public boolean getAllowStoppingUserWithDelayedLocking() { + if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) { + return mAllowStoppingUserWithDelayedLocking; + } + if (mDefaultProperties != null) { + return mDefaultProperties.mAllowStoppingUserWithDelayedLocking; + } + throw new SecurityException( + "You don't have permission to query allowStoppingUserWithDelayedLocking"); + } + /** @hide */ + public void setAllowStoppingUserWithDelayedLocking(boolean val) { + this.mAllowStoppingUserWithDelayedLocking = val; + setPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING); + } + private boolean mAllowStoppingUserWithDelayedLocking; /** * Returns the user's {@link CrossProfileIntentFilterAccessControlLevel}. @@ -776,8 +857,7 @@ public final class UserProperties implements Parcelable { private @CrossProfileIntentFilterAccessControlLevel int mCrossProfileIntentFilterAccessControl; /** - * Returns the user's {@link CrossProfileIntentResolutionStrategy}. If not explicitly - * configured, default value is {@link #CROSS_PROFILE_INTENT_RESOLUTION_STRATEGY_DEFAULT}. + * Returns the user's {@link CrossProfileIntentResolutionStrategy}. * @return user's {@link CrossProfileIntentResolutionStrategy}. * * @hide @@ -792,11 +872,8 @@ public final class UserProperties implements Parcelable { throw new SecurityException("You don't have permission to query " + "crossProfileIntentResolutionStrategy"); } - /** - * Sets {@link CrossProfileIntentResolutionStrategy} for the user. - * @param val resolution strategy for user - * @hide - */ + + /** @hide */ public void setCrossProfileIntentResolutionStrategy( @CrossProfileIntentResolutionStrategy int val) { this.mCrossProfileIntentResolutionStrategy = val; @@ -804,6 +881,39 @@ public final class UserProperties implements Parcelable { } private @CrossProfileIntentResolutionStrategy int mCrossProfileIntentResolutionStrategy; + /** + * Returns the user's {@link CrossProfileContentSharingStrategy}. + * + * Content sharing applications, such as Android Sharesheet allow sharing of content + * (an image, for ex.) between profiles, based upon cross-profile access checks between the + * originating and destined profile. + * In some cases however, we may want another user (such as profile parent) to serve as the + * delegated user to be used for such checks. + * To effect the same, clients can fetch this property and accordingly replace the + * originating/destined profile by another user for cross-profile access checks. + * + * @return user's {@link CrossProfileContentSharingStrategy}. + */ + @SuppressLint("UnflaggedApi") // b/306636213 + public @CrossProfileContentSharingStrategy int getCrossProfileContentSharingStrategy() { + if (isPresent(INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY)) { + return mCrossProfileContentSharingStrategy; + } + if (mDefaultProperties != null) { + return mDefaultProperties.mCrossProfileContentSharingStrategy; + } + throw new SecurityException("You don't have permission to query " + + "crossProfileContentSharingStrategy"); + } + + /** @hide */ + public void setCrossProfileContentSharingStrategy( + @CrossProfileContentSharingStrategy int val) { + this.mCrossProfileContentSharingStrategy = val; + setPresent(INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY); + } + private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy; + @Override public String toString() { @@ -825,8 +935,11 @@ public final class UserProperties implements Parcelable { + ", mCredentialShareableWithParent=" + isCredentialShareableWithParent() + ", mAuthAlwaysRequiredToDisableQuietMode=" + isAuthAlwaysRequiredToDisableQuietMode() + + ", mAllowStoppingUserWithDelayedLocking=" + + getAllowStoppingUserWithDelayedLocking() + ", mDeleteAppWithParent=" + getDeleteAppWithParent() + ", mAlwaysVisible=" + getAlwaysVisible() + + ", mCrossProfileContentSharingStrategy=" + getCrossProfileContentSharingStrategy() + "}"; } @@ -854,8 +967,12 @@ public final class UserProperties implements Parcelable { + isCredentialShareableWithParent()); pw.println(prefix + " mAuthAlwaysRequiredToDisableQuietMode=" + isAuthAlwaysRequiredToDisableQuietMode()); + pw.println(prefix + " mAllowStoppingUserWithDelayedLocking=" + + getAllowStoppingUserWithDelayedLocking()); pw.println(prefix + " mDeleteAppWithParent=" + getDeleteAppWithParent()); pw.println(prefix + " mAlwaysVisible=" + getAlwaysVisible()); + pw.println(prefix + " mCrossProfileContentSharingStrategy=" + + getCrossProfileContentSharingStrategy()); } /** @@ -928,12 +1045,17 @@ public final class UserProperties implements Parcelable { case ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE: setAuthAlwaysRequiredToDisableQuietMode(parser.getAttributeBoolean(i)); break; + case ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING: + setAllowStoppingUserWithDelayedLocking(parser.getAttributeBoolean(i)); + break; case ATTR_DELETE_APP_WITH_PARENT: setDeleteAppWithParent(parser.getAttributeBoolean(i)); break; case ATTR_ALWAYS_VISIBLE: setAlwaysVisible(parser.getAttributeBoolean(i)); break; + case ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY: + setCrossProfileContentSharingStrategy(parser.getAttributeInt(i)); default: Slog.w(LOG_TAG, "Skipping unknown property " + attributeName); } @@ -1000,6 +1122,10 @@ public final class UserProperties implements Parcelable { serializer.attributeBoolean(null, ATTR_AUTH_ALWAYS_REQUIRED_TO_DISABLE_QUIET_MODE, mAuthAlwaysRequiredToDisableQuietMode); } + if (isPresent(INDEX_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING)) { + serializer.attributeBoolean(null, ATTR_ALLOW_STOPPING_USER_WITH_DELAYED_LOCKING, + mAllowStoppingUserWithDelayedLocking); + } if (isPresent(INDEX_DELETE_APP_WITH_PARENT)) { serializer.attributeBoolean(null, ATTR_DELETE_APP_WITH_PARENT, mDeleteAppWithParent); @@ -1008,6 +1134,10 @@ public final class UserProperties implements Parcelable { serializer.attributeBoolean(null, ATTR_ALWAYS_VISIBLE, mAlwaysVisible); } + if (isPresent(INDEX_CROSS_PROFILE_CONTENT_SHARING_STRATEGY)) { + serializer.attributeInt(null, ATTR_CROSS_PROFILE_CONTENT_SHARING_STRATEGY, + mCrossProfileContentSharingStrategy); + } } // For use only with an object that has already had any permission-lacking fields stripped out. @@ -1027,8 +1157,10 @@ public final class UserProperties implements Parcelable { dest.writeBoolean(mMediaSharedWithParent); dest.writeBoolean(mCredentialShareableWithParent); dest.writeBoolean(mAuthAlwaysRequiredToDisableQuietMode); + dest.writeBoolean(mAllowStoppingUserWithDelayedLocking); dest.writeBoolean(mDeleteAppWithParent); dest.writeBoolean(mAlwaysVisible); + dest.writeInt(mCrossProfileContentSharingStrategy); } /** @@ -1052,8 +1184,10 @@ public final class UserProperties implements Parcelable { mMediaSharedWithParent = source.readBoolean(); mCredentialShareableWithParent = source.readBoolean(); mAuthAlwaysRequiredToDisableQuietMode = source.readBoolean(); + mAllowStoppingUserWithDelayedLocking = source.readBoolean(); mDeleteAppWithParent = source.readBoolean(); mAlwaysVisible = source.readBoolean(); + mCrossProfileContentSharingStrategy = source.readInt(); } @Override @@ -1098,8 +1232,11 @@ public final class UserProperties implements Parcelable { private boolean mMediaSharedWithParent = false; private boolean mCredentialShareableWithParent = false; private boolean mAuthAlwaysRequiredToDisableQuietMode = false; + private boolean mAllowStoppingUserWithDelayedLocking = false; private boolean mDeleteAppWithParent = false; private boolean mAlwaysVisible = false; + private @CrossProfileContentSharingStrategy int mCrossProfileContentSharingStrategy = + CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION; /** * @hide @@ -1215,6 +1352,16 @@ public final class UserProperties implements Parcelable { return this; } + /** Sets the value for {@link #mAllowStoppingUserWithDelayedLocking} + * @hide + */ + public Builder setAllowStoppingUserWithDelayedLocking( + boolean allowStoppingUserWithDelayedLocking) { + mAllowStoppingUserWithDelayedLocking = + allowStoppingUserWithDelayedLocking; + return this; + } + /** Sets the value for {@link #mDeleteAppWithParent} * @hide */ @@ -1231,6 +1378,19 @@ public final class UserProperties implements Parcelable { return this; } + /** Sets the value for {@link #mCrossProfileContentSharingStrategy} + * @hide + */ + + @TestApi + @SuppressLint("UnflaggedApi") // b/306636213 + @NonNull + public Builder setCrossProfileContentSharingStrategy(@CrossProfileContentSharingStrategy + int crossProfileContentSharingStrategy) { + mCrossProfileContentSharingStrategy = crossProfileContentSharingStrategy; + return this; + } + /** Builds a UserProperties object with *all* values populated. * @hide */ @@ -1252,8 +1412,10 @@ public final class UserProperties implements Parcelable { mMediaSharedWithParent, mCredentialShareableWithParent, mAuthAlwaysRequiredToDisableQuietMode, + mAllowStoppingUserWithDelayedLocking, mDeleteAppWithParent, - mAlwaysVisible); + mAlwaysVisible, + mCrossProfileContentSharingStrategy); } } // end Builder @@ -1271,8 +1433,10 @@ public final class UserProperties implements Parcelable { boolean mediaSharedWithParent, boolean credentialShareableWithParent, boolean authAlwaysRequiredToDisableQuietMode, + boolean allowStoppingUserWithDelayedLocking, boolean deleteAppWithParent, - boolean alwaysVisible) { + boolean alwaysVisible, + @CrossProfileContentSharingStrategy int crossProfileContentSharingStrategy) { mDefaultProperties = null; setShowInLauncher(showInLauncher); setStartWithParent(startWithParent); @@ -1288,7 +1452,9 @@ public final class UserProperties implements Parcelable { setCredentialShareableWithParent(credentialShareableWithParent); setAuthAlwaysRequiredToDisableQuietMode( authAlwaysRequiredToDisableQuietMode); + setAllowStoppingUserWithDelayedLocking(allowStoppingUserWithDelayedLocking); setDeleteAppWithParent(deleteAppWithParent); setAlwaysVisible(alwaysVisible); + setCrossProfileContentSharingStrategy(crossProfileContentSharingStrategy); } } diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index 1b90570e4609..b04b7badbae0 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -108,3 +108,10 @@ flag { description: "Feature flag to reduce app crashes caused by split installs with INSTALL_DONT_KILL" bug: "291212866" } + +flag { + name: "fix_duplicated_flags" + namespace: "package_manager_service" + description: "Feature flag to fix duplicated PackageManager flag values" + bug: "314815969" +} diff --git a/core/java/android/credentials/flags.aconfig b/core/java/android/credentials/flags.aconfig index bab84aadc73b..f876eebe64c1 100644 --- a/core/java/android/credentials/flags.aconfig +++ b/core/java/android/credentials/flags.aconfig @@ -26,4 +26,11 @@ flag { name: "new_settings_intents" description: "Enables settings intents to redirect to new settings page" bug: "307587989" +} + +flag { + namespace: "credential_manager" + name: "new_settings_ui" + description: "Enables new settings UI for VIC" + bug: "315209085" }
\ No newline at end of file diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index dec5b9c7352c..e3a552023e4b 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -1355,6 +1355,27 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri new Key<Boolean>("android.control.autoframingAvailable", boolean.class); /** + * <p>The operating luminance range of low light boost measured in lux (lx).</p> + * <p><b>Range of valid values:</b><br></p> + * <p>The lower bound indicates the lowest scene luminance value the AE mode + * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' can operate within. Scenes of lower luminance + * than this may receive less brightening, increased noise, or artifacts.</p> + * <p>The upper bound indicates the luminance threshold at the point when the mode is enabled. + * For example, 'Range[0.3, 30.0]' defines 0.3 lux being the lowest scene luminance the + * mode can reliably support. 30.0 lux represents the threshold when this mode is + * activated. Scenes measured at less than or equal to 30 lux will activate low light + * boost.</p> + * <p>If this key is defined, then the AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' will + * also be present.</p> + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + */ + @PublicKey + @NonNull + @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST) + public static final Key<android.util.Range<Float>> CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE = + new Key<android.util.Range<Float>>("android.control.lowLightBoostInfoLuminanceRange", new TypeReference<android.util.Range<Float>>() {{ }}); + + /** * <p>List of edge enhancement modes for {@link CaptureRequest#EDGE_MODE android.edge.mode} that are supported by this camera * device.</p> * <p>Full-capability camera devices must always support OFF; camera devices that support diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java index 002c0b207506..bcce4b65be18 100644 --- a/core/java/android/hardware/camera2/CameraManager.java +++ b/core/java/android/hardware/camera2/CameraManager.java @@ -1844,7 +1844,7 @@ public final class CameraManager { * Remaps Camera Ids in the CameraService. * * @hide - */ + */ @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) public void remapCameraIds(@NonNull CameraIdRemapping cameraIdRemapping) throws CameraAccessException, SecurityException, IllegalArgumentException { @@ -1852,6 +1852,29 @@ public final class CameraManager { } /** + * Injects session params into existing clients in the CameraService. + * + * @param cameraId The camera id of client to inject session params into. + * If no such client exists for cameraId, no injection will + * take place. + * @param sessionParams A {@link CaptureRequest} object containing the + * the sessionParams to inject into the existing client. + * + * @throws CameraAccessException {@link CameraAccessException#CAMERA_DISCONNECTED} will be + * thrown if camera service is not available. Further, if + * if no such client exists for cameraId, + * {@link CameraAccessException#CAMERA_ERROR} will be thrown. + * @throws SecurityException If the caller does not have permission to inject session + * params + * @hide + */ + @RequiresPermission(android.Manifest.permission.CAMERA_INJECT_EXTERNAL_CAMERA) + public void injectSessionParams(@NonNull String cameraId, @NonNull CaptureRequest sessionParams) + throws CameraAccessException, SecurityException { + CameraManagerGlobal.get().injectSessionParams(cameraId, sessionParams); + } + + /** * Reports {@link CameraExtensionSessionStats} to the {@link ICameraService} to be logged for * currently active session. Validation is done downstream. * @@ -2110,6 +2133,30 @@ public final class CameraManager { } } + /** Injects session params into an existing client for cameraid. */ + public void injectSessionParams(@NonNull String cameraId, + @NonNull CaptureRequest sessionParams) + throws CameraAccessException, SecurityException { + synchronized (mLock) { + ICameraService cameraService = getCameraService(); + if (cameraService == null) { + throw new CameraAccessException( + CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable."); + } + + try { + cameraService.injectSessionParams(cameraId, sessionParams.getNativeMetadata()); + } catch (ServiceSpecificException e) { + throwAsPublicException(e); + } catch (RemoteException e) { + throw new CameraAccessException( + CameraAccessException.CAMERA_DISCONNECTED, + "Camera service is currently unavailable."); + } + } + } + private String[] extractCameraIdListLocked() { String[] cameraIds = null; int idCount = 0; diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java index 003718e6b54e..765a8f7c842c 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -2333,6 +2333,46 @@ public abstract class CameraMetadata<TKey> { */ public static final int CONTROL_AE_MODE_ON_EXTERNAL_FLASH = 5; + /** + * <p>Like 'ON' but applies additional brightness boost in low light scenes.</p> + * <p>When the scene lighting conditions are within the range defined by + * {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange} this mode will apply additional + * brightness boost.</p> + * <p>This mode will automatically adjust the intensity of low light boost applied + * according to the scene lighting conditions. A darker scene will receive more boost + * while a brighter scene will receive less boost.</p> + * <p>This mode can ignore the set target frame rate to allow more light to be captured + * which can result in choppier motion. The frame rate can extend to lower than the + * {@link CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES android.control.aeAvailableTargetFpsRanges} but will not go below 10 FPS. This mode + * can also increase the sensor sensitivity gain which can result in increased luma + * and chroma noise. The sensor sensitivity gain can extend to higher values beyond + * {@link CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE android.sensor.info.sensitivityRange}. This mode may also apply additional + * processing to recover details in dark and bright areas of the image,and noise + * reduction at high sensitivity gain settings to manage the trade-off between light + * sensitivity and capture noise.</p> + * <p>This mode is restricted to two output surfaces. One output surface type can either + * be SurfaceView or TextureView. Another output surface type can either be MediaCodec + * or MediaRecorder. This mode cannot be used with a target FPS range higher than 30 + * FPS.</p> + * <p>If the session configuration is not supported, the AE mode reported in the + * CaptureResult will be 'ON' instead of 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY'.</p> + * <p>The application can observe the CapturerResult field + * {@link CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE android.control.lowLightBoostState} to determine when low light boost is 'ACTIVE' or + * 'INACTIVE'.</p> + * <p>The low light boost is 'ACTIVE' once the scene lighting condition is less than the + * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}. + * This mode will be 'INACTIVE' once the scene lighting condition is greater than the + * upper bound lux value defined by {@link CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE android.control.lowLightBoostInfoLuminanceRange}.</p> + * + * @see CameraCharacteristics#CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES + * @see CameraCharacteristics#CONTROL_LOW_LIGHT_BOOST_INFO_LUMINANCE_RANGE + * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE + * @see CameraCharacteristics#SENSOR_INFO_SENSITIVITY_RANGE + * @see CaptureRequest#CONTROL_AE_MODE + */ + @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST) + public static final int CONTROL_AE_MODE_ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY = 6; + // // Enumeration values for CaptureRequest#CONTROL_AE_PRECAPTURE_TRIGGER // @@ -4074,6 +4114,24 @@ public abstract class CameraMetadata<TKey> { public static final int CONTROL_AUTOFRAMING_STATE_CONVERGED = 2; // + // Enumeration values for CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE + // + + /** + * <p>The AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' is enabled but not applied.</p> + * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE + */ + @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST) + public static final int CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE = 0; + + /** + * <p>The AE mode 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY' is enabled and applied.</p> + * @see CaptureResult#CONTROL_LOW_LIGHT_BOOST_STATE + */ + @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST) + public static final int CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE = 1; + + // // Enumeration values for CaptureResult#FLASH_STATE // diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java index 35f295a36d87..ab4406c37c8e 100644 --- a/core/java/android/hardware/camera2/CaptureResult.java +++ b/core/java/android/hardware/camera2/CaptureResult.java @@ -2814,6 +2814,31 @@ public class CaptureResult extends CameraMetadata<CaptureResult.Key<?>> { new Key<Integer>("android.control.autoframingState", int.class); /** + * <p>Current state of the low light boost AE mode.</p> + * <p>When low light boost is enabled by setting the AE mode to + * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY', it can dynamically apply a low light + * boost when the light level threshold is exceeded.</p> + * <p>This state indicates when low light boost is 'ACTIVE' and applied. Similarly, it can + * indicate when it is not being applied by returning 'INACTIVE'.</p> + * <p>This key will be absent from the CaptureResult if AE mode is not set to + * 'ON_LOW_LIGHT_BOOST_BRIGHTNESS_PRIORITY.</p> + * <p><b>Possible values:</b></p> + * <ul> + * <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE INACTIVE}</li> + * <li>{@link #CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE ACTIVE}</li> + * </ul> + * + * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> + * @see #CONTROL_LOW_LIGHT_BOOST_STATE_INACTIVE + * @see #CONTROL_LOW_LIGHT_BOOST_STATE_ACTIVE + */ + @PublicKey + @NonNull + @FlaggedApi(Flags.FLAG_CAMERA_AE_MODE_LOW_LIGHT_BOOST) + public static final Key<Integer> CONTROL_LOW_LIGHT_BOOST_STATE = + new Key<Integer>("android.control.lowLightBoostState", int.class); + + /** * <p>Operation mode for edge * enhancement.</p> * <p>Edge enhancement improves sharpness and details in the captured image. OFF means diff --git a/core/java/android/hardware/camera2/extension/RequestProcessor.java b/core/java/android/hardware/camera2/extension/RequestProcessor.java index 7c099d67e6e9..bf5ea12df358 100644 --- a/core/java/android/hardware/camera2/extension/RequestProcessor.java +++ b/core/java/android/hardware/camera2/extension/RequestProcessor.java @@ -250,7 +250,7 @@ public final class RequestProcessor { */ @FlaggedApi(Flags.FLAG_CONCERT_MODE) @NonNull - List<Pair<CaptureRequest.Key, Object>> getParameters() { + public List<Pair<CaptureRequest.Key, Object>> getParameters() { return mParameters; } diff --git a/core/java/android/os/CoolingDevice.java b/core/java/android/os/CoolingDevice.java index 4ddcd9d4ff8b..06ec72051c2f 100644 --- a/core/java/android/os/CoolingDevice.java +++ b/core/java/android/os/CoolingDevice.java @@ -55,7 +55,11 @@ public final class CoolingDevice implements Parcelable { TYPE_TPU, TYPE_POWER_AMPLIFIER, TYPE_DISPLAY, - TYPE_SPEAKER + TYPE_SPEAKER, + TYPE_WIFI, + TYPE_CAMERA, + TYPE_FLASHLIGHT, + TYPE_USB_PORT }) @Retention(RetentionPolicy.SOURCE) public @interface Type {} @@ -84,6 +88,14 @@ public final class CoolingDevice implements Parcelable { public static final int TYPE_DISPLAY = CoolingType.DISPLAY; /** Speaker cooling device */ public static final int TYPE_SPEAKER = CoolingType.SPEAKER; + /** WiFi cooling device */ + public static final int TYPE_WIFI = CoolingType.WIFI; + /** Camera cooling device */ + public static final int TYPE_CAMERA = CoolingType.CAMERA; + /** Flashlight cooling device */ + public static final int TYPE_FLASHLIGHT = CoolingType.FLASHLIGHT; + /** USB PORT cooling device */ + public static final int TYPE_USB_PORT = CoolingType.USB_PORT; /** * Verify a valid cooling device type. @@ -91,7 +103,7 @@ public final class CoolingDevice implements Parcelable { * @return true if a cooling device type is valid otherwise false. */ public static boolean isValidType(@Type int type) { - return type >= TYPE_FAN && type <= TYPE_SPEAKER; + return type >= TYPE_FAN && type <= TYPE_USB_PORT; } public CoolingDevice(long value, @Type int type, @NonNull String name) { diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index c527cb5344c1..04d6f61d84c9 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -2702,4 +2702,13 @@ public final class Debug * @hide */ public static native boolean isVmapStack(); + + /** + * Log internal statistics about the allocator. + * @return true if the statistics were logged properly, false if not. + * + * @hide + */ + public static native boolean logAllocatorStats(); + } diff --git a/core/java/android/os/Temperature.java b/core/java/android/os/Temperature.java index a138431f745a..2e12278a0ad3 100644 --- a/core/java/android/os/Temperature.java +++ b/core/java/android/os/Temperature.java @@ -79,7 +79,13 @@ public final class Temperature implements Parcelable { TYPE_TPU, TYPE_DISPLAY, TYPE_MODEM, - TYPE_SOC + TYPE_SOC, + TYPE_WIFI, + TYPE_CAMERA, + TYPE_FLASHLIGHT, + TYPE_SPEAKER, + TYPE_AMBIENT, + TYPE_POGO }) @Retention(RetentionPolicy.SOURCE) public @interface Type {} @@ -101,6 +107,12 @@ public final class Temperature implements Parcelable { public static final int TYPE_DISPLAY = TemperatureType.DISPLAY; public static final int TYPE_MODEM = TemperatureType.MODEM; public static final int TYPE_SOC = TemperatureType.SOC; + public static final int TYPE_WIFI = TemperatureType.WIFI; + public static final int TYPE_CAMERA = TemperatureType.CAMERA; + public static final int TYPE_FLASHLIGHT = TemperatureType.FLASHLIGHT; + public static final int TYPE_SPEAKER = TemperatureType.SPEAKER; + public static final int TYPE_AMBIENT = TemperatureType.AMBIENT; + public static final int TYPE_POGO = TemperatureType.POGO; /** * Verify a valid Temperature type. @@ -108,7 +120,7 @@ public final class Temperature implements Parcelable { * @return true if a Temperature type is valid otherwise false. */ public static boolean isValidType(@Type int type) { - return type >= TYPE_UNKNOWN && type <= TYPE_SOC; + return type >= TYPE_UNKNOWN && type <= TYPE_POGO; } /** diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig index 980c13c5c2d6..145981c92283 100644 --- a/core/java/android/os/flags.aconfig +++ b/core/java/android/os/flags.aconfig @@ -76,3 +76,11 @@ flag { description: "Guards the ADPF GPU APIs." bug: "284324521" } + +flag { + name: "battery_service_support_current_adb_command" + namespace: "backstage_power" + description: "Whether or not BatteryService supports adb commands for Current values." + is_fixed_read_only: true + bug: "315037695" +} diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 8b5995a740f8..9c27f19b349a 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -608,6 +608,23 @@ public final class Settings { /** * Activity Action: Show settings to allow configuration of + * {@link Manifest.permission#MEDIA_ROUTING_CONTROL} permission. + * + * Input: Optionally, the Intent's data URI can specify the application package name to + * directly invoke the management GUI specific to the package name. For example + * "package:com.my.app". However, modifying this permission setting for any package is allowed + * only when that package holds an appropriate companion device profile such as + * {@link android.companion.AssociationRequest#DEVICE_PROFILE_WATCH}. + * <p> + * Output: Nothing. + */ + @FlaggedApi("com.android.media.flags.enable_privileged_routing_for_media_routing_control") + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_REQUEST_MEDIA_ROUTING_CONTROL = + "android.settings.REQUEST_MEDIA_ROUTING_CONTROL"; + + /** + * Activity Action: Show settings to allow configuration of * {@link Manifest.permission#RUN_USER_INITIATED_JOBS} permission * * Input: Optionally, the Intent's data URI can specify the application package name to diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig new file mode 100644 index 000000000000..91a713ee6250 --- /dev/null +++ b/core/java/android/service/dreams/flags.aconfig @@ -0,0 +1,9 @@ +package: "android.service.dreams" + +flag { + name: "dream_overlay_host" + namespace: "communal" + description: "This flag enables using a host to handle displaying a dream's overlay rather than " + "relying on the dream's window" + bug: "291990564" +} diff --git a/core/java/android/service/voice/VisualQueryDetectionService.java b/core/java/android/service/voice/VisualQueryDetectionService.java index d184b1eb96ab..76b076be8fab 100644 --- a/core/java/android/service/voice/VisualQueryDetectionService.java +++ b/core/java/android/service/voice/VisualQueryDetectionService.java @@ -338,16 +338,24 @@ public abstract class VisualQueryDetectionService extends Service /** * Overrides {@link Context#openFileInput} to read files with the given file names under the - * internal app storage of the {@link VoiceInteractionService}, i.e., only files stored in - * {@link Context#getFilesDir()} can be opened. + * internal app storage of the {@link VoiceInteractionService}, i.e., the input file path would + * be added with {@link Context#getFilesDir()} as prefix. + * + * @param filename Relative path of a file under {@link Context#getFilesDir()}. + * @throws FileNotFoundException if the file does not exist or cannot be open. */ @Override - public @Nullable FileInputStream openFileInput(@NonNull String filename) throws + public @NonNull FileInputStream openFileInput(@NonNull String filename) throws FileNotFoundException { try { AndroidFuture<ParcelFileDescriptor> future = new AndroidFuture<>(); + assert mDetectorSessionStorageService != null; mDetectorSessionStorageService.openFile(filename, future); ParcelFileDescriptor pfd = future.get(); + if (pfd == null) { + throw new FileNotFoundException( + "File does not exist. Unable to open " + filename + "."); + } return new FileInputStream(pfd.getFileDescriptor()); } catch (RemoteException | ExecutionException | InterruptedException e) { Log.w(TAG, "Cannot open file due to remote service failure"); diff --git a/core/java/android/service/voice/VisualQueryDetector.java b/core/java/android/service/voice/VisualQueryDetector.java index 91de894c1d93..b7d97057a08b 100644 --- a/core/java/android/service/voice/VisualQueryDetector.java +++ b/core/java/android/service/voice/VisualQueryDetector.java @@ -447,12 +447,12 @@ public class VisualQueryDetector { public void onOpenFile(String filename, AndroidFuture future) throws RemoteException { Slog.v(TAG, "BinderCallback#onOpenFile " + filename); Binder.withCleanCallingIdentity(() -> mExecutor.execute(() -> { - Slog.v(TAG, "onOpenFile: " + filename); + Slog.v(TAG, "onOpenFile: " + filename + "under internal app storage."); File f = new File(mContext.getFilesDir(), filename); ParcelFileDescriptor pfd = null; try { - Slog.d(TAG, "opened a file with ParcelFileDescriptor."); pfd = ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY); + Slog.d(TAG, "Successfully opened a file with ParcelFileDescriptor."); } catch (FileNotFoundException e) { Slog.e(TAG, "Cannot open file. No ParcelFileDescriptor returned."); } finally { diff --git a/core/java/android/view/AttachedSurfaceControl.java b/core/java/android/view/AttachedSurfaceControl.java index fd5517d29d74..f28574ecb3b2 100644 --- a/core/java/android/view/AttachedSurfaceControl.java +++ b/core/java/android/view/AttachedSurfaceControl.java @@ -27,9 +27,6 @@ import android.window.SurfaceSyncGroup; import com.android.window.flags.Flags; -import java.util.concurrent.Executor; -import java.util.function.Consumer; - /** * Provides an interface to the root-Surface of a View Hierarchy or Window. This * is used in combination with the {@link android.view.SurfaceControl} API to enable @@ -197,42 +194,6 @@ public interface AttachedSurfaceControl { } /** - * Add a trusted presentation listener on the SurfaceControl associated with this window. - * - * @param t Transaction that the trusted presentation listener is added on. This should - * be applied by the caller. - * @param thresholds The {@link SurfaceControl.TrustedPresentationThresholds} that will specify - * when the to invoke the callback. - * @param executor The {@link Executor} where the callback will be invoked on. - * @param listener The {@link Consumer} that will receive the callbacks when entered or - * exited the threshold. - * - * @see SurfaceControl.Transaction#setTrustedPresentationCallback(SurfaceControl, - * SurfaceControl.TrustedPresentationThresholds, Executor, Consumer) - * - * @hide b/287076178 un-hide with API bump - */ - default void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull SurfaceControl.TrustedPresentationThresholds thresholds, - @NonNull Executor executor, @NonNull Consumer<Boolean> listener) { - } - - /** - * Remove a trusted presentation listener on the SurfaceControl associated with this window. - * - * @param t Transaction that the trusted presentation listener removed on. This should - * be applied by the caller. - * @param listener The {@link Consumer} that was previously registered with - * addTrustedPresentationCallback that should be removed. - * - * @see SurfaceControl.Transaction#clearTrustedPresentationCallback(SurfaceControl) - * @hide b/287076178 un-hide with API bump - */ - default void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull Consumer<Boolean> listener) { - } - - /** * Transfer the currently in progress touch gesture from the host to the requested * {@link SurfaceControlViewHost.SurfacePackage}. This requires that the * SurfaceControlViewHost was created with the current host's inputToken. diff --git a/core/java/android/view/IWindowManager.aidl b/core/java/android/view/IWindowManager.aidl index 17bbee6d020f..36b74e39072a 100644 --- a/core/java/android/view/IWindowManager.aidl +++ b/core/java/android/view/IWindowManager.aidl @@ -73,6 +73,8 @@ import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; import android.window.ScreenCapture; import android.window.WindowContextInfo; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; /** * System private interface to the window manager. @@ -1075,4 +1077,10 @@ interface IWindowManager @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest" + ".permission.MONITOR_INPUT)") void unregisterDecorViewGestureListener(IDecorViewGestureListener listener, int displayId); + + void registerTrustedPresentationListener(in IBinder window, in ITrustedPresentationListener listener, + in TrustedPresentationThresholds thresholds, int id); + + + void unregisterTrustedPresentationListener(in ITrustedPresentationListener listener, int id); } diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 1530aa78d73d..659db5f59f39 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -3216,6 +3216,12 @@ public final class ViewRootImpl implements ViewParent, endDragResizing(); destroyHardwareResources(); } + + if (sToolkitSetFrameRateReadOnlyFlagValue && viewVisibility == View.VISIBLE) { + // Boost frame rate when the viewVisibility becomes true. + // This is mainly for lanuchers that lanuch new windows. + boostFrameRate(FRAME_RATE_TOUCH_BOOST_TIME); + } } // Non-visible windows can't hold accessibility focus. @@ -3925,6 +3931,11 @@ public final class ViewRootImpl implements ViewParent, focused.restoreDefaultFocus(); } } + + if (sToolkitSetFrameRateReadOnlyFlagValue) { + // Boost the frame rate when the ViewRootImpl first becomes available. + boostFrameRate(FRAME_RATE_TOUCH_BOOST_TIME); + } } final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible; @@ -12005,18 +12016,6 @@ public final class ViewRootImpl implements ViewParent, scheduleTraversals(); } - @Override - public void addTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull SurfaceControl.TrustedPresentationThresholds thresholds, - @NonNull Executor executor, @NonNull Consumer<Boolean> listener) { - t.setTrustedPresentationCallback(getSurfaceControl(), thresholds, executor, listener); - } - - @Override - public void removeTrustedPresentationCallback(@NonNull SurfaceControl.Transaction t, - @NonNull Consumer<Boolean> listener) { - t.clearTrustedPresentationCallback(getSurfaceControl()); - } private void logAndTrace(String msg) { if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) { @@ -12036,7 +12035,7 @@ public final class ViewRootImpl implements ViewParent, try { if (mLastPreferredFrameRateCategory != frameRateCategory) { mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, - frameRateCategory, false).applyAsyncUnsafe(); + frameRateCategory, false).applyAsyncUnsafe(); mLastPreferredFrameRateCategory = frameRateCategory; } } catch (Exception e) { @@ -12159,6 +12158,22 @@ public final class ViewRootImpl implements ViewParent, return mPreferredFrameRate; } + /** + * Get the value of mIsFrameRateBoosting + */ + @VisibleForTesting + public boolean getIsFrameRateBoosting() { + return mIsFrameRateBoosting; + } + + private void boostFrameRate(int boostTimeOut) { + mIsFrameRateBoosting = true; + setPreferredFrameRateCategory(mPreferredFrameRateCategory); + mHandler.removeMessages(MSG_TOUCH_BOOST_TIMEOUT); + mHandler.sendEmptyMessageDelayed(MSG_TOUCH_BOOST_TIMEOUT, + boostTimeOut); + } + @Override public boolean transferHostTouchGestureToEmbedded( @NonNull SurfaceControlViewHost.SurfacePackage surfacePackage) { diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index 046ea77f196d..c7e180732fd4 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -122,7 +122,11 @@ import android.view.WindowInsets.Side.InsetsSide; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.accessibility.AccessibilityNodeInfo; +import android.window.ITrustedPresentationListener; import android.window.TaskFpsCallback; +import android.window.TrustedPresentationThresholds; + +import com.android.window.flags.Flags; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -656,26 +660,35 @@ public interface WindowManager extends ViewManager { /** * Display IME Policy: The IME should appear on the local display. + * * @hide */ - @TestApi + @SuppressLint("UnflaggedApi") // promoting from @TestApi. + @SystemApi int DISPLAY_IME_POLICY_LOCAL = 0; /** - * Display IME Policy: The IME should appear on the fallback display. + * Display IME Policy: The IME should appear on a fallback display. + * + * <p>The fallback display is always {@link Display#DEFAULT_DISPLAY}.</p> + * * @hide */ - @TestApi + @SuppressLint("UnflaggedApi") // promoting from @TestApi. + @SystemApi int DISPLAY_IME_POLICY_FALLBACK_DISPLAY = 1; /** * Display IME Policy: The IME should be hidden. * - * Setting this policy will prevent the IME from making a connection. This - * will prevent any IME from receiving metadata about input. + * <p>Setting this policy will prevent the IME from making a connection. This + * will prevent any IME from receiving metadata about input and this display will effectively + * have no IME.</p> + * * @hide */ - @TestApi + @SuppressLint("UnflaggedApi") // promoting from @TestApi. + @SystemApi int DISPLAY_IME_POLICY_HIDE = 2; /** @@ -3251,6 +3264,13 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC = 1 << 24; /** + * Flag to indicate that the window consumes the insets of {@link Type#ime()}. This makes + * windows below this window unable to receive visible IME insets. + * @hide + */ + public static final int PRIVATE_FLAG_CONSUME_IME_INSETS = 1 << 25; + + /** * Flag to indicate that the window is controlling the appearance of system bars. So we * don't need to adjust it by reading its system UI flags for compatibility. * @hide @@ -3334,6 +3354,7 @@ public interface WindowManager extends ViewManager { PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION, PRIVATE_FLAG_NOT_MAGNIFIABLE, PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, + PRIVATE_FLAG_CONSUME_IME_INSETS, PRIVATE_FLAG_APPEARANCE_CONTROLLED, PRIVATE_FLAG_BEHAVIOR_CONTROLLED, PRIVATE_FLAG_FIT_INSETS_CONTROLLED, @@ -3432,6 +3453,10 @@ public interface WindowManager extends ViewManager { equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC, name = "COLOR_SPACE_AGNOSTIC"), @ViewDebug.FlagToString( + mask = PRIVATE_FLAG_CONSUME_IME_INSETS, + equals = PRIVATE_FLAG_CONSUME_IME_INSETS, + name = "CONSUME_IME_INSETS"), + @ViewDebug.FlagToString( mask = PRIVATE_FLAG_APPEARANCE_CONTROLLED, equals = PRIVATE_FLAG_APPEARANCE_CONTROLLED, name = "APPEARANCE_CONTROLLED"), @@ -3458,7 +3483,7 @@ public interface WindowManager extends ViewManager { @ViewDebug.FlagToString( mask = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, equals = PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY, - name = "PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY") + name = "SYSTEM_APPLICATION_OVERLAY") }) @PrivateFlags @TestApi @@ -5884,4 +5909,34 @@ public interface WindowManager extends ViewManager { default boolean replaceContentOnDisplayWithSc(int displayId, @NonNull SurfaceControl sc) { throw new UnsupportedOperationException(); } + + /** + * Add a trusted presentation listener associated with a window. + * + * <p> If this listener is already registered then the window and thresholds will be updated. + * + * @param window The Window to add the trusted presentation listener for + * @param thresholds The {@link TrustedPresentationThresholds} that will specify + * when the to invoke the callback. + * @param executor The {@link Executor} where the callback will be invoked on. + * @param listener The {@link Consumer} that will receive the callbacks + * when entered or exited trusted presentation per the thresholds. + */ + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + default void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor, + @NonNull Consumer<Boolean> listener) { + throw new UnsupportedOperationException(); + } + + /** + * Removes a presentation listener associated with a window. If the listener was not previously + * registered, the call will be a noop. + * + * @see WindowManager#registerTrustedPresentationListener(IBinder, TrustedPresentationThresholds, Executor, Consumer) + */ + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + default void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + throw new UnsupportedOperationException(); + } } diff --git a/core/java/android/view/WindowManagerGlobal.java b/core/java/android/view/WindowManagerGlobal.java index 214f1ec3d1ec..f1e406196abf 100644 --- a/core/java/android/view/WindowManagerGlobal.java +++ b/core/java/android/view/WindowManagerGlobal.java @@ -30,9 +30,13 @@ import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemProperties; import android.util.AndroidRuntimeException; +import android.util.ArrayMap; import android.util.ArraySet; import android.util.Log; +import android.util.Pair; import android.view.inputmethod.InputMethodManager; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; import com.android.internal.util.FastPrintWriter; @@ -43,6 +47,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.WeakHashMap; import java.util.concurrent.Executor; +import java.util.function.Consumer; import java.util.function.IntConsumer; /** @@ -143,6 +148,9 @@ public final class WindowManagerGlobal { private Runnable mSystemPropertyUpdater; + private final TrustedPresentationListener mTrustedPresentationListener = + new TrustedPresentationListener(); + private WindowManagerGlobal() { } @@ -324,7 +332,7 @@ public final class WindowManagerGlobal { final Context context = view.getContext(); if (context != null && (context.getApplicationInfo().flags - & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { + & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) { wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } @@ -482,7 +490,7 @@ public final class WindowManagerGlobal { if (who != null) { WindowLeaked leak = new WindowLeaked( what + " " + who + " has leaked window " - + root.getView() + " that was originally added here"); + + root.getView() + " that was originally added here"); leak.setStackTrace(root.getLocation().getStackTrace()); Log.e(TAG, "", leak); } @@ -790,6 +798,87 @@ public final class WindowManagerGlobal { } } + public void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, Executor executor, + @NonNull Consumer<Boolean> listener) { + mTrustedPresentationListener.addListener(window, thresholds, listener, executor); + } + + public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + mTrustedPresentationListener.removeListener(listener); + } + + private final class TrustedPresentationListener extends + ITrustedPresentationListener.Stub { + private static int sId = 0; + private final ArrayMap<Consumer<Boolean>, Pair<Integer, Executor>> mListeners = + new ArrayMap<>(); + + private final Object mTplLock = new Object(); + + private void addListener(IBinder window, TrustedPresentationThresholds thresholds, + Consumer<Boolean> listener, Executor executor) { + synchronized (mTplLock) { + if (mListeners.containsKey(listener)) { + Log.i(TAG, "Updating listener " + listener + " thresholds to " + thresholds); + removeListener(listener); + } + int id = sId++; + mListeners.put(listener, new Pair<>(id, executor)); + try { + WindowManagerGlobal.getWindowManagerService() + .registerTrustedPresentationListener(window, this, thresholds, id); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + } + + private void removeListener(Consumer<Boolean> listener) { + synchronized (mTplLock) { + var removedListener = mListeners.remove(listener); + if (removedListener == null) { + Log.i(TAG, "listener " + listener + " does not exist."); + return; + } + + try { + WindowManagerGlobal.getWindowManagerService() + .unregisterTrustedPresentationListener(this, removedListener.first); + } catch (RemoteException e) { + e.rethrowAsRuntimeException(); + } + } + } + + @Override + public void onTrustedPresentationChanged(int[] inTrustedStateListenerIds, + int[] outOfTrustedStateListenerIds) { + ArrayList<Runnable> firedListeners = new ArrayList<>(); + synchronized (mTplLock) { + mListeners.forEach((listener, idExecutorPair) -> { + final var listenerId = idExecutorPair.first; + final var executor = idExecutorPair.second; + for (int id : inTrustedStateListenerIds) { + if (listenerId == id) { + firedListeners.add(() -> executor.execute( + () -> listener.accept(/*presentationState*/true))); + } + } + for (int id : outOfTrustedStateListenerIds) { + if (listenerId == id) { + firedListeners.add(() -> executor.execute( + () -> listener.accept(/*presentationState*/false))); + } + } + }); + } + for (int i = 0; i < firedListeners.size(); i++) { + firedListeners.get(i).run(); + } + } + } + /** @hide */ public void addWindowlessRoot(ViewRootImpl impl) { synchronized (mLock) { @@ -801,7 +890,7 @@ public final class WindowManagerGlobal { public void removeWindowlessRoot(ViewRootImpl impl) { synchronized (mLock) { mWindowlessRoots.remove(impl); - } + } } public void setRecentsAppBehindSystemBars(boolean behindSystemBars) { diff --git a/core/java/android/view/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index d7b74b3bcfe2..b4b1fde89a46 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -37,6 +37,7 @@ import android.os.StrictMode; import android.util.Log; import android.window.ITaskFpsCallback; import android.window.TaskFpsCallback; +import android.window.TrustedPresentationThresholds; import android.window.WindowContext; import android.window.WindowMetricsController; import android.window.WindowProvider; @@ -508,4 +509,17 @@ public final class WindowManagerImpl implements WindowManager { } return false; } + + @Override + public void registerTrustedPresentationListener(@NonNull IBinder window, + @NonNull TrustedPresentationThresholds thresholds, @NonNull Executor executor, + @NonNull Consumer<Boolean> listener) { + mGlobal.registerTrustedPresentationListener(window, thresholds, executor, listener); + } + + @Override + public void unregisterTrustedPresentationListener(@NonNull Consumer<Boolean> listener) { + mGlobal.unregisterTrustedPresentationListener(listener); + + } } diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index fab8c7796dfd..1b7d57b785f5 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -20,8 +20,8 @@ import static android.view.InsetsController.ANIMATION_TYPE_HIDE; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_HIDE_ANIMATION; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_IME_INSETS_SHOW_ANIMATION; +import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_HIDE_ANIMATION; +import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_SHOW_ANIMATION; import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN; import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN; @@ -758,7 +758,7 @@ public interface ImeTracker { * A helper method to translate animation type to CUJ type for IME animations. * * @param animType the animation type. - * @return the integer in {@link com.android.internal.jank.InteractionJankMonitor.CujType}, + * @return the integer in {@link com.android.internal.jank.Cuj.CujType}, * or {@code -1} if the animation type is not supported for tracking yet. */ private static int getImeInsetsCujFromAnimation(@AnimationType int animType) { diff --git a/core/java/android/window/ITrustedPresentationListener.aidl b/core/java/android/window/ITrustedPresentationListener.aidl new file mode 100644 index 000000000000..b33128abb7e5 --- /dev/null +++ b/core/java/android/window/ITrustedPresentationListener.aidl @@ -0,0 +1,24 @@ +/* + * 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.window; + +/** + * @hide + */ +oneway interface ITrustedPresentationListener { + void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds); +}
\ No newline at end of file diff --git a/core/java/android/window/TrustedPresentationListener.java b/core/java/android/window/TrustedPresentationListener.java new file mode 100644 index 000000000000..02fd6d98fb0d --- /dev/null +++ b/core/java/android/window/TrustedPresentationListener.java @@ -0,0 +1,26 @@ +/* + * 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 android.window; + +/** + * @hide + */ +public interface TrustedPresentationListener { + + void onTrustedPresentationChanged(boolean inTrustedPresentationState); + +} diff --git a/core/java/android/window/TrustedPresentationThresholds.aidl b/core/java/android/window/TrustedPresentationThresholds.aidl new file mode 100644 index 000000000000..d7088bf0fddc --- /dev/null +++ b/core/java/android/window/TrustedPresentationThresholds.aidl @@ -0,0 +1,3 @@ +package android.window; + +parcelable TrustedPresentationThresholds; diff --git a/core/java/android/window/TrustedPresentationThresholds.java b/core/java/android/window/TrustedPresentationThresholds.java new file mode 100644 index 000000000000..90f8834b37d1 --- /dev/null +++ b/core/java/android/window/TrustedPresentationThresholds.java @@ -0,0 +1,144 @@ +/* + * 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 android.window; + +import android.annotation.FlaggedApi; +import android.annotation.FloatRange; +import android.annotation.IntRange; +import android.annotation.SuppressLint; +import android.os.Parcel; +import android.os.Parcelable; +import android.view.SurfaceControl; + +import androidx.annotation.NonNull; + +import com.android.window.flags.Flags; + +/** + * Threshold values that are sent with + * {@link android.view.WindowManager#registerTrustedPresentationListener(IBinder, + * TrustedPresentationThresholds, Executor, Consumer)} + */ +@FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) +public final class TrustedPresentationThresholds implements Parcelable { + /** + * The min alpha the {@link SurfaceControl} is required to have to be considered inside the + * threshold. + */ + @FloatRange(from = 0f, fromInclusive = false, to = 1f) + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + @SuppressLint("InternalField") // simple data class + public final float minAlpha; + + /** + * The min fraction of the SurfaceControl that was presented to the user to be considered + * inside the threshold. + */ + @FloatRange(from = 0f, fromInclusive = false, to = 1f) + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + @SuppressLint("InternalField") // simple data class + public final float minFractionRendered; + + /** + * The time in milliseconds required for the {@link SurfaceControl} to be in the threshold. + */ + @IntRange(from = 1) + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + @SuppressLint("InternalField") // simple data class + public final int stabilityRequirementMs; + + private void checkValid() { + if (minAlpha <= 0 || minFractionRendered <= 0 || stabilityRequirementMs < 1) { + throw new IllegalArgumentException( + "TrustedPresentationThresholds values are invalid"); + } + } + + /** + * Creates a new TrustedPresentationThresholds. + * + * @param minAlpha The min alpha the {@link SurfaceControl} is required to + * have to be considered inside the + * threshold. + * @param minFractionRendered The min fraction of the SurfaceControl that was presented + * to the user to be considered + * inside the threshold. + * @param stabilityRequirementMs The time in milliseconds required for the + * {@link SurfaceControl} to be in the threshold. + */ + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public TrustedPresentationThresholds( + @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minAlpha, + @FloatRange(from = 0f, fromInclusive = false, to = 1f) float minFractionRendered, + @IntRange(from = 1) int stabilityRequirementMs) { + this.minAlpha = minAlpha; + this.minFractionRendered = minFractionRendered; + this.stabilityRequirementMs = stabilityRequirementMs; + checkValid(); + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public String toString() { + return "TrustedPresentationThresholds { " + + "minAlpha = " + minAlpha + ", " + + "minFractionRendered = " + minFractionRendered + ", " + + "stabilityRequirementMs = " + stabilityRequirementMs + + " }"; + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeFloat(minAlpha); + dest.writeFloat(minFractionRendered); + dest.writeInt(stabilityRequirementMs); + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public int describeContents() { + return 0; + } + + /** + * @hide + */ + TrustedPresentationThresholds(@NonNull Parcel in) { + minAlpha = in.readFloat(); + minFractionRendered = in.readFloat(); + stabilityRequirementMs = in.readInt(); + + checkValid(); + } + + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public static final @NonNull Creator<TrustedPresentationThresholds> CREATOR = + new Creator<TrustedPresentationThresholds>() { + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public TrustedPresentationThresholds[] newArray(int size) { + return new TrustedPresentationThresholds[size]; + } + + @Override + @FlaggedApi(Flags.FLAG_TRUSTED_PRESENTATION_LISTENER_FOR_WINDOW) + public TrustedPresentationThresholds createFromParcel(@NonNull Parcel in) { + return new TrustedPresentationThresholds(in); + } + }; +} diff --git a/core/java/android/window/flags/window_surfaces.aconfig b/core/java/android/window/flags/window_surfaces.aconfig index 29932f342b74..56df49370379 100644 --- a/core/java/android/window/flags/window_surfaces.aconfig +++ b/core/java/android/window/flags/window_surfaces.aconfig @@ -56,3 +56,11 @@ flag { is_fixed_read_only: true bug: "308662081" } + +flag { + namespace: "window_surfaces" + name: "trusted_presentation_listener_for_window" + description: "Enable trustedPresentationListener on windows public API" + is_fixed_read_only: true + bug: "278027319" +}
\ No newline at end of file diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java new file mode 100644 index 000000000000..f460233f0edd --- /dev/null +++ b/core/java/com/android/internal/jank/Cuj.java @@ -0,0 +1,503 @@ +/* + * 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.internal.jank; + +import android.annotation.IntDef; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.FrameworkStatsLog; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.util.Arrays; + +/** @hide */ +public class Cuj { + @VisibleForTesting + public static final int MAX_LENGTH_OF_CUJ_NAME = 80; + + // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE. + public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0; + public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2; + public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3; + public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4; + public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5; + public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6; + public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7; + public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8; + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9; + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10; + public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11; + public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12; + public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13; + public static final int CUJ_NOTIFICATION_ADD = 14; + public static final int CUJ_NOTIFICATION_REMOVE = 15; + public static final int CUJ_NOTIFICATION_APP_START = 16; + public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17; + public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18; + public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19; + public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20; + public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21; + public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22; + public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23; + public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24; + public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25; + public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26; + public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27; + public static final int CUJ_SETTINGS_PAGE_SCROLL = 28; + public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32; + public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33; + public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; + public static final int CUJ_PIP_TRANSITION = 35; + public static final int CUJ_WALLPAPER_TRANSITION = 36; + public static final int CUJ_USER_SWITCH = 37; + public static final int CUJ_SPLASHSCREEN_AVD = 38; + public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39; + public static final int CUJ_SCREEN_OFF = 40; + public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41; + public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42; + public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43; + public static final int CUJ_UNFOLD_ANIM = 44; + public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45; + public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46; + public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47; + public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48; + public static final int CUJ_SPLIT_SCREEN_ENTER = 49; + public static final int CUJ_SPLIT_SCREEN_EXIT = 50; + public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved. + public static final int CUJ_SPLIT_SCREEN_RESIZE = 52; + public static final int CUJ_SETTINGS_SLIDER = 53; + public static final int CUJ_TAKE_SCREENSHOT = 54; + public static final int CUJ_VOLUME_CONTROL = 55; + public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56; + public static final int CUJ_SETTINGS_TOGGLE = 57; + public static final int CUJ_SHADE_DIALOG_OPEN = 58; + public static final int CUJ_USER_DIALOG_OPEN = 59; + public static final int CUJ_TASKBAR_EXPAND = 60; + public static final int CUJ_TASKBAR_COLLAPSE = 61; + public static final int CUJ_SHADE_CLEAR_ALL = 62; + public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63; + public static final int CUJ_LOCKSCREEN_OCCLUSION = 64; + public static final int CUJ_RECENTS_SCROLLING = 65; + public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66; + public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67; + public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68; + public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70; + public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71; + // 72 - 77 are reserved for b/281564325. + + /** + * In some cases when we do not have any end-target, we play a simple slide-down animation. + * eg: Open an app from Overview/Task switcher such that there is no home-screen icon. + * eg: Exit the app using back gesture. + */ + public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78; + public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80; + public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81; + + public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82; + + public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83; + + public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84; + public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85; + public static final int CUJ_PREDICTIVE_BACK_HOME = 86; + + // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE. + @VisibleForTesting + static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME; + + /** @hide */ + @IntDef({ + CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, + CUJ_NOTIFICATION_SHADE_SCROLL_FLING, + CUJ_NOTIFICATION_SHADE_ROW_EXPAND, + CUJ_NOTIFICATION_SHADE_ROW_SWIPE, + CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, + CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, + CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS, + CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON, + CUJ_LAUNCHER_APP_CLOSE_TO_HOME, + CUJ_LAUNCHER_APP_CLOSE_TO_PIP, + CUJ_LAUNCHER_QUICK_SWITCH, + CUJ_NOTIFICATION_HEADS_UP_APPEAR, + CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, + CUJ_NOTIFICATION_ADD, + CUJ_NOTIFICATION_REMOVE, + CUJ_NOTIFICATION_APP_START, + CUJ_LOCKSCREEN_PASSWORD_APPEAR, + CUJ_LOCKSCREEN_PATTERN_APPEAR, + CUJ_LOCKSCREEN_PIN_APPEAR, + CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR, + CUJ_LOCKSCREEN_PATTERN_DISAPPEAR, + CUJ_LOCKSCREEN_PIN_DISAPPEAR, + CUJ_LOCKSCREEN_TRANSITION_FROM_AOD, + CUJ_LOCKSCREEN_TRANSITION_TO_AOD, + CUJ_LAUNCHER_OPEN_ALL_APPS, + CUJ_LAUNCHER_ALL_APPS_SCROLL, + CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET, + CUJ_SETTINGS_PAGE_SCROLL, + CUJ_LOCKSCREEN_UNLOCK_ANIMATION, + CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON, + CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER, + CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, + CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, + CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, + CUJ_PIP_TRANSITION, + CUJ_WALLPAPER_TRANSITION, + CUJ_USER_SWITCH, + CUJ_SPLASHSCREEN_AVD, + CUJ_SPLASHSCREEN_EXIT_ANIM, + CUJ_SCREEN_OFF, + CUJ_SCREEN_OFF_SHOW_AOD, + CUJ_ONE_HANDED_ENTER_TRANSITION, + CUJ_ONE_HANDED_EXIT_TRANSITION, + CUJ_UNFOLD_ANIM, + CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS, + CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS, + CUJ_SUW_LOADING_TO_NEXT_FLOW, + CUJ_SUW_LOADING_SCREEN_FOR_STATUS, + CUJ_SPLIT_SCREEN_ENTER, + CUJ_SPLIT_SCREEN_EXIT, + CUJ_LOCKSCREEN_LAUNCH_CAMERA, + CUJ_SPLIT_SCREEN_RESIZE, + CUJ_SETTINGS_SLIDER, + CUJ_TAKE_SCREENSHOT, + CUJ_VOLUME_CONTROL, + CUJ_BIOMETRIC_PROMPT_TRANSITION, + CUJ_SETTINGS_TOGGLE, + CUJ_SHADE_DIALOG_OPEN, + CUJ_USER_DIALOG_OPEN, + CUJ_TASKBAR_EXPAND, + CUJ_TASKBAR_COLLAPSE, + CUJ_SHADE_CLEAR_ALL, + CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, + CUJ_LOCKSCREEN_OCCLUSION, + CUJ_RECENTS_SCROLLING, + CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS, + CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE, + CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME, + CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION, + CUJ_LAUNCHER_OPEN_SEARCH_RESULT, + CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK, + CUJ_IME_INSETS_SHOW_ANIMATION, + CUJ_IME_INSETS_HIDE_ANIMATION, + CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER, + CUJ_LAUNCHER_UNFOLD_ANIM, + CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY, + CUJ_PREDICTIVE_BACK_CROSS_TASK, + CUJ_PREDICTIVE_BACK_HOME, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface CujType { + } + + private static final int NO_STATSD_LOGGING = -1; + + // Used to convert CujType to InteractionType enum value for statsd logging. + // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd. + private static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1]; + static { + Arrays.fill(CUJ_TO_STATSD_INTERACTION_TYPE, NO_STATSD_LOGGING); + + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK; + CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME; + } + + private Cuj() { + } + + /** + * A helper method to translate CUJ type to CUJ name. + * + * @param cujType the cuj type defined in this file + * @return the name of the cuj type + */ + public static String getNameOfCuj(int cujType) { + // Please note: + // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME. + // 2. The returned string should be the same with the name defined in atoms.proto. + switch (cujType) { + case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE: + return "NOTIFICATION_SHADE_EXPAND_COLLAPSE"; + case CUJ_NOTIFICATION_SHADE_SCROLL_FLING: + return "NOTIFICATION_SHADE_SCROLL_FLING"; + case CUJ_NOTIFICATION_SHADE_ROW_EXPAND: + return "NOTIFICATION_SHADE_ROW_EXPAND"; + case CUJ_NOTIFICATION_SHADE_ROW_SWIPE: + return "NOTIFICATION_SHADE_ROW_SWIPE"; + case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE: + return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE"; + case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE: + return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE"; + case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS: + return "LAUNCHER_APP_LAUNCH_FROM_RECENTS"; + case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON: + return "LAUNCHER_APP_LAUNCH_FROM_ICON"; + case CUJ_LAUNCHER_APP_CLOSE_TO_HOME: + return "LAUNCHER_APP_CLOSE_TO_HOME"; + case CUJ_LAUNCHER_APP_CLOSE_TO_PIP: + return "LAUNCHER_APP_CLOSE_TO_PIP"; + case CUJ_LAUNCHER_QUICK_SWITCH: + return "LAUNCHER_QUICK_SWITCH"; + case CUJ_NOTIFICATION_HEADS_UP_APPEAR: + return "NOTIFICATION_HEADS_UP_APPEAR"; + case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR: + return "NOTIFICATION_HEADS_UP_DISAPPEAR"; + case CUJ_NOTIFICATION_ADD: + return "NOTIFICATION_ADD"; + case CUJ_NOTIFICATION_REMOVE: + return "NOTIFICATION_REMOVE"; + case CUJ_NOTIFICATION_APP_START: + return "NOTIFICATION_APP_START"; + case CUJ_LOCKSCREEN_PASSWORD_APPEAR: + return "LOCKSCREEN_PASSWORD_APPEAR"; + case CUJ_LOCKSCREEN_PATTERN_APPEAR: + return "LOCKSCREEN_PATTERN_APPEAR"; + case CUJ_LOCKSCREEN_PIN_APPEAR: + return "LOCKSCREEN_PIN_APPEAR"; + case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR: + return "LOCKSCREEN_PASSWORD_DISAPPEAR"; + case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR: + return "LOCKSCREEN_PATTERN_DISAPPEAR"; + case CUJ_LOCKSCREEN_PIN_DISAPPEAR: + return "LOCKSCREEN_PIN_DISAPPEAR"; + case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD: + return "LOCKSCREEN_TRANSITION_FROM_AOD"; + case CUJ_LOCKSCREEN_TRANSITION_TO_AOD: + return "LOCKSCREEN_TRANSITION_TO_AOD"; + case CUJ_LAUNCHER_OPEN_ALL_APPS : + return "LAUNCHER_OPEN_ALL_APPS"; + case CUJ_LAUNCHER_ALL_APPS_SCROLL: + return "LAUNCHER_ALL_APPS_SCROLL"; + case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET: + return "LAUNCHER_APP_LAUNCH_FROM_WIDGET"; + case CUJ_SETTINGS_PAGE_SCROLL: + return "SETTINGS_PAGE_SCROLL"; + case CUJ_LOCKSCREEN_UNLOCK_ANIMATION: + return "LOCKSCREEN_UNLOCK_ANIMATION"; + case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON: + return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON"; + case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER: + return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER"; + case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE: + return "SHADE_APP_LAUNCH_FROM_QS_TILE"; + case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON: + return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON"; + case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP: + return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP"; + case CUJ_PIP_TRANSITION: + return "PIP_TRANSITION"; + case CUJ_WALLPAPER_TRANSITION: + return "WALLPAPER_TRANSITION"; + case CUJ_USER_SWITCH: + return "USER_SWITCH"; + case CUJ_SPLASHSCREEN_AVD: + return "SPLASHSCREEN_AVD"; + case CUJ_SPLASHSCREEN_EXIT_ANIM: + return "SPLASHSCREEN_EXIT_ANIM"; + case CUJ_SCREEN_OFF: + return "SCREEN_OFF"; + case CUJ_SCREEN_OFF_SHOW_AOD: + return "SCREEN_OFF_SHOW_AOD"; + case CUJ_ONE_HANDED_ENTER_TRANSITION: + return "ONE_HANDED_ENTER_TRANSITION"; + case CUJ_ONE_HANDED_EXIT_TRANSITION: + return "ONE_HANDED_EXIT_TRANSITION"; + case CUJ_UNFOLD_ANIM: + return "UNFOLD_ANIM"; + case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS: + return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS"; + case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS: + return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS"; + case CUJ_SUW_LOADING_TO_NEXT_FLOW: + return "SUW_LOADING_TO_NEXT_FLOW"; + case CUJ_SUW_LOADING_SCREEN_FOR_STATUS: + return "SUW_LOADING_SCREEN_FOR_STATUS"; + case CUJ_SPLIT_SCREEN_ENTER: + return "SPLIT_SCREEN_ENTER"; + case CUJ_SPLIT_SCREEN_EXIT: + return "SPLIT_SCREEN_EXIT"; + case CUJ_LOCKSCREEN_LAUNCH_CAMERA: + return "LOCKSCREEN_LAUNCH_CAMERA"; + case CUJ_SPLIT_SCREEN_RESIZE: + return "SPLIT_SCREEN_RESIZE"; + case CUJ_SETTINGS_SLIDER: + return "SETTINGS_SLIDER"; + case CUJ_TAKE_SCREENSHOT: + return "TAKE_SCREENSHOT"; + case CUJ_VOLUME_CONTROL: + return "VOLUME_CONTROL"; + case CUJ_BIOMETRIC_PROMPT_TRANSITION: + return "BIOMETRIC_PROMPT_TRANSITION"; + case CUJ_SETTINGS_TOGGLE: + return "SETTINGS_TOGGLE"; + case CUJ_SHADE_DIALOG_OPEN: + return "SHADE_DIALOG_OPEN"; + case CUJ_USER_DIALOG_OPEN: + return "USER_DIALOG_OPEN"; + case CUJ_TASKBAR_EXPAND: + return "TASKBAR_EXPAND"; + case CUJ_TASKBAR_COLLAPSE: + return "TASKBAR_COLLAPSE"; + case CUJ_SHADE_CLEAR_ALL: + return "SHADE_CLEAR_ALL"; + case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION: + return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION"; + case CUJ_LOCKSCREEN_OCCLUSION: + return "LOCKSCREEN_OCCLUSION"; + case CUJ_RECENTS_SCROLLING: + return "RECENTS_SCROLLING"; + case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS: + return "LAUNCHER_APP_SWIPE_TO_RECENTS"; + case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE: + return "LAUNCHER_CLOSE_ALL_APPS_SWIPE"; + case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME: + return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME"; + case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION: + return "LOCKSCREEN_CLOCK_MOVE_ANIMATION"; + case CUJ_LAUNCHER_OPEN_SEARCH_RESULT: + return "LAUNCHER_OPEN_SEARCH_RESULT"; + case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK: + return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK"; + case CUJ_IME_INSETS_SHOW_ANIMATION: + return "IME_INSETS_SHOW_ANIMATION"; + case CUJ_IME_INSETS_HIDE_ANIMATION: + return "IME_INSETS_HIDE_ANIMATION"; + case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER: + return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER"; + case CUJ_LAUNCHER_UNFOLD_ANIM: + return "LAUNCHER_UNFOLD_ANIM"; + case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY: + return "PREDICTIVE_BACK_CROSS_ACTIVITY"; + case CUJ_PREDICTIVE_BACK_CROSS_TASK: + return "PREDICTIVE_BACK_CROSS_TASK"; + case CUJ_PREDICTIVE_BACK_HOME: + return "PREDICTIVE_BACK_HOME"; + } + return "UNKNOWN"; + } + + public static int getStatsdInteractionType(@CujType int cujType) { + return CUJ_TO_STATSD_INTERACTION_TYPE[cujType]; + } + + /** Returns whether the measurements for the given CUJ should be written to statsd. */ + public static boolean logToStatsd(@CujType int cujType) { + return getStatsdInteractionType(cujType) != NO_STATSD_LOGGING; + } + + /** + * A helper method to translate interaction type to CUJ name. + * + * @param interactionType the interaction type defined in AtomsProto.java + * @return the name of the interaction type + */ + public static String getNameOfInteraction(int interactionType) { + // There is an offset amount of 1 between cujType and interactionType. + return Cuj.getNameOfCuj(getCujTypeFromInteraction(interactionType)); + } + + /** + * A helper method to translate interaction type to CUJ type. + * + * @param interactionType the interaction type defined in AtomsProto.java + * @return the integer in {@link Cuj.CujType} + */ + private static int getCujTypeFromInteraction(int interactionType) { + return interactionType - 1; + } +} diff --git a/core/java/com/android/internal/jank/FrameTracker.java b/core/java/com/android/internal/jank/FrameTracker.java index c83452d88290..86729f74cd85 100644 --- a/core/java/com/android/internal/jank/FrameTracker.java +++ b/core/java/com/android/internal/jank/FrameTracker.java @@ -53,7 +53,6 @@ import android.view.WindowCallbacks; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.jank.DisplayRefreshRate.RefreshRate; import com.android.internal.jank.InteractionJankMonitor.Configuration; -import com.android.internal.jank.InteractionJankMonitor.Session; import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; @@ -67,7 +66,6 @@ import java.util.concurrent.TimeUnit; public class FrameTracker extends SurfaceControl.OnJankDataListener implements HardwareRendererObserver.OnFrameMetricsAvailableListener { private static final String TAG = "FrameTracker"; - private static final boolean DEBUG = false; private static final long INVALID_ID = -1; public static final int NANOS_IN_MILLISECOND = 1_000_000; @@ -93,20 +91,19 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener REASON_CANCEL_NORMAL, REASON_CANCEL_NOT_BEGUN, REASON_CANCEL_SAME_VSYNC, + REASON_CANCEL_TIMEOUT, }) @Retention(RetentionPolicy.SOURCE) public @interface Reasons { } - @VisibleForTesting - public final InteractionJankMonitor mMonitor; private final HardwareRendererObserver mObserver; private final int mTraceThresholdMissedFrames; private final int mTraceThresholdFrameTimeMillis; private final ThreadedRendererWrapper mRendererWrapper; private final FrameMetricsWrapper mMetricsWrapper; private final SparseArray<JankInfo> mJankInfos = new SparseArray<>(); - private final Session mSession; + private final Configuration mConfig; private final ViewRootWrapper mViewRoot; private final SurfaceControlWrapper mSurfaceControlWrapper; private final int mDisplayId; @@ -197,19 +194,18 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } } - public FrameTracker(@NonNull InteractionJankMonitor monitor, @NonNull Session session, - @NonNull Handler handler, @Nullable ThreadedRendererWrapper renderer, + public FrameTracker(@NonNull Configuration config, + @Nullable ThreadedRendererWrapper renderer, @Nullable ViewRootWrapper viewRootWrapper, @NonNull SurfaceControlWrapper surfaceControlWrapper, @NonNull ChoreographerWrapper choreographer, @Nullable FrameMetricsWrapper metrics, @NonNull StatsLogWrapper statsLog, int traceThresholdMissedFrames, int traceThresholdFrameTimeMillis, - @Nullable FrameTrackerListener listener, @NonNull Configuration config) { - mMonitor = monitor; + @Nullable FrameTrackerListener listener) { mSurfaceOnly = config.isSurfaceOnly(); - mSession = session; - mHandler = handler; + mConfig = config; + mHandler = config.getHandler(); mChoreographer = choreographer; mSurfaceControlWrapper = surfaceControlWrapper; mStatsLog = statsLog; @@ -222,7 +218,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mObserver = mSurfaceOnly ? null : new HardwareRendererObserver(this, mMetricsWrapper.getTiming(), - handler, /* waitForPresentTime= */ false); + mHandler, /* waitForPresentTime= */ false); mTraceThresholdMissedFrames = traceThresholdMissedFrames; mTraceThresholdFrameTimeMillis = traceThresholdFrameTimeMillis; @@ -242,7 +238,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mSurfaceChangedCallback = new ViewRootImpl.SurfaceChangedCallback() { @Override public void surfaceCreated(SurfaceControl.Transaction t) { - getHandler().runWithScissors(() -> { + mHandler.runWithScissors(() -> { if (mSurfaceControl == null) { mSurfaceControl = mViewRoot.getSurfaceControl(); if (mBeginVsyncId != INVALID_ID) { @@ -262,13 +258,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener // Wait a while to give the system a chance for the remaining // frames to arrive, then force finish the session. - getHandler().postDelayed(() -> { - if (DEBUG) { - Log.d(TAG, "surfaceDestroyed: " + mSession.getName() - + ", finalized=" + mMetricsFinalized - + ", info=" + mJankInfos.size() - + ", vsync=" + mBeginVsyncId); - } + mHandler.postDelayed(() -> { if (!mMetricsFinalized) { end(REASON_END_SURFACE_DESTROYED); finish(); @@ -282,11 +272,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } } - @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) - public Handler getHandler() { - return mHandler; - } - /** * Begin a trace session of the CUJ. */ @@ -300,10 +285,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mBeginVsyncId = mDeferMonitoring ? currentVsync + 1 : currentVsync; } if (mSurfaceControl != null) { - if (DEBUG) { - Log.d(TAG, "begin: " + mSession.getName() + ", begin=" + mBeginVsyncId - + ", defer=" + mDeferMonitoring + ", current=" + currentVsync); - } if (mDeferMonitoring && currentVsync < mBeginVsyncId) { markEvent("FT#deferMonitoring", 0); // Normal case, we begin the instrument from the very beginning, @@ -314,11 +295,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener // there is no need to skip the frame where the begin invocation happens. beginInternal(); } - } else { - if (DEBUG) { - Log.d(TAG, "begin: defer beginning since the surface is not ready for CUJ=" - + mSession.getName()); - } } } @@ -336,8 +312,8 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener return; } mTracingStarted = true; - Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, mSession.getName(), mSession.getName(), - (int) mBeginVsyncId); + String name = mConfig.getSessionName(); + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, name, name, (int) mBeginVsyncId); markEvent("FT#beginVsync", mBeginVsyncId); markEvent("FT#layerId", mSurfaceControl.getLayerId()); mSurfaceControlWrapper.addJankStatsListener(this, mSurfaceControl); @@ -361,15 +337,10 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } else if (mEndVsyncId <= mBeginVsyncId) { return cancel(REASON_CANCEL_SAME_VSYNC); } else { - if (DEBUG) { - Log.d(TAG, "end: " + mSession.getName() - + ", end=" + mEndVsyncId + ", reason=" + reason); - } + final String name = mConfig.getSessionName(); markEvent("FT#end", reason); markEvent("FT#endVsync", mEndVsyncId); - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(), - (int) mBeginVsyncId); - mSession.setReason(reason); + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, name, (int) mBeginVsyncId); // We don't remove observer here, // will remove it when all the frame metrics in this duration are called back. @@ -395,16 +366,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener mFlushAttempts++; } else { mWaitForFinishTimedOut = () -> { - Log.e(TAG, "force finish cuj, time out: " + mSession.getName()); + Log.e(TAG, "force finish cuj, time out: " + name); finish(); }; delay = TimeUnit.SECONDS.toMillis(10); } - getHandler().postDelayed(mWaitForFinishTimedOut, delay); + mHandler.postDelayed(mWaitForFinishTimedOut, delay); } }; - getHandler().postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND); - notifyCujEvent(ACTION_SESSION_END); + mHandler.postDelayed(mWaitForFinishTimedOut, FLUSH_DELAY_MILLISECOND); + notifyCujEvent(ACTION_SESSION_END, reason); return true; } } @@ -421,22 +392,16 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener markEvent("FT#cancel", reason); // We don't need to end the trace section if it has never begun. if (mTracingStarted) { - Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_APP, mSession.getName(), - (int) mBeginVsyncId); + Trace.asyncTraceForTrackEnd( + Trace.TRACE_TAG_APP, mConfig.getSessionName(), (int) mBeginVsyncId); } // Always remove the observers in cancel call to avoid leakage. removeObservers(); - if (DEBUG) { - Log.d(TAG, "cancel: " + mSession.getName() + ", begin=" + mBeginVsyncId - + ", end=" + mEndVsyncId + ", reason=" + reason); - } - - mSession.setReason(reason); // Notify the listener the session has been cancelled. // We don't notify the listeners if the session never begun. - notifyCujEvent(ACTION_SESSION_CANCEL); + notifyCujEvent(ACTION_SESSION_CANCEL, reason); return true; } @@ -455,13 +420,13 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener "The length of the trace event description <%s> exceeds %d", event, MAX_LENGTH_EVENT_DESC)); } - Trace.instantForTrack(Trace.TRACE_TAG_APP, mSession.getName(), event); + Trace.instantForTrack(Trace.TRACE_TAG_APP, mConfig.getSessionName(), event); } } - private void notifyCujEvent(String action) { + private void notifyCujEvent(String action, @Reasons int reason) { if (mListener == null) return; - mListener.onCujEvents(mSession, action); + mListener.onCujEvents(this, action, reason); } @Override @@ -496,7 +461,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener */ @VisibleForTesting public void postCallback(Runnable callback) { - getHandler().post(callback); + mHandler.post(callback); } @Nullable @@ -587,13 +552,15 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (mMetricsFinalized || mCancelled) return; mMetricsFinalized = true; - getHandler().removeCallbacks(mWaitForFinishTimedOut); + mHandler.removeCallbacks(mWaitForFinishTimedOut); mWaitForFinishTimedOut = null; markEvent("FT#finish", mJankInfos.size()); // The tracing has been ended, remove the observer, see if need to trigger perfetto. removeObservers(); + final String name = mConfig.getSessionName(); + int totalFramesCount = 0; long maxFrameTimeNanos = 0; int missedFramesCount = 0; @@ -616,7 +583,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener totalFramesCount++; boolean missedFrame = false; if ((info.jankType & JANK_APP_DEADLINE_MISSED) != 0) { - Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + mSession.getName()); + Log.w(TAG, "Missed App frame:" + info + ", CUJ=" + name); missedAppFramesCount++; missedFrame = true; } @@ -625,7 +592,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener || (info.jankType & JANK_SURFACEFLINGER_GPU_DEADLINE_MISSED) != 0 || (info.jankType & SURFACE_FLINGER_SCHEDULING) != 0 || (info.jankType & PREDICTION_ERROR) != 0) { - Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + mSession.getName()); + Log.w(TAG, "Missed SF frame:" + info + ", CUJ=" + name); missedSfFramesCount++; missedFrame = true; } @@ -646,7 +613,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (!mSurfaceOnly && !info.hwuiCallbackFired) { markEvent("FT#MissedHWUICallback", info.frameVsyncId); Log.w(TAG, "Missing HWUI jank callback for vsyncId: " + info.frameVsyncId - + ", CUJ=" + mSession.getName()); + + ", CUJ=" + name); } } if (!mSurfaceOnly && info.hwuiCallbackFired) { @@ -654,7 +621,7 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener if (!info.surfaceControlCallbackFired) { markEvent("FT#MissedSFCallback", info.frameVsyncId); Log.w(TAG, "Missing SF jank callback for vsyncId: " + info.frameVsyncId - + ", CUJ=" + mSession.getName()); + + ", CUJ=" + name); } } } @@ -662,29 +629,26 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener maxSuccessiveMissedFramesCount, successiveMissedFramesCount); // Log the frame stats as counters to make them easily accessible in traces. - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedFrames", - missedFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedAppFrames", - missedAppFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#missedSfFrames", - missedSfFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#totalFrames", - totalFramesCount); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxFrameTimeMillis", + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedFrames", missedFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedAppFrames", missedAppFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#missedSfFrames", missedSfFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#totalFrames", totalFramesCount); + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxFrameTimeMillis", (int) (maxFrameTimeNanos / NANOS_IN_MILLISECOND)); - Trace.traceCounter(Trace.TRACE_TAG_APP, mSession.getName() + "#maxSuccessiveMissedFrames", + Trace.traceCounter(Trace.TRACE_TAG_APP, name + "#maxSuccessiveMissedFrames", maxSuccessiveMissedFramesCount); // Trigger perfetto if necessary. - if (shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) { - triggerPerfetto(); + if (mListener != null + && shouldTriggerPerfetto(missedFramesCount, (int) maxFrameTimeNanos)) { + mListener.triggerPerfetto(mConfig); } - if (mSession.logToStatsd()) { + if (mConfig.logToStatsd()) { mStatsLog.write( FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED, mDisplayId, refreshRate, - mSession.getStatsdInteractionType(), + mConfig.getStatsdInteractionType(), totalFramesCount, missedFramesCount, maxFrameTimeNanos, /* will be 0 if mSurfaceOnly == true */ @@ -692,16 +656,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener missedAppFramesCount, maxSuccessiveMissedFramesCount); } - if (DEBUG) { - Log.i(TAG, "finish: CUJ=" + mSession.getName() - + " (" + mBeginVsyncId + "," + mEndVsyncId + ")" - + " totalFrames=" + totalFramesCount - + " missedAppFrames=" + missedAppFramesCount - + " missedSfFrames=" + missedSfFramesCount - + " missedFrames=" + missedFramesCount - + " maxFrameTimeMillis=" + maxFrameTimeNanos / NANOS_IN_MILLISECOND - + " maxSuccessiveMissedFramesCount=" + maxSuccessiveMissedFramesCount); - } } ThreadedRendererWrapper getThreadedRenderer() { @@ -737,13 +691,6 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener } /** - * Trigger the prefetto daemon. - */ - public void triggerPerfetto() { - mMonitor.trigger(mSession); - } - - /** * A wrapper class that we can spy FrameMetrics (a final class) in unit tests. */ public static class FrameMetricsWrapper { @@ -895,9 +842,17 @@ public class FrameTracker extends SurfaceControl.OnJankDataListener /** * Notify that the CUJ session was created. * - * @param session the CUJ session + * @param tracker the tracker * @param action the specific action + * @param reason the reason for the action + */ + void onCujEvents(FrameTracker tracker, String action, @Reasons int reason); + + /** + * Notify that the Perfetto trace should be triggered. + * + * @param config the tracker configuration */ - void onCujEvents(Session session, String action); + void triggerPerfetto(Configuration config); } } diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java index e6b036cfaa19..8b1879f5225d 100644 --- a/core/java/com/android/internal/jank/InteractionJankMonitor.java +++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java @@ -23,89 +23,9 @@ import static android.provider.DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_NORMAL; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; -import static com.android.internal.jank.FrameTracker.REASON_END_UNKNOWN; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL; -import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; import android.Manifest; import android.annotation.ColorInt; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.UiThread; @@ -137,34 +57,31 @@ import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.util.PerfettoTrigger; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.time.Instant; -import java.util.Locale; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.function.Supplier; /** - * This class let users to begin and end the always on tracing mechanism. + * This class lets users begin and end the always on tracing mechanism. * * Enabling for local development: - * + *<pre> * adb shell device_config put interaction_jank_monitor enabled true * adb shell device_config put interaction_jank_monitor sampling_interval 1 - * + * </pre> * On debuggable builds, an overlay can be used to display the name of the * currently running cuj using: - * + * <pre> * adb shell device_config put interaction_jank_monitor debug_overlay_enabled true - * - * NOTE: The overlay will interfere with metrics, so it should only be used - * for understanding which UI events correspeond to which CUJs. + * </pre> + * <b>NOTE</b>: The overlay will interfere with metrics, so it should only be used + * for understanding which UI events correspond to which CUJs. * * @hide */ public class InteractionJankMonitor { private static final String TAG = InteractionJankMonitor.class.getSimpleName(); - private static final boolean DEBUG = false; private static final String ACTION_PREFIX = InteractionJankMonitor.class.getCanonicalName(); private static final String DEFAULT_WORKER_NAME = TAG + "-Worker"; @@ -186,218 +103,79 @@ public class InteractionJankMonitor { private static final int DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS = 64; private static final boolean DEFAULT_DEBUG_OVERLAY_ENABLED = false; - @VisibleForTesting - public static final int MAX_LENGTH_OF_CUJ_NAME = 80; private static final int MAX_LENGTH_SESSION_NAME = 100; public static final String ACTION_SESSION_END = ACTION_PREFIX + ".ACTION_SESSION_END"; public static final String ACTION_SESSION_CANCEL = ACTION_PREFIX + ".ACTION_SESSION_CANCEL"; - // Every value must have a corresponding entry in CUJ_STATSD_INTERACTION_TYPE. - public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = 0; - public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = 2; - public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = 3; - public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = 4; - public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = 5; - public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = 6; - public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS = 7; - public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON = 8; - public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = 9; - public static final int CUJ_LAUNCHER_APP_CLOSE_TO_PIP = 10; - public static final int CUJ_LAUNCHER_QUICK_SWITCH = 11; - public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = 12; - public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = 13; - public static final int CUJ_NOTIFICATION_ADD = 14; - public static final int CUJ_NOTIFICATION_REMOVE = 15; - public static final int CUJ_NOTIFICATION_APP_START = 16; - public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = 17; - public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = 18; - public static final int CUJ_LOCKSCREEN_PIN_APPEAR = 19; - public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = 20; - public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = 21; - public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = 22; - public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = 23; - public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = 24; - public static final int CUJ_LAUNCHER_OPEN_ALL_APPS = 25; - public static final int CUJ_LAUNCHER_ALL_APPS_SCROLL = 26; - public static final int CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET = 27; - public static final int CUJ_SETTINGS_PAGE_SCROLL = 28; - public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = 29; - public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = 30; - public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = 31; - public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = 32; - public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = 33; - public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = 34; - public static final int CUJ_PIP_TRANSITION = 35; - public static final int CUJ_WALLPAPER_TRANSITION = 36; - public static final int CUJ_USER_SWITCH = 37; - public static final int CUJ_SPLASHSCREEN_AVD = 38; - public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = 39; - public static final int CUJ_SCREEN_OFF = 40; - public static final int CUJ_SCREEN_OFF_SHOW_AOD = 41; - public static final int CUJ_ONE_HANDED_ENTER_TRANSITION = 42; - public static final int CUJ_ONE_HANDED_EXIT_TRANSITION = 43; - public static final int CUJ_UNFOLD_ANIM = 44; - public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = 45; - public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = 46; - public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = 47; - public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = 48; - public static final int CUJ_SPLIT_SCREEN_ENTER = 49; - public static final int CUJ_SPLIT_SCREEN_EXIT = 50; - public static final int CUJ_LOCKSCREEN_LAUNCH_CAMERA = 51; // reserved. - public static final int CUJ_SPLIT_SCREEN_RESIZE = 52; - public static final int CUJ_SETTINGS_SLIDER = 53; - public static final int CUJ_TAKE_SCREENSHOT = 54; - public static final int CUJ_VOLUME_CONTROL = 55; - public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = 56; - public static final int CUJ_SETTINGS_TOGGLE = 57; - public static final int CUJ_SHADE_DIALOG_OPEN = 58; - public static final int CUJ_USER_DIALOG_OPEN = 59; - public static final int CUJ_TASKBAR_EXPAND = 60; - public static final int CUJ_TASKBAR_COLLAPSE = 61; - public static final int CUJ_SHADE_CLEAR_ALL = 62; - public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = 63; - public static final int CUJ_LOCKSCREEN_OCCLUSION = 64; - public static final int CUJ_RECENTS_SCROLLING = 65; - public static final int CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS = 66; - public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE = 67; - public static final int CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME = 68; - public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = 70; - public static final int CUJ_LAUNCHER_OPEN_SEARCH_RESULT = 71; - // 72 - 77 are reserved for b/281564325. - - /** - * In some cases when we do not have any end-target, we play a simple slide-down animation. - * eg: Open an app from Overview/Task switcher such that there is no home-screen icon. - * eg: Exit the app using back gesture. - */ - public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78; - public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80; - public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81; - - public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = 82; - - public static final int CUJ_LAUNCHER_UNFOLD_ANIM = 83; - - public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84; - public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85; - public static final int CUJ_PREDICTIVE_BACK_HOME = 86; - - private static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME; - private static final int NO_STATSD_LOGGING = -1; - - // Used to convert CujType to InteractionType enum value for statsd logging. - // Use NO_STATSD_LOGGING in case the measurement for a given CUJ should not be logged to statsd. - @VisibleForTesting - public static final int[] CUJ_TO_STATSD_INTERACTION_TYPE = new int[LAST_CUJ + 1]; - - static { - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__NOTIFICATION_SHADE_SWIPE; - CUJ_TO_STATSD_INTERACTION_TYPE[1] = NO_STATSD_LOGGING; // This is deprecated. - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_SCROLL_FLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_SCROLL_FLING; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_EXPAND; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_ROW_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_ROW_SWIPE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_EXPAND_COLLAPSE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_QS_SCROLL_SWIPE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_RECENTS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_ICON; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_PIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_PIP; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_QUICK_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_QUICK_SWITCH; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_ADD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_REMOVE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_REMOVE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_APP_START] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_APPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_APPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_APPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_APPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PASSWORD_DISAPPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PATTERN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PATTERN_DISAPPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_PIN_DISAPPEAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_PIN_DISAPPEAR; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_FROM_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_FROM_AOD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_TRANSITION_TO_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_TRANSITION_TO_AOD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_ALL_APPS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_ALL_APPS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_ALL_APPS_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_ALL_APPS_SCROLL; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_LAUNCH_FROM_WIDGET; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_PAGE_SCROLL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_PAGE_SCROLL; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_UNLOCK_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_UNLOCK_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_QS_TILE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PIP_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PIP_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__WALLPAPER_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_SWITCH] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_SWITCH; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_AVD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_AVD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLASHSCREEN_EXIT_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLASHSCREEN_EXIT_ANIM; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SCREEN_OFF_SHOW_AOD] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SCREEN_OFF_SHOW_AOD; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_ENTER_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_ENTER_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_ONE_HANDED_EXIT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__ONE_HANDED_EXIT_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__UNFOLD_ANIM; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_TO_NEXT_FLOW] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_TO_NEXT_FLOW; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SUW_LOADING_SCREEN_FOR_STATUS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SUW_LOADING_SCREEN_FOR_STATUS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_ENTER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_ENTER; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_EXIT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_EXIT; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_LAUNCH_CAMERA] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_LAUNCH_CAMERA; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_RESIZE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_RESIZE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_SLIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_SLIDER; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TAKE_SCREENSHOT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TAKE_SCREENSHOT; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_VOLUME_CONTROL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__VOLUME_CONTROL; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_BIOMETRIC_PROMPT_TRANSITION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__BIOMETRIC_PROMPT_TRANSITION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SETTINGS_TOGGLE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SETTINGS_TOGGLE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_USER_DIALOG_OPEN] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__USER_DIALOG_OPEN; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_EXPAND] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_EXPAND; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_TASKBAR_COLLAPSE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__TASKBAR_COLLAPSE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_CLEAR_ALL] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_OCCLUSION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_OCCLUSION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_RECENTS_SCROLLING] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__RECENTS_SCROLLING; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_SWIPE_TO_RECENTS; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_SWIPE; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_CLOSE_ALL_APPS_TO_HOME; - CUJ_TO_STATSD_INTERACTION_TYPE[69] = NO_STATSD_LOGGING; // This is deprecated. - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LOCKSCREEN_CLOCK_MOVE_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_OPEN_SEARCH_RESULT] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_OPEN_SEARCH_RESULT; - // 72 - 77 are reserved for b/281564325. - CUJ_TO_STATSD_INTERACTION_TYPE[72] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[73] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[74] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[75] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; - CUJ_TO_STATSD_INTERACTION_TYPE[79] = NO_STATSD_LOGGING; // This is deprecated. - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_UNFOLD_ANIM] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_UNFOLD_ANIM; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = - UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = - UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK; - CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = - UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME; - } + // These are not the CUJ constants you are looking for. These constants simply forward their + // definition from {@link Cuj}. They are here only as a transition measure until all references + // have been updated to the new location. + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_SCROLL_FLING = Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_EXPAND = Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_ROW_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE = Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; + @Deprecated public static final int CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE = Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE; + @Deprecated public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME; + @Deprecated public static final int CUJ_LAUNCHER_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH; + @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_APPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR; + @Deprecated public static final int CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR = Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR; + @Deprecated public static final int CUJ_NOTIFICATION_ADD = Cuj.CUJ_NOTIFICATION_ADD; + @Deprecated public static final int CUJ_NOTIFICATION_REMOVE = Cuj.CUJ_NOTIFICATION_REMOVE; + @Deprecated public static final int CUJ_NOTIFICATION_APP_START = Cuj.CUJ_NOTIFICATION_APP_START; + @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_APPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_APPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_APPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_APPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PIN_APPEAR = Cuj.CUJ_LOCKSCREEN_PIN_APPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PATTERN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PATTERN_DISAPPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_PIN_DISAPPEAR = Cuj.CUJ_LOCKSCREEN_PIN_DISAPPEAR; + @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_FROM_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD; + @Deprecated public static final int CUJ_LOCKSCREEN_TRANSITION_TO_AOD = Cuj.CUJ_LOCKSCREEN_TRANSITION_TO_AOD; + @Deprecated public static final int CUJ_SETTINGS_PAGE_SCROLL = Cuj.CUJ_SETTINGS_PAGE_SCROLL; + @Deprecated public static final int CUJ_LOCKSCREEN_UNLOCK_ANIMATION = Cuj.CUJ_LOCKSCREEN_UNLOCK_ANIMATION; + @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON; + @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER; + @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE; + @Deprecated public static final int CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON = Cuj.CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON; + @Deprecated public static final int CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP = Cuj.CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP; + @Deprecated public static final int CUJ_PIP_TRANSITION = Cuj.CUJ_PIP_TRANSITION; + @Deprecated public static final int CUJ_USER_SWITCH = Cuj.CUJ_USER_SWITCH; + @Deprecated public static final int CUJ_SPLASHSCREEN_AVD = Cuj.CUJ_SPLASHSCREEN_AVD; + @Deprecated public static final int CUJ_SPLASHSCREEN_EXIT_ANIM = Cuj.CUJ_SPLASHSCREEN_EXIT_ANIM; + @Deprecated public static final int CUJ_SCREEN_OFF = Cuj.CUJ_SCREEN_OFF; + @Deprecated public static final int CUJ_SCREEN_OFF_SHOW_AOD = Cuj.CUJ_SCREEN_OFF_SHOW_AOD; + @Deprecated public static final int CUJ_UNFOLD_ANIM = Cuj.CUJ_UNFOLD_ANIM; + @Deprecated public static final int CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS = Cuj.CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS; + @Deprecated public static final int CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS = Cuj.CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS; + @Deprecated public static final int CUJ_SUW_LOADING_TO_NEXT_FLOW = Cuj.CUJ_SUW_LOADING_TO_NEXT_FLOW; + @Deprecated public static final int CUJ_SUW_LOADING_SCREEN_FOR_STATUS = Cuj.CUJ_SUW_LOADING_SCREEN_FOR_STATUS; + @Deprecated public static final int CUJ_SPLIT_SCREEN_RESIZE = Cuj.CUJ_SPLIT_SCREEN_RESIZE; + @Deprecated public static final int CUJ_SETTINGS_SLIDER = Cuj.CUJ_SETTINGS_SLIDER; + @Deprecated public static final int CUJ_TAKE_SCREENSHOT = Cuj.CUJ_TAKE_SCREENSHOT; + @Deprecated public static final int CUJ_VOLUME_CONTROL = Cuj.CUJ_VOLUME_CONTROL; + @Deprecated public static final int CUJ_BIOMETRIC_PROMPT_TRANSITION = Cuj.CUJ_BIOMETRIC_PROMPT_TRANSITION; + @Deprecated public static final int CUJ_SETTINGS_TOGGLE = Cuj.CUJ_SETTINGS_TOGGLE; + @Deprecated public static final int CUJ_SHADE_DIALOG_OPEN = Cuj.CUJ_SHADE_DIALOG_OPEN; + @Deprecated public static final int CUJ_USER_DIALOG_OPEN = Cuj.CUJ_USER_DIALOG_OPEN; + @Deprecated public static final int CUJ_TASKBAR_EXPAND = Cuj.CUJ_TASKBAR_EXPAND; + @Deprecated public static final int CUJ_TASKBAR_COLLAPSE = Cuj.CUJ_TASKBAR_COLLAPSE; + @Deprecated public static final int CUJ_SHADE_CLEAR_ALL = Cuj.CUJ_SHADE_CLEAR_ALL; + @Deprecated public static final int CUJ_LOCKSCREEN_OCCLUSION = Cuj.CUJ_LOCKSCREEN_OCCLUSION; + @Deprecated public static final int CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION = Cuj.CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION; + @Deprecated public static final int CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER = Cuj.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER; + @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = Cuj.CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY; + @Deprecated public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = Cuj.CUJ_PREDICTIVE_BACK_CROSS_TASK; + @Deprecated public static final int CUJ_PREDICTIVE_BACK_HOME = Cuj.CUJ_PREDICTIVE_BACK_HOME; private static class InstanceHolder { public static final InteractionJankMonitor INSTANCE = new InteractionJankMonitor(new HandlerThread(DEFAULT_WORKER_NAME)); } - private final DeviceConfig.OnPropertiesChangedListener mPropertiesChangedListener = - this::updateProperties; - @GuardedBy("mLock") - private final SparseArray<FrameTracker> mRunningTrackers; - @GuardedBy("mLock") - private final SparseArray<Runnable> mTimeoutActions; - private final HandlerThread mWorker; + private final SparseArray<RunningTracker> mRunningTrackers = new SparseArray<>(); + private final Handler mWorker; private final DisplayResolutionTracker mDisplayResolutionTracker; private final Object mLock = new Object(); private @ColorInt int mDebugBgColor = Color.CYAN; @@ -409,91 +187,6 @@ public class InteractionJankMonitor { private int mTraceThresholdMissedFrames = DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES; private int mTraceThresholdFrameTimeMillis = DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS; - /** @hide */ - @IntDef({ - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, - CUJ_NOTIFICATION_SHADE_SCROLL_FLING, - CUJ_NOTIFICATION_SHADE_ROW_EXPAND, - CUJ_NOTIFICATION_SHADE_ROW_SWIPE, - CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, - CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, - CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS, - CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON, - CUJ_LAUNCHER_APP_CLOSE_TO_HOME, - CUJ_LAUNCHER_APP_CLOSE_TO_PIP, - CUJ_LAUNCHER_QUICK_SWITCH, - CUJ_NOTIFICATION_HEADS_UP_APPEAR, - CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, - CUJ_NOTIFICATION_ADD, - CUJ_NOTIFICATION_REMOVE, - CUJ_NOTIFICATION_APP_START, - CUJ_LOCKSCREEN_PASSWORD_APPEAR, - CUJ_LOCKSCREEN_PATTERN_APPEAR, - CUJ_LOCKSCREEN_PIN_APPEAR, - CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR, - CUJ_LOCKSCREEN_PATTERN_DISAPPEAR, - CUJ_LOCKSCREEN_PIN_DISAPPEAR, - CUJ_LOCKSCREEN_TRANSITION_FROM_AOD, - CUJ_LOCKSCREEN_TRANSITION_TO_AOD, - CUJ_LAUNCHER_OPEN_ALL_APPS, - CUJ_LAUNCHER_ALL_APPS_SCROLL, - CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET, - CUJ_SETTINGS_PAGE_SCROLL, - CUJ_LOCKSCREEN_UNLOCK_ANIMATION, - CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON, - CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER, - CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE, - CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON, - CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP, - CUJ_PIP_TRANSITION, - CUJ_WALLPAPER_TRANSITION, - CUJ_USER_SWITCH, - CUJ_SPLASHSCREEN_AVD, - CUJ_SPLASHSCREEN_EXIT_ANIM, - CUJ_SCREEN_OFF, - CUJ_SCREEN_OFF_SHOW_AOD, - CUJ_ONE_HANDED_ENTER_TRANSITION, - CUJ_ONE_HANDED_EXIT_TRANSITION, - CUJ_UNFOLD_ANIM, - CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS, - CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS, - CUJ_SUW_LOADING_TO_NEXT_FLOW, - CUJ_SUW_LOADING_SCREEN_FOR_STATUS, - CUJ_SPLIT_SCREEN_ENTER, - CUJ_SPLIT_SCREEN_EXIT, - CUJ_LOCKSCREEN_LAUNCH_CAMERA, - CUJ_SPLIT_SCREEN_RESIZE, - CUJ_SETTINGS_SLIDER, - CUJ_TAKE_SCREENSHOT, - CUJ_VOLUME_CONTROL, - CUJ_BIOMETRIC_PROMPT_TRANSITION, - CUJ_SETTINGS_TOGGLE, - CUJ_SHADE_DIALOG_OPEN, - CUJ_USER_DIALOG_OPEN, - CUJ_TASKBAR_EXPAND, - CUJ_TASKBAR_COLLAPSE, - CUJ_SHADE_CLEAR_ALL, - CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION, - CUJ_LOCKSCREEN_OCCLUSION, - CUJ_RECENTS_SCROLLING, - CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS, - CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE, - CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME, - CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION, - CUJ_LAUNCHER_OPEN_SEARCH_RESULT, - CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK, - CUJ_IME_INSETS_SHOW_ANIMATION, - CUJ_IME_INSETS_HIDE_ANIMATION, - CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER, - CUJ_LAUNCHER_UNFOLD_ANIM, - CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY, - CUJ_PREDICTIVE_BACK_CROSS_TASK, - CUJ_PREDICTIVE_BACK_HOME, - }) - @Retention(RetentionPolicy.SOURCE) - public @interface CujType { - } - /** * Get the singleton of InteractionJankMonitor. * @@ -511,71 +204,44 @@ public class InteractionJankMonitor { @VisibleForTesting @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG) public InteractionJankMonitor(@NonNull HandlerThread worker) { - mRunningTrackers = new SparseArray<>(); - mTimeoutActions = new SparseArray<>(); - mWorker = worker; - mWorker.start(); - mDisplayResolutionTracker = new DisplayResolutionTracker(worker.getThreadHandler()); - mSamplingInterval = DEFAULT_SAMPLING_INTERVAL; - mEnabled = DEFAULT_ENABLED; + worker.start(); + mWorker = worker.getThreadHandler(); + mDisplayResolutionTracker = new DisplayResolutionTracker(mWorker); final Context context = ActivityThread.currentApplication(); - if (context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) { - if (DEBUG) { - Log.d(TAG, "Initialized the InteractionJankMonitor." - + " (No READ_DEVICE_CONFIG permission to change configs)" - + " enabled=" + mEnabled + ", interval=" + mSamplingInterval - + ", missedFrameThreshold=" + mTraceThresholdMissedFrames - + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis - + ", package=" + context.getPackageName()); - } + if (context == null || context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) != PERMISSION_GRANTED) { + Log.w(TAG, "Initializing without READ_DEVICE_CONFIG permission." + + " enabled=" + mEnabled + ", interval=" + mSamplingInterval + + ", missedFrameThreshold=" + mTraceThresholdMissedFrames + + ", frameTimeThreshold=" + mTraceThresholdFrameTimeMillis + + ", package=" + (context == null ? "null" : context.getPackageName())); return; } // Post initialization to the background in case we're running on the main thread. - mWorker.getThreadHandler().post( - () -> { - try { - mPropertiesChangedListener.onPropertiesChanged( - DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR)); - DeviceConfig.addOnPropertiesChangedListener( - NAMESPACE_INTERACTION_JANK_MONITOR, - new HandlerExecutor(mWorker.getThreadHandler()), - mPropertiesChangedListener); - } catch (SecurityException ex) { - Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted=" - + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) - + ", package=" + context.getPackageName()); - } - }); + mWorker.post(() -> { + try { + updateProperties(DeviceConfig.getProperties(NAMESPACE_INTERACTION_JANK_MONITOR)); + DeviceConfig.addOnPropertiesChangedListener(NAMESPACE_INTERACTION_JANK_MONITOR, + new HandlerExecutor(mWorker), this::updateProperties); + } catch (SecurityException ex) { + Log.d(TAG, "Can't get properties: READ_DEVICE_CONFIG granted=" + + context.checkCallingOrSelfPermission(READ_DEVICE_CONFIG) + + ", package=" + context.getPackageName()); + } + }); } /** * Creates a {@link FrameTracker} instance. * - * @param config the config used in instrumenting - * @param session the session associates with this tracker + * @param config the conifg associates with this tracker * @return instance of the FrameTracker */ @VisibleForTesting - public FrameTracker createFrameTracker(Configuration config, Session session) { + public FrameTracker createFrameTracker(Configuration config) { final View view = config.mView; - if (!config.hasValidView()) { - boolean attached = false; - boolean hasViewRoot = false; - boolean hasRenderer = false; - if (view != null) { - attached = view.isAttachedToWindow(); - hasViewRoot = view.getViewRootImpl() != null; - hasRenderer = view.getThreadedRenderer() != null; - } - Log.d(TAG, "create FrameTracker fails: view=" + view - + ", attached=" + attached + ", hasViewRoot=" + hasViewRoot - + ", hasRenderer=" + hasRenderer, new Throwable()); - return null; - } - final ThreadedRendererWrapper threadedRenderer = view == null ? null : new ThreadedRendererWrapper(view.getThreadedRenderer()); final ViewRootWrapper viewRoot = @@ -583,52 +249,50 @@ public class InteractionJankMonitor { final SurfaceControlWrapper surfaceControl = new SurfaceControlWrapper(); final ChoreographerWrapper choreographer = new ChoreographerWrapper(Choreographer.getInstance()); - final FrameTrackerListener eventsListener = (s, act) -> handleCujEvents(act, s); + final FrameTrackerListener eventsListener = new FrameTrackerListener() { + @Override + public void onCujEvents(FrameTracker tracker, String action, int reason) { + config.getHandler().runWithScissors(() -> + handleCujEvents(config.mCujType, tracker, action, reason), + EXECUTOR_TASK_TIMEOUT); + } + + @Override + public void triggerPerfetto(Configuration config) { + mWorker.post(() -> PerfettoTrigger.trigger(config.getPerfettoTrigger())); + } + }; final FrameMetricsWrapper frameMetrics = new FrameMetricsWrapper(); - return new FrameTracker(this, session, config.getHandler(), threadedRenderer, viewRoot, + return new FrameTracker(config, threadedRenderer, viewRoot, surfaceControl, choreographer, frameMetrics, new FrameTracker.StatsLogWrapper(mDisplayResolutionTracker), mTraceThresholdMissedFrames, mTraceThresholdFrameTimeMillis, - eventsListener, config); + eventsListener); } @UiThread - private void handleCujEvents(String action, Session session) { + private void handleCujEvents( + @Cuj.CujType int cuj, FrameTracker tracker, String action, @Reasons int reason) { // Clear the running and timeout tasks if the end / cancel was fired within the tracker. // Or we might have memory leaks. - if (needRemoveTasks(action, session)) { - getTracker(session.getCuj()).getHandler().runWithScissors(() -> { - removeTimeout(session.getCuj()); - removeTracker(session.getCuj(), session.getReason()); - }, EXECUTOR_TASK_TIMEOUT); + if (needRemoveTasks(action, reason)) { + removeTrackerIfCurrent(cuj, tracker, reason); } } - private boolean needRemoveTasks(String action, Session session) { - final boolean badEnd = action.equals(ACTION_SESSION_END) - && session.getReason() != REASON_END_NORMAL; + private static boolean needRemoveTasks(String action, @Reasons int reason) { + final boolean badEnd = action.equals(ACTION_SESSION_END) && reason != REASON_END_NORMAL; final boolean badCancel = action.equals(ACTION_SESSION_CANCEL) - && !(session.getReason() == REASON_CANCEL_NORMAL - || session.getReason() == REASON_CANCEL_TIMEOUT); + && !(reason == REASON_CANCEL_NORMAL || reason == REASON_CANCEL_TIMEOUT); return badEnd || badCancel; } - private void removeTimeout(@CujType int cujType) { - synchronized (mLock) { - Runnable timeout = mTimeoutActions.get(cujType); - if (timeout != null) { - getTracker(cujType).getHandler().removeCallbacks(timeout); - mTimeoutActions.remove(cujType); - } - } - } - /** * @param cujType cuj type * @return true if the cuj is under instrumenting, false otherwise. */ - public boolean isInstrumenting(@CujType int cujType) { + public boolean isInstrumenting(@Cuj.CujType int cujType) { synchronized (mLock) { return mRunningTrackers.contains(cujType); } @@ -638,10 +302,10 @@ public class InteractionJankMonitor { * Begins a trace session. * * @param v an attached view. - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. * @return boolean true if the tracker is started successfully, false otherwise. */ - public boolean begin(View v, @CujType int cujType) { + public boolean begin(View v, @Cuj.CujType int cujType) { try { return begin(Configuration.Builder.withView(cujType, v)); } catch (IllegalArgumentException ex) { @@ -667,7 +331,7 @@ public class InteractionJankMonitor { final boolean success = config.getHandler().runWithScissors( () -> result.mResult = beginInternal(config), EXECUTOR_TASK_TIMEOUT); if (!success) { - Log.d(TAG, "begin failed due to timeout, CUJ=" + getNameOfCuj(config.mCujType)); + Log.d(TAG, "begin failed due to timeout, CUJ=" + Cuj.getNameOfCuj(config.mCujType)); return false; } return result.mResult; @@ -680,75 +344,59 @@ public class InteractionJankMonitor { @UiThread private boolean beginInternal(@NonNull Configuration conf) { int cujType = conf.mCujType; - if (!shouldMonitor(cujType)) return false; - FrameTracker tracker = getTracker(cujType); - // Skip subsequent calls if we already have an ongoing tracing. - if (tracker != null) return false; + if (!shouldMonitor()) { + return false; + } - // begin a new trace session. - tracker = createFrameTracker(conf, new Session(cujType, conf.mTag)); - if (tracker == null) return false; - putTracker(cujType, tracker); - tracker.begin(); + RunningTracker tracker = putTrackerIfNoCurrent(cujType, () -> + new RunningTracker( + conf, createFrameTracker(conf), () -> cancel(cujType, REASON_CANCEL_TIMEOUT))); + if (tracker == null) { + return false; + } + tracker.mTracker.begin(); // Cancel the trace if we don't get an end() call in specified duration. - scheduleTimeoutAction( - cujType, conf.mTimeout, () -> cancel(cujType, REASON_CANCEL_TIMEOUT)); + scheduleTimeoutAction(tracker.mConfig, tracker.mTimeoutAction); + return true; } /** * Check if the monitoring is enabled and if it should be sampled. */ - @SuppressWarnings("RandomModInteger") @VisibleForTesting - public boolean shouldMonitor(@CujType int cujType) { - boolean shouldSample = ThreadLocalRandom.current().nextInt() % mSamplingInterval == 0; - if (!mEnabled || !shouldSample) { - if (DEBUG) { - Log.d(TAG, "Skip monitoring cuj: " + getNameOfCuj(cujType) - + ", enable=" + mEnabled + ", debuggable=" + DEFAULT_ENABLED - + ", sample=" + shouldSample + ", interval=" + mSamplingInterval); - } - return false; - } - return true; + public boolean shouldMonitor() { + return mEnabled && (ThreadLocalRandom.current().nextInt(mSamplingInterval) == 0); } - /** - * Schedules a timeout action. - * @param cuj cuj type - * @param timeout duration to timeout - * @param action action once timeout - */ @VisibleForTesting - public void scheduleTimeoutAction(@CujType int cuj, long timeout, Runnable action) { - synchronized (mLock) { - mTimeoutActions.put(cuj, action); - getTracker(cuj).getHandler().postDelayed(action, timeout); - } + public void scheduleTimeoutAction(Configuration config, Runnable action) { + config.getHandler().postDelayed(action, config.mTimeout); } /** * Ends a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. * @return boolean true if the tracker is ended successfully, false otherwise. */ - public boolean end(@CujType int cujType) { + public boolean end(@Cuj.CujType int cujType) { postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> { EventLogTags.writeJankCujEventsEndRequest( cujType, unixNanos, elapsedNanos, realtimeNanos); }); - FrameTracker tracker = getTracker(cujType); + RunningTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. - if (tracker == null) return false; + if (tracker == null) { + return false; + } try { final TrackerResult result = new TrackerResult(); - final boolean success = tracker.getHandler().runWithScissors( - () -> result.mResult = endInternal(cujType), EXECUTOR_TASK_TIMEOUT); + final boolean success = tracker.mConfig.getHandler().runWithScissors( + () -> result.mResult = endInternal(tracker), EXECUTOR_TASK_TIMEOUT); if (!success) { - Log.d(TAG, "end failed due to timeout, CUJ=" + getNameOfCuj(cujType)); + Log.d(TAG, "end failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType)); return false; } return result.mResult; @@ -759,15 +407,11 @@ public class InteractionJankMonitor { } @UiThread - private boolean endInternal(@CujType int cujType) { - // remove the timeout action first. - removeTimeout(cujType); - FrameTracker tracker = getTracker(cujType); - if (tracker == null) return false; - // if the end call doesn't return true, another thread is handling end of the cuj. - if (tracker.end(REASON_END_NORMAL)) { - removeTracker(cujType, REASON_END_NORMAL); + private boolean endInternal(RunningTracker tracker) { + if (removeTrackerIfCurrent(tracker, REASON_END_NORMAL)) { + return false; } + tracker.mTracker.end(REASON_END_NORMAL); return true; } @@ -776,7 +420,7 @@ public class InteractionJankMonitor { * * @return boolean true if the tracker is cancelled successfully, false otherwise. */ - public boolean cancel(@CujType int cujType) { + public boolean cancel(@Cuj.CujType int cujType) { postEventLogToWorkerThread((unixNanos, elapsedNanos, realtimeNanos) -> { EventLogTags.writeJankCujEventsCancelRequest( cujType, unixNanos, elapsedNanos, realtimeNanos); @@ -790,16 +434,18 @@ public class InteractionJankMonitor { * @return boolean true if the tracker is cancelled successfully, false otherwise. */ @VisibleForTesting - public boolean cancel(@CujType int cujType, @Reasons int reason) { - FrameTracker tracker = getTracker(cujType); + public boolean cancel(@Cuj.CujType int cujType, @Reasons int reason) { + RunningTracker tracker = getTracker(cujType); // Skip this call since we haven't started a trace yet. - if (tracker == null) return false; + if (tracker == null) { + return false; + } try { final TrackerResult result = new TrackerResult(); - final boolean success = tracker.getHandler().runWithScissors( - () -> result.mResult = cancelInternal(cujType, reason), EXECUTOR_TASK_TIMEOUT); + final boolean success = tracker.mConfig.getHandler().runWithScissors( + () -> result.mResult = cancelInternal(tracker, reason), EXECUTOR_TASK_TIMEOUT); if (!success) { - Log.d(TAG, "cancel failed due to timeout, CUJ=" + getNameOfCuj(cujType)); + Log.d(TAG, "cancel failed due to timeout, CUJ=" + Cuj.getNameOfCuj(cujType)); return false; } return result.mResult; @@ -810,71 +456,86 @@ public class InteractionJankMonitor { } @UiThread - private boolean cancelInternal(@CujType int cujType, @Reasons int reason) { - // remove the timeout action first. - removeTimeout(cujType); - FrameTracker tracker = getTracker(cujType); - if (tracker == null) return false; - // if the cancel call doesn't return true, another thread is handling cancel of the cuj. - if (tracker.cancel(reason)) { - removeTracker(cujType, reason); + private boolean cancelInternal(RunningTracker tracker, @Reasons int reason) { + if (removeTrackerIfCurrent(tracker, reason)) { + return false; } + tracker.mTracker.cancel(reason); return true; } @UiThread - private void putTracker(@CujType int cuj, @NonNull FrameTracker tracker) { + private RunningTracker putTrackerIfNoCurrent( + @Cuj.CujType int cuj, Supplier<RunningTracker> supplier) { synchronized (mLock) { + if (mRunningTrackers.contains(cuj)) { + return null; + } + + RunningTracker tracker = supplier.get(); + if (tracker == null) { + return null; + } + mRunningTrackers.put(cuj, tracker); if (mDebugOverlay != null) { mDebugOverlay.onTrackerAdded(cuj, tracker); } - if (DEBUG) { - Log.d(TAG, "Added tracker for " + getNameOfCuj(cuj) - + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers)); - } + + return tracker; } } - private FrameTracker getTracker(@CujType int cuj) { + private RunningTracker getTracker(@Cuj.CujType int cuj) { synchronized (mLock) { return mRunningTrackers.get(cuj); } } + /** + * @return {@code true} if another tracker is current + */ + @UiThread + private boolean removeTrackerIfCurrent(RunningTracker tracker, int reason) { + return removeTrackerIfCurrent(tracker.mConfig.mCujType, tracker.mTracker, reason); + } + + /** + * @return {@code true} if another tracker is current + */ @UiThread - private void removeTracker(@CujType int cuj, int reason) { + private boolean removeTrackerIfCurrent(@Cuj.CujType int cuj, FrameTracker tracker, int reason) { synchronized (mLock) { + RunningTracker running = mRunningTrackers.get(cuj); + if (running == null || running.mTracker != tracker) { + return true; + } + + running.mConfig.getHandler().removeCallbacks(running.mTimeoutAction); mRunningTrackers.remove(cuj); if (mDebugOverlay != null) { mDebugOverlay.onTrackerRemoved(cuj, reason, mRunningTrackers); } - if (DEBUG) { - Log.d(TAG, "Removed tracker for " + getNameOfCuj(cuj) - + ". mRunningTrackers=" + listNamesOfCujs(mRunningTrackers)); - } + return false; } } @WorkerThread - private void updateProperties(DeviceConfig.Properties properties) { + @VisibleForTesting + public void updateProperties(DeviceConfig.Properties properties) { for (String property : properties.getKeyset()) { switch (property) { - case SETTINGS_SAMPLING_INTERVAL_KEY: - mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL); - break; - case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY: - mTraceThresholdMissedFrames = - properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES); - break; - case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY: - mTraceThresholdFrameTimeMillis = - properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS); - break; - case SETTINGS_ENABLED_KEY: - mEnabled = properties.getBoolean(property, DEFAULT_ENABLED); - break; - case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY: + case SETTINGS_SAMPLING_INTERVAL_KEY -> + mSamplingInterval = properties.getInt(property, DEFAULT_SAMPLING_INTERVAL); + case SETTINGS_THRESHOLD_MISSED_FRAMES_KEY -> + mTraceThresholdMissedFrames = + properties.getInt(property, DEFAULT_TRACE_THRESHOLD_MISSED_FRAMES); + case SETTINGS_THRESHOLD_FRAME_TIME_MILLIS_KEY -> + mTraceThresholdFrameTimeMillis = + properties.getInt(property, DEFAULT_TRACE_THRESHOLD_FRAME_TIME_MILLIS); + case SETTINGS_ENABLED_KEY -> + mEnabled = properties.getBoolean(property, DEFAULT_ENABLED); + case SETTINGS_DEBUG_OVERLAY_ENABLED_KEY -> { // Never allow the debug overlay to be used on user builds boolean debugOverlayEnabled = Build.IS_DEBUGGABLE && properties.getBoolean(property, DEFAULT_DEBUG_OVERLAY_ENABLED); @@ -885,49 +546,35 @@ public class InteractionJankMonitor { mDebugOverlay.dispose(); mDebugOverlay = null; } - break; - default: - if (DEBUG) { - Log.d(TAG, "Got a change event for an unknown property: " - + property + " => " + properties.getString(property, "")); - } + } + default -> Log.w(TAG, "Got a change event for an unknown property: " + + property + " => " + properties.getString(property, "")); } } } - @VisibleForTesting - public DeviceConfig.OnPropertiesChangedListener getPropertiesChangedListener() { - return mPropertiesChangedListener; - } - - /** - * Triggers the perfetto daemon to collect and upload data. - */ - @VisibleForTesting - public void trigger(Session session) { - mWorker.getThreadHandler().post( - () -> PerfettoTrigger.trigger(session.getPerfettoTrigger())); - } - /** * A helper method to translate interaction type to CUJ name. * * @param interactionType the interaction type defined in AtomsProto.java * @return the name of the interaction type + * @deprecated use {@link Cuj#getNameOfInteraction(int)} */ + @Deprecated public static String getNameOfInteraction(int interactionType) { - // There is an offset amount of 1 between cujType and interactionType. - return getNameOfCuj(getCujTypeFromInteraction(interactionType)); + return Cuj.getNameOfInteraction(interactionType); } /** - * A helper method to translate interaction type to CUJ type. + * A helper method to translate CUJ type to CUJ name. * - * @param interactionType the interaction type defined in AtomsProto.java - * @return the integer in {@link CujType} + * @param cujType the cuj type defined in this file + * @return the name of the cuj type + * @deprecated use {@link Cuj#getNameOfCuj(int)} */ - private static int getCujTypeFromInteraction(int interactionType) { - return interactionType - 1; + @Deprecated + public static String getNameOfCuj(int cujType) { + return Cuj.getNameOfCuj(cujType); } /** @@ -943,195 +590,14 @@ public class InteractionJankMonitor { mDebugYOffset = yOffset; } - /** - * A helper method for getting a string representation of all running CUJs. For example, - * "(LOCKSCREEN_TRANSITION_FROM_AOD, IME_INSETS_ANIMATION)" - */ - private static String listNamesOfCujs(SparseArray<FrameTracker> trackers) { - if (!DEBUG) { - return null; - } - StringBuilder sb = new StringBuilder(); - sb.append('('); - for (int i = 0; i < trackers.size(); i++) { - sb.append(getNameOfCuj(trackers.keyAt(i))); - if (i < trackers.size() - 1) { - sb.append(", "); - } - } - sb.append(')'); - return sb.toString(); - } + private void postEventLogToWorkerThread(TimeFunction logFunction) { + final Instant now = Instant.now(); + final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS) + + now.getNano(); + final long elapsedNanos = SystemClock.elapsedRealtimeNanos(); + final long realtimeNanos = SystemClock.uptimeNanos(); - /** - * A helper method to translate CUJ type to CUJ name. - * - * @param cujType the cuj type defined in this file - * @return the name of the cuj type - */ - public static String getNameOfCuj(int cujType) { - // Please note: - // 1. The length of the returned string shouldn't exceed MAX_LENGTH_OF_CUJ_NAME. - // 2. The returned string should be the same with the name defined in atoms.proto. - switch (cujType) { - case CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE: - return "NOTIFICATION_SHADE_EXPAND_COLLAPSE"; - case CUJ_NOTIFICATION_SHADE_SCROLL_FLING: - return "NOTIFICATION_SHADE_SCROLL_FLING"; - case CUJ_NOTIFICATION_SHADE_ROW_EXPAND: - return "NOTIFICATION_SHADE_ROW_EXPAND"; - case CUJ_NOTIFICATION_SHADE_ROW_SWIPE: - return "NOTIFICATION_SHADE_ROW_SWIPE"; - case CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE: - return "NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE"; - case CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE: - return "NOTIFICATION_SHADE_QS_SCROLL_SWIPE"; - case CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS: - return "LAUNCHER_APP_LAUNCH_FROM_RECENTS"; - case CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON: - return "LAUNCHER_APP_LAUNCH_FROM_ICON"; - case CUJ_LAUNCHER_APP_CLOSE_TO_HOME: - return "LAUNCHER_APP_CLOSE_TO_HOME"; - case CUJ_LAUNCHER_APP_CLOSE_TO_PIP: - return "LAUNCHER_APP_CLOSE_TO_PIP"; - case CUJ_LAUNCHER_QUICK_SWITCH: - return "LAUNCHER_QUICK_SWITCH"; - case CUJ_NOTIFICATION_HEADS_UP_APPEAR: - return "NOTIFICATION_HEADS_UP_APPEAR"; - case CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR: - return "NOTIFICATION_HEADS_UP_DISAPPEAR"; - case CUJ_NOTIFICATION_ADD: - return "NOTIFICATION_ADD"; - case CUJ_NOTIFICATION_REMOVE: - return "NOTIFICATION_REMOVE"; - case CUJ_NOTIFICATION_APP_START: - return "NOTIFICATION_APP_START"; - case CUJ_LOCKSCREEN_PASSWORD_APPEAR: - return "LOCKSCREEN_PASSWORD_APPEAR"; - case CUJ_LOCKSCREEN_PATTERN_APPEAR: - return "LOCKSCREEN_PATTERN_APPEAR"; - case CUJ_LOCKSCREEN_PIN_APPEAR: - return "LOCKSCREEN_PIN_APPEAR"; - case CUJ_LOCKSCREEN_PASSWORD_DISAPPEAR: - return "LOCKSCREEN_PASSWORD_DISAPPEAR"; - case CUJ_LOCKSCREEN_PATTERN_DISAPPEAR: - return "LOCKSCREEN_PATTERN_DISAPPEAR"; - case CUJ_LOCKSCREEN_PIN_DISAPPEAR: - return "LOCKSCREEN_PIN_DISAPPEAR"; - case CUJ_LOCKSCREEN_TRANSITION_FROM_AOD: - return "LOCKSCREEN_TRANSITION_FROM_AOD"; - case CUJ_LOCKSCREEN_TRANSITION_TO_AOD: - return "LOCKSCREEN_TRANSITION_TO_AOD"; - case CUJ_LAUNCHER_OPEN_ALL_APPS : - return "LAUNCHER_OPEN_ALL_APPS"; - case CUJ_LAUNCHER_ALL_APPS_SCROLL: - return "LAUNCHER_ALL_APPS_SCROLL"; - case CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET: - return "LAUNCHER_APP_LAUNCH_FROM_WIDGET"; - case CUJ_SETTINGS_PAGE_SCROLL: - return "SETTINGS_PAGE_SCROLL"; - case CUJ_LOCKSCREEN_UNLOCK_ANIMATION: - return "LOCKSCREEN_UNLOCK_ANIMATION"; - case CUJ_SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON: - return "SHADE_APP_LAUNCH_FROM_HISTORY_BUTTON"; - case CUJ_SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER: - return "SHADE_APP_LAUNCH_FROM_MEDIA_PLAYER"; - case CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE: - return "SHADE_APP_LAUNCH_FROM_QS_TILE"; - case CUJ_SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON: - return "SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON"; - case CUJ_STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP: - return "STATUS_BAR_APP_LAUNCH_FROM_CALL_CHIP"; - case CUJ_PIP_TRANSITION: - return "PIP_TRANSITION"; - case CUJ_WALLPAPER_TRANSITION: - return "WALLPAPER_TRANSITION"; - case CUJ_USER_SWITCH: - return "USER_SWITCH"; - case CUJ_SPLASHSCREEN_AVD: - return "SPLASHSCREEN_AVD"; - case CUJ_SPLASHSCREEN_EXIT_ANIM: - return "SPLASHSCREEN_EXIT_ANIM"; - case CUJ_SCREEN_OFF: - return "SCREEN_OFF"; - case CUJ_SCREEN_OFF_SHOW_AOD: - return "SCREEN_OFF_SHOW_AOD"; - case CUJ_ONE_HANDED_ENTER_TRANSITION: - return "ONE_HANDED_ENTER_TRANSITION"; - case CUJ_ONE_HANDED_EXIT_TRANSITION: - return "ONE_HANDED_EXIT_TRANSITION"; - case CUJ_UNFOLD_ANIM: - return "UNFOLD_ANIM"; - case CUJ_SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS: - return "SUW_LOADING_TO_SHOW_INFO_WITH_ACTIONS"; - case CUJ_SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS: - return "SUW_SHOW_FUNCTION_SCREEN_WITH_ACTIONS"; - case CUJ_SUW_LOADING_TO_NEXT_FLOW: - return "SUW_LOADING_TO_NEXT_FLOW"; - case CUJ_SUW_LOADING_SCREEN_FOR_STATUS: - return "SUW_LOADING_SCREEN_FOR_STATUS"; - case CUJ_SPLIT_SCREEN_ENTER: - return "SPLIT_SCREEN_ENTER"; - case CUJ_SPLIT_SCREEN_EXIT: - return "SPLIT_SCREEN_EXIT"; - case CUJ_LOCKSCREEN_LAUNCH_CAMERA: - return "LOCKSCREEN_LAUNCH_CAMERA"; - case CUJ_SPLIT_SCREEN_RESIZE: - return "SPLIT_SCREEN_RESIZE"; - case CUJ_SETTINGS_SLIDER: - return "SETTINGS_SLIDER"; - case CUJ_TAKE_SCREENSHOT: - return "TAKE_SCREENSHOT"; - case CUJ_VOLUME_CONTROL: - return "VOLUME_CONTROL"; - case CUJ_BIOMETRIC_PROMPT_TRANSITION: - return "BIOMETRIC_PROMPT_TRANSITION"; - case CUJ_SETTINGS_TOGGLE: - return "SETTINGS_TOGGLE"; - case CUJ_SHADE_DIALOG_OPEN: - return "SHADE_DIALOG_OPEN"; - case CUJ_USER_DIALOG_OPEN: - return "USER_DIALOG_OPEN"; - case CUJ_TASKBAR_EXPAND: - return "TASKBAR_EXPAND"; - case CUJ_TASKBAR_COLLAPSE: - return "TASKBAR_COLLAPSE"; - case CUJ_SHADE_CLEAR_ALL: - return "SHADE_CLEAR_ALL"; - case CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION: - return "LAUNCHER_UNLOCK_ENTRANCE_ANIMATION"; - case CUJ_LOCKSCREEN_OCCLUSION: - return "LOCKSCREEN_OCCLUSION"; - case CUJ_RECENTS_SCROLLING: - return "RECENTS_SCROLLING"; - case CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS: - return "LAUNCHER_APP_SWIPE_TO_RECENTS"; - case CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE: - return "LAUNCHER_CLOSE_ALL_APPS_SWIPE"; - case CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME: - return "LAUNCHER_CLOSE_ALL_APPS_TO_HOME"; - case CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION: - return "LOCKSCREEN_CLOCK_MOVE_ANIMATION"; - case CUJ_LAUNCHER_OPEN_SEARCH_RESULT: - return "LAUNCHER_OPEN_SEARCH_RESULT"; - case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK: - return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK"; - case CUJ_IME_INSETS_SHOW_ANIMATION: - return "IME_INSETS_SHOW_ANIMATION"; - case CUJ_IME_INSETS_HIDE_ANIMATION: - return "IME_INSETS_HIDE_ANIMATION"; - case CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER: - return "SPLIT_SCREEN_DOUBLE_TAP_DIVIDER"; - case CUJ_LAUNCHER_UNFOLD_ANIM: - return "LAUNCHER_UNFOLD_ANIM"; - case CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY: - return "PREDICTIVE_BACK_CROSS_ACTIVITY"; - case CUJ_PREDICTIVE_BACK_CROSS_TASK: - return "PREDICTIVE_BACK_CROSS_TASK"; - case CUJ_PREDICTIVE_BACK_HOME: - return "PREDICTIVE_BACK_HOME"; - } - return "UNKNOWN"; + mWorker.post(() -> logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos)); } private static class TrackerResult { @@ -1147,9 +613,10 @@ public class InteractionJankMonitor { private final Context mContext; private final long mTimeout; private final String mTag; + private final String mSessionName; private final boolean mSurfaceOnly; private final SurfaceControl mSurfaceControl; - private final @CujType int mCujType; + private final @Cuj.CujType int mCujType; private final boolean mDeferMonitor; private final Handler mHandler; @@ -1167,17 +634,17 @@ public class InteractionJankMonitor { private String mAttrTag = ""; private boolean mAttrSurfaceOnly; private SurfaceControl mAttrSurfaceControl; - private @CujType int mAttrCujType; + private final @Cuj.CujType int mAttrCujType; private boolean mAttrDeferMonitor = true; /** * Creates a builder which instruments only surface. - * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}. + * @param cuj The enum defined in {@link Cuj.CujType}. * @param context context * @param surfaceControl surface control * @return builder */ - public static Builder withSurface(@CujType int cuj, @NonNull Context context, + public static Builder withSurface(@Cuj.CujType int cuj, @NonNull Context context, @NonNull SurfaceControl surfaceControl) { return new Builder(cuj) .setContext(context) @@ -1187,16 +654,17 @@ public class InteractionJankMonitor { /** * Creates a builder which instruments both surface and view. - * @param cuj The enum defined in {@link InteractionJankMonitor.CujType}. + * @param cuj The enum defined in {@link Cuj.CujType}. * @param view view * @return builder */ - public static Builder withView(@CujType int cuj, @NonNull View view) { - return new Builder(cuj).setView(view) + public static Builder withView(@Cuj.CujType int cuj, @NonNull View view) { + return new Builder(cuj) + .setView(view) .setContext(view.getContext()); } - private Builder(@CujType int cuj) { + private Builder(@Cuj.CujType int cuj) { mAttrCujType = cuj; } @@ -1281,11 +749,12 @@ public class InteractionJankMonitor { } } - private Configuration(@CujType int cuj, View view, String tag, long timeout, + private Configuration(@Cuj.CujType int cuj, View view, @NonNull String tag, long timeout, boolean surfaceOnly, Context context, SurfaceControl surfaceControl, boolean deferMonitor) { mCujType = cuj; mTag = tag; + mSessionName = generateSessionName(Cuj.getNameOfCuj(cuj), tag); mTimeout = timeout; mView = view; mSurfaceOnly = surfaceOnly; @@ -1298,6 +767,23 @@ public class InteractionJankMonitor { mHandler = mSurfaceOnly ? mContext.getMainThreadHandler() : mView.getHandler(); } + @VisibleForTesting + public static String generateSessionName( + @NonNull String cujName, @NonNull String cujPostfix) { + final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix); + if (hasPostfix) { + final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length(); + if (cujPostfix.length() > remaining) { + cujPostfix = cujPostfix.substring(0, remaining - 3).concat("..."); + } + } + // The max length of the whole string should be: + // 105 with postfix, 83 without postfix + return hasPostfix + ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix) + : TextUtils.formatSimple("J<%s>", cujName); + } + private void validate() { boolean shouldThrow = false; final StringBuilder msg = new StringBuilder(); @@ -1360,10 +846,10 @@ public class InteractionJankMonitor { return mSurfaceControl; } - @VisibleForTesting /** * @return a view which is attached to the view tree. */ + @VisibleForTesting public View getView() { return mView; } @@ -1375,7 +861,7 @@ public class InteractionJankMonitor { return mDeferMonitor; } - @VisibleForTesting + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public Handler getHandler() { return mHandler; } @@ -1387,79 +873,27 @@ public class InteractionJankMonitor { public int getDisplayId() { return (mSurfaceOnly ? mContext : mView.getContext()).getDisplayId(); } - } - - /** - * A class to represent a session. - */ - public static class Session { - @CujType - private final int mCujType; - private final long mTimeStamp; - @Reasons - private int mReason = REASON_END_UNKNOWN; - private final String mName; - - public Session(@CujType int cujType, @NonNull String postfix) { - mCujType = cujType; - mTimeStamp = System.nanoTime(); - mName = generateSessionName(getNameOfCuj(cujType), postfix); - } - - private String generateSessionName(@NonNull String cujName, @NonNull String cujPostfix) { - final boolean hasPostfix = !TextUtils.isEmpty(cujPostfix); - // We assert that the cujName shouldn't exceed MAX_LENGTH_OF_CUJ_NAME. - if (cujName.length() > MAX_LENGTH_OF_CUJ_NAME) { - throw new IllegalArgumentException(TextUtils.formatSimple( - "The length of cuj name <%s> exceeds %d", cujName, MAX_LENGTH_OF_CUJ_NAME)); - } - if (hasPostfix) { - final int remaining = MAX_LENGTH_SESSION_NAME - cujName.length(); - if (cujPostfix.length() > remaining) { - cujPostfix = cujPostfix.substring(0, remaining - 3).concat("..."); - } - } - // The max length of the whole string should be: - // 105 with postfix, 83 without postfix - return hasPostfix - ? TextUtils.formatSimple("J<%s::%s>", cujName, cujPostfix) - : TextUtils.formatSimple("J<%s>", cujName); - } - @CujType - public int getCuj() { - return mCujType; + public String getSessionName() { + return mSessionName; } public int getStatsdInteractionType() { - return CUJ_TO_STATSD_INTERACTION_TYPE[mCujType]; + return Cuj.getStatsdInteractionType(mCujType); } /** Describes whether the measurement from this session should be written to statsd. */ public boolean logToStatsd() { - return getStatsdInteractionType() != NO_STATSD_LOGGING; + return Cuj.logToStatsd(mCujType); } public String getPerfettoTrigger() { - return String.format(Locale.US, "com.android.telemetry.interaction-jank-monitor-%d", - mCujType); - } - - public String getName() { - return mName; - } - - public long getTimeStamp() { - return mTimeStamp; - } - - public void setReason(@Reasons int reason) { - mReason = reason; + return TextUtils.formatSimple( + "com.android.telemetry.interaction-jank-monitor-%d", mCujType); } - @Reasons - public int getReason() { - return mReason; + public @Cuj.CujType int getCujType() { + return mCujType; } } @@ -1468,15 +902,15 @@ public class InteractionJankMonitor { void invoke(long unixNanos, long elapsedNanos, long realtimeNanos); } - private void postEventLogToWorkerThread(TimeFunction logFunction) { - final Instant now = Instant.now(); - final long unixNanos = TimeUnit.NANOSECONDS.convert(now.getEpochSecond(), TimeUnit.SECONDS) - + now.getNano(); - final long elapsedNanos = SystemClock.elapsedRealtimeNanos(); - final long realtimeNanos = SystemClock.uptimeNanos(); + static class RunningTracker { + public final Configuration mConfig; + public final FrameTracker mTracker; + public final Runnable mTimeoutAction; - mWorker.getThreadHandler().post(() -> { - logFunction.invoke(unixNanos, elapsedNanos, realtimeNanos); - }); + RunningTracker(Configuration config, FrameTracker tracker, Runnable timeoutAction) { + this.mConfig = config; + this.mTracker = tracker; + this.mTimeoutAction = timeoutAction; + } } } diff --git a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java index ef7944c21ad2..f3f16a0c662d 100644 --- a/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java +++ b/core/java/com/android/internal/jank/InteractionMonitorDebugOverlay.java @@ -34,7 +34,6 @@ import android.view.WindowCallbacks; import com.android.internal.annotations.GuardedBy; import com.android.internal.jank.FrameTracker.Reasons; -import com.android.internal.jank.InteractionJankMonitor.CujType; /** * An overlay that uses WindowCallbacks to draw the names of all running CUJs to the window @@ -94,14 +93,14 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { } @UiThread - private boolean attachViewRootIfNeeded(FrameTracker tracker) { - FrameTracker.ViewRootWrapper viewRoot = tracker.getViewRoot(); + private boolean attachViewRootIfNeeded(InteractionJankMonitor.RunningTracker tracker) { + FrameTracker.ViewRootWrapper viewRoot = tracker.mTracker.getViewRoot(); if (mViewRoot == null && viewRoot != null) { // Add a trace marker so we can identify traces that were captured while the debug // overlay was enabled. Traces that use the debug overlay should NOT be used for // performance analysis. Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_APP, TRACK_NAME, "DEBUG_OVERLAY_DRAW", 0); - mHandler = tracker.getHandler(); + mHandler = tracker.mConfig.getHandler(); mViewRoot = viewRoot; mHandler.runWithScissors(() -> viewRoot.addWindowCallbacks(this), InteractionJankMonitor.EXECUTOR_TASK_TIMEOUT); @@ -111,11 +110,12 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { return false; } + @GuardedBy("mLock") private float getWidthOfLongestCujName(int cujFontSize) { mDebugPaint.setTextSize(cujFontSize); float maxLength = 0; for (int i = 0; i < mRunningCujs.size(); i++) { - String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i)); + String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i)); float textLength = mDebugPaint.measureText(cujName); if (textLength > maxLength) { maxLength = textLength; @@ -149,8 +149,8 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { } @UiThread - void onTrackerRemoved(@CujType int removedCuj, @Reasons int reason, - SparseArray<FrameTracker> runningTrackers) { + void onTrackerRemoved(@Cuj.CujType int removedCuj, @Reasons int reason, + SparseArray<InteractionJankMonitor.RunningTracker> runningTrackers) { synchronized (mLock) { mRunningCujs.put(removedCuj, reason); // If REASON_STILL_RUNNING is not in mRunningCujs, then all CUJs have ended @@ -164,7 +164,7 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { // trackers for (int i = 0; i < runningTrackers.size(); i++) { if (mViewRoot.equals( - runningTrackers.valueAt(i).getViewRoot())) { + runningTrackers.valueAt(i).mTracker.getViewRoot())) { needsNewViewRoot = false; break; } @@ -185,7 +185,7 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { } @UiThread - void onTrackerAdded(@CujType int addedCuj, FrameTracker tracker) { + void onTrackerAdded(@Cuj.CujType int addedCuj, InteractionJankMonitor.RunningTracker tracker) { synchronized (mLock) { // Use REASON_STILL_RUNNING (not technically one of the '@Reasons') to indicate the CUJ // is still running @@ -230,41 +230,44 @@ class InteractionMonitorDebugOverlay implements WindowCallbacks { int cujFontSize = dipToPx(18); final float cujNameTextHeight = getTextHeight(cujFontSize); final float packageNameTextHeight = getTextHeight(packageNameFontSize); - float maxLength = getWidthOfLongestCujName(cujFontSize); - final int dx = (int) ((w - maxLength) / 2f); - canvas.translate(dx, dy); - // Draw background rectangle for displaying the text showing the CUJ name - mDebugPaint.setColor(mBgColor); - canvas.drawRect( - -padding * 2, // more padding on top so we can draw the package name - -padding, - padding * 2 + maxLength, - padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(), - mDebugPaint); - mDebugPaint.setTextSize(packageNameFontSize); - mDebugPaint.setColor(Color.BLACK); - mDebugPaint.setStrikeThruText(false); - canvas.translate(0, packageNameTextHeight); - canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint); - mDebugPaint.setTextSize(cujFontSize); - // Draw text for CUJ names - for (int i = 0; i < mRunningCujs.size(); i++) { - int status = mRunningCujs.valueAt(i); - if (status == REASON_STILL_RUNNING) { - mDebugPaint.setColor(Color.BLACK); - mDebugPaint.setStrikeThruText(false); - } else if (status == REASON_END_NORMAL) { - mDebugPaint.setColor(Color.GRAY); - mDebugPaint.setStrikeThruText(false); - } else { - // Cancelled, or otherwise ended for a bad reason - mDebugPaint.setColor(Color.RED); - mDebugPaint.setStrikeThruText(true); + synchronized (mLock) { + float maxLength = getWidthOfLongestCujName(cujFontSize); + + final int dx = (int) ((w - maxLength) / 2f); + canvas.translate(dx, dy); + // Draw background rectangle for displaying the text showing the CUJ name + mDebugPaint.setColor(mBgColor); + canvas.drawRect( + -padding * 2, // more padding on top so we can draw the package name + -padding, + padding * 2 + maxLength, + padding * 2 + packageNameTextHeight + cujNameTextHeight * mRunningCujs.size(), + mDebugPaint); + mDebugPaint.setTextSize(packageNameFontSize); + mDebugPaint.setColor(Color.BLACK); + mDebugPaint.setStrikeThruText(false); + canvas.translate(0, packageNameTextHeight); + canvas.drawText("package:" + mPackageName, 0, 0, mDebugPaint); + mDebugPaint.setTextSize(cujFontSize); + // Draw text for CUJ names + for (int i = 0; i < mRunningCujs.size(); i++) { + int status = mRunningCujs.valueAt(i); + if (status == REASON_STILL_RUNNING) { + mDebugPaint.setColor(Color.BLACK); + mDebugPaint.setStrikeThruText(false); + } else if (status == REASON_END_NORMAL) { + mDebugPaint.setColor(Color.GRAY); + mDebugPaint.setStrikeThruText(false); + } else { + // Cancelled, or otherwise ended for a bad reason + mDebugPaint.setColor(Color.RED); + mDebugPaint.setStrikeThruText(true); + } + String cujName = Cuj.getNameOfCuj(mRunningCujs.keyAt(i)); + canvas.translate(0, cujNameTextHeight); + canvas.drawText(cujName, 0, 0, mDebugPaint); } - String cujName = InteractionJankMonitor.getNameOfCuj(mRunningCujs.keyAt(i)); - canvas.translate(0, cujNameTextHeight); - canvas.drawText(cujName, 0, 0, mDebugPaint); } } } diff --git a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java index 4ed361f24439..6c09b7c04fa7 100644 --- a/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java +++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackage.java @@ -112,7 +112,7 @@ public interface ParsingPackage { ParsingPackage addUsesOptionalNativeLibrary(String libraryName); ParsingPackage addUsesSdkLibrary(String libraryName, long versionMajor, - String[] certSha256Digests); + String[] certSha256Digests, boolean usesSdkLibrariesOptional); ParsingPackage addUsesStaticLibrary(String libraryName, long version, String[] certSha256Digests); diff --git a/core/java/com/android/internal/protolog/ProtoLogGroup.java b/core/java/com/android/internal/protolog/ProtoLogGroup.java index 4bb7c33b41e2..8c2a52560050 100644 --- a/core/java/com/android/internal/protolog/ProtoLogGroup.java +++ b/core/java/com/android/internal/protolog/ProtoLogGroup.java @@ -93,6 +93,7 @@ public enum ProtoLogGroup implements IProtoLogGroup { WM_DEBUG_DREAM(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, true, Consts.TAG_WM), WM_DEBUG_DIMMER(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), + WM_DEBUG_TPL(Consts.ENABLE_DEBUG, Consts.ENABLE_LOG_TO_PROTO_DEBUG, false, Consts.TAG_WM), TEST_GROUP(true, true, false, "WindowManagerProtoLogTest"); private final boolean mEnabled; diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java index 4e4f26cd6ad6..f86595f1ea7b 100644 --- a/core/java/com/android/server/pm/pkg/AndroidPackage.java +++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java @@ -1340,6 +1340,15 @@ public interface AndroidPackage { @Nullable long[] getUsesSdkLibrariesVersionsMajor(); + + /** + * @see R.styleable#AndroidManifestUsesSdkLibrary_optional + * @hide + */ + @Immutable.Ignore + @Nullable + boolean[] getUsesSdkLibrariesOptional(); + /** * TODO(b/135203078): Move static library stuff to an inner data class * diff --git a/core/jni/android_os_Debug.cpp b/core/jni/android_os_Debug.cpp index e0bcef642d82..de1ce4e29198 100644 --- a/core/jni/android_os_Debug.cpp +++ b/core/jni/android_os_Debug.cpp @@ -1014,6 +1014,10 @@ static jboolean android_os_Debug_isVmapStack(JNIEnv *env, jobject clazz) return cfg_state == CONFIG_SET; } +static jboolean android_os_Debug_logAllocatorStats(JNIEnv*, jobject) { + return mallopt(M_LOG_STATS, 0) == 1 ? JNI_TRUE : JNI_FALSE; +} + /* * JNI registration. */ @@ -1056,6 +1060,7 @@ static const JNINativeMethod gMethods[] = { {"getDmabufHeapPoolsSizeKb", "()J", (void*)android_os_Debug_getDmabufHeapPoolsSizeKb}, {"getGpuTotalUsageKb", "()J", (void*)android_os_Debug_getGpuTotalUsageKb}, {"isVmapStack", "()Z", (void*)android_os_Debug_isVmapStack}, + {"logAllocatorStats", "()Z", (void*)android_os_Debug_logAllocatorStats}, }; int register_android_os_Debug(JNIEnv *env) diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index f8546b73a37e..596cfe5a695d 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -2515,6 +2515,10 @@ <attr name="versionMajor" format="integer" /> <!-- The SHA-256 digest of the SDK library signing certificate. --> <attr name="certDigest" format="string" /> + <!-- Specify whether the SDK is optional. The default is false, false means app can be + installed even if the SDK library doesn't exist, and the SDK library can be uninstalled + when the app is still installed. --> + <attr name="optional" format="boolean" /> </declare-styleable> <!-- The <code>static-library</code> tag declares that this apk is providing itself diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index b12f30242e80..f10e7f8c337e 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -115,6 +115,8 @@ <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @hide @SystemApi --> <public name="isVirtualDeviceOnly"/> + <!-- @FlaggedApi("android.content.pm.sdk_lib_independence") --> + <public name="optional"/> </staging-public-group> <staging-public-group type="id" first-id="0x01bc0000"> diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 617262203c6b..b30a0c8fbbd3 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -472,6 +472,7 @@ public class ViewRootImplTest { * Test the value of the frame rate cateogry based on the visibility of a view * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE * Visible: FRAME_RATE_CATEGORY_NORMAL + * Also, mIsFrameRateBoosting should be true when the visibility becomes visible */ @Test @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY) @@ -485,6 +486,7 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NO_PREFERENCE); }); + sInstrumentation.waitForIdleSync(); sInstrumentation.runOnMainSync(() -> { view.setVisibility(View.VISIBLE); @@ -492,6 +494,11 @@ public class ViewRootImplTest { assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL); }); + sInstrumentation.waitForIdleSync(); + + sInstrumentation.runOnMainSync(() -> { + assertEquals(viewRootImpl.getIsFrameRateBoosting(), true); + }); } /** diff --git a/core/tests/coretests/src/com/android/internal/jank/CujTest.java b/core/tests/coretests/src/com/android/internal/jank/CujTest.java new file mode 100644 index 000000000000..bf35ed0a1601 --- /dev/null +++ b/core/tests/coretests/src/com/android/internal/jank/CujTest.java @@ -0,0 +1,156 @@ +/* + * 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.internal.jank; + +import static android.text.TextUtils.formatSimple; + +import static com.android.internal.jank.Cuj.getNameOfCuj; + +import static com.google.common.truth.Truth.assertThat; +import static com.google.common.truth.Truth.assertWithMessage; + +import androidx.test.filters.SmallTest; + +import com.android.internal.util.FrameworkStatsLog; + +import com.google.common.truth.Expect; + +import org.junit.Rule; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +@SmallTest +public class CujTest { + private static final String ENUM_NAME_PREFIX = + "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__"; + private static final Set<String> DEPRECATED_VALUES = new HashSet<>() { + { + add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION"); + } + }; + private static final Map<Integer, String> ENUM_NAME_EXCEPTION_MAP = new HashMap<>() { + { + put(Cuj.CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD")); + put(Cuj.CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR")); + put(Cuj.CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH")); + put(Cuj.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR")); + put(Cuj.CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND")); + put(Cuj.CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")); + put(Cuj.CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING")); + } + }; + + @Rule + public final Expect mExpect = Expect.create(); + + @Test + public void testCujNameLimit() { + getCujConstants().forEach(f -> { + final int cuj = getIntFieldChecked(f); + mExpect.withMessage(formatSimple("Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj))) + .that(getNameOfCuj(cuj).length()) + .isAtMost(Cuj.MAX_LENGTH_OF_CUJ_NAME); + }); + } + + @Test + public void testCujTypeEnumCorrectlyDefined() throws Exception { + List<Field> cujEnumFields = getCujConstants().toList(); + + HashSet<Integer> allValues = new HashSet<>(); + for (Field field : cujEnumFields) { + int fieldValue = field.getInt(null); + assertWithMessage("All CujType values must be unique. Field %s repeats existing value.", + field.getName()) + .that(allValues.add(fieldValue)) + .isTrue(); + assertWithMessage("Field %s must have a value <= LAST_CUJ", field.getName()) + .that(fieldValue) + .isAtMost(Cuj.LAST_CUJ); + assertWithMessage("Field %s must have a statsd mapping.", field.getName()) + .that(Cuj.logToStatsd(fieldValue)) + .isTrue(); + } + } + + @Test + public void testCujsMapToEnumsCorrectly() { + List<Field> cujs = getCujConstants().toList(); + + Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) + .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) + && !DEPRECATED_VALUES.contains(f.getName()) + && Modifier.isStatic(f.getModifiers()) + && f.getType() == int.class) + .collect(Collectors.toMap(CujTest::getIntFieldChecked, Field::getName)); + + assertThat(enumsMap.size() - 1).isEqualTo(cujs.size()); + + cujs.forEach(f -> { + final int cuj = getIntFieldChecked(f); + final String cujName = f.getName(); + final String expectedEnumName = + ENUM_NAME_EXCEPTION_MAP.getOrDefault(cuj, getEnumName(cujName.substring(4))); + final int enumKey = Cuj.getStatsdInteractionType(cuj); + final String enumName = enumsMap.get(enumKey); + final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj)); + + mExpect.withMessage( + formatSimple("%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey)) + .that(expectedEnumName.equals(enumName)) + .isTrue(); + mExpect.withMessage( + formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s", + cuj, cujName, expectedNameOfCuj)) + .that(cujName.equals(expectedNameOfCuj)) + .isTrue(); + }); + } + + private static String getEnumName(String name) { + return formatSimple("%s%s", ENUM_NAME_PREFIX, name); + } + + private static int getIntFieldChecked(Field field) { + try { + return field.getInt(null); + } catch (IllegalAccessException ex) { + throw new RuntimeException(ex); + } + } + + private static Stream<Field> getCujConstants() { + return Arrays.stream(Cuj.class.getDeclaredFields()) + .filter(f -> f.getName().startsWith("CUJ_") + && Modifier.isStatic(f.getModifiers()) + && f.getType() == int.class); + } +} diff --git a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java index 1b9717a6dfc5..1a7117e3b4a1 100644 --- a/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/FrameTrackerTest.java @@ -22,9 +22,8 @@ import static android.view.SurfaceControl.JankData.JANK_SURFACEFLINGER_DEADLINE_ import static com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import static com.android.internal.jank.FrameTracker.ViewRootWrapper; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_WALLPAPER_TRANSITION; +import static com.android.internal.jank.Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; +import static com.android.internal.jank.Cuj.CUJ_WALLPAPER_TRANSITION; import static com.android.internal.util.FrameworkStatsLog.UI_INTERACTION_FRAME_INFO_REPORTED; import static com.google.common.truth.Truth.assertThat; @@ -49,15 +48,14 @@ import android.view.SurfaceControl.OnJankDataListener; import android.view.View; import android.view.ViewAttachTestActivity; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.filters.SmallTest; -import androidx.test.rule.ActivityTestRule; import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; import com.android.internal.jank.FrameTracker.StatsLogWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.InteractionJankMonitor.Configuration; -import com.android.internal.jank.InteractionJankMonitor.Session; import org.junit.Before; import org.junit.Rule; @@ -69,14 +67,14 @@ import java.util.concurrent.TimeUnit; @SmallTest public class FrameTrackerTest { - private static final String CUJ_POSTFIX = ""; + private static final String SESSION_NAME = "SessionName"; private static final long FRAME_TIME_60Hz = (long) 1e9 / 60; private ViewAttachTestActivity mActivity; @Rule - public ActivityTestRule<ViewAttachTestActivity> mRule = - new ActivityTestRule<>(ViewAttachTestActivity.class); + public ActivityScenarioRule<ViewAttachTestActivity> mRule = + new ActivityScenarioRule<>(ViewAttachTestActivity.class); private ThreadedRendererWrapper mRenderer; private FrameMetricsWrapper mWrapper; @@ -86,12 +84,13 @@ public class FrameTrackerTest { private StatsLogWrapper mStatsLog; private ArgumentCaptor<OnJankDataListener> mListenerCapture; private SurfaceControl mSurfaceControl; + private FrameTracker.FrameTrackerListener mTrackerListener; private ArgumentCaptor<Runnable> mRunnableArgumentCaptor; @Before public void setup() { // Prepare an activity for getting ThreadedRenderer later. - mActivity = mRule.getActivity(); + mRule.getScenario().onActivity(activity -> mActivity = activity); View view = mActivity.getWindow().getDecorView(); assertThat(view.isAttachedToWindow()).isTrue(); @@ -116,36 +115,38 @@ public class FrameTrackerTest { mChoreographer = mock(ChoreographerWrapper.class); mStatsLog = mock(StatsLogWrapper.class); mRunnableArgumentCaptor = ArgumentCaptor.forClass(Runnable.class); + mTrackerListener = mock(FrameTracker.FrameTrackerListener.class); } - private FrameTracker spyFrameTracker(int cuj, String postfix, boolean surfaceOnly) { - InteractionJankMonitor monitor = mock(InteractionJankMonitor.class); - Handler handler = mRule.getActivity().getMainThreadHandler(); - Session session = new Session(cuj, postfix); + private FrameTracker spyFrameTracker(boolean surfaceOnly) { + Handler handler = mActivity.getMainThreadHandler(); Configuration config = mock(Configuration.class); + when(config.getSessionName()).thenReturn(SESSION_NAME); when(config.isSurfaceOnly()).thenReturn(surfaceOnly); when(config.getSurfaceControl()).thenReturn(mSurfaceControl); when(config.shouldDeferMonitor()).thenReturn(true); when(config.getDisplayId()).thenReturn(42); - View view = mRule.getActivity().getWindow().getDecorView(); + View view = mActivity.getWindow().getDecorView(); Handler spyHandler = spy(new Handler(handler.getLooper())); when(config.getView()).thenReturn(surfaceOnly ? null : view); when(config.getHandler()).thenReturn(spyHandler); + when(config.logToStatsd()).thenReturn(true); + when(config.getStatsdInteractionType()).thenReturn(surfaceOnly + ? Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION) + : Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)); FrameTracker frameTracker = Mockito.spy( - new FrameTracker(monitor, session, spyHandler, mRenderer, mViewRootWrapper, + new FrameTracker(config, mRenderer, mViewRootWrapper, mSurfaceControlWrapper, mChoreographer, mWrapper, mStatsLog, /* traceThresholdMissedFrames= */ 1, /* traceThresholdFrameTimeMillis= */ -1, - /* FrameTrackerListener= */ null, config)); - doNothing().when(frameTracker).triggerPerfetto(); + mTrackerListener)); doNothing().when(frameTracker).postTraceStartMarker(mRunnableArgumentCaptor.capture()); return frameTracker; } @Test public void testOnlyFirstWindowFrameOverThreshold() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); // Just provide current timestamp anytime mWrapper asked for VSYNC_TIMESTAMP when(mWrapper.getMetric(FrameMetrics.VSYNC_TIMESTAMP)) @@ -169,11 +170,11 @@ public class FrameTrackerTest { sendFrame(tracker, 500, JANK_APP_DEADLINE_MISSED, 103L); verify(tracker).removeObservers(); - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(5000000L) /* maxFrameTimeNanos */, @@ -184,8 +185,7 @@ public class FrameTrackerTest { @Test public void testSfJank() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -206,12 +206,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, eq(40000000L) /* maxFrameTimeNanos */, @@ -222,8 +222,7 @@ public class FrameTrackerTest { @Test public void testFirstFrameJankyNoTrigger() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -243,13 +242,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); - // We detected a janky frame - trigger Perfetto - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(4000000L) /* maxFrameTimeNanos */, @@ -260,8 +258,7 @@ public class FrameTrackerTest { @Test public void testOtherFrameOverThreshold() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -282,12 +279,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, eq(40000000L) /* maxFrameTimeNanos */, @@ -298,8 +295,7 @@ public class FrameTrackerTest { @Test public void testLastFrameOverThresholdBeforeEnd() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -323,12 +319,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // We detected a janky frame - trigger Perfetto - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, eq(50000000L) /* maxFrameTimeNanos */, @@ -342,8 +338,7 @@ public class FrameTrackerTest { */ @Test public void testNoOvercountingAfterEnd() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -367,11 +362,11 @@ public class FrameTrackerTest { sendFrame(tracker, 50, JANK_APP_DEADLINE_MISSED, 103L); verify(tracker).removeObservers(); - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE]), + eq(Cuj.getStatsdInteractionType(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(4000000L) /* maxFrameTimeNanos */, @@ -382,8 +377,7 @@ public class FrameTrackerTest { @Test public void testBeginCancel() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -402,13 +396,12 @@ public class FrameTrackerTest { tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); verify(tracker).removeObservers(); // Since the tracker has been cancelled, shouldn't trigger perfetto. - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); } @Test public void testCancelIfEndVsyncIdEqualsToBeginVsyncId() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -426,13 +419,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // Should never trigger Perfetto since it is a cancel. - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); } @Test public void testCancelIfEndVsyncIdLessThanBeginVsyncId() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -450,13 +442,12 @@ public class FrameTrackerTest { verify(tracker).removeObservers(); // Should never trigger Perfetto since it is a cancel. - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); } @Test public void testCancelWhenSessionNeverBegun() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); tracker.cancel(FrameTracker.REASON_CANCEL_NORMAL); verify(tracker).removeObservers(); @@ -464,8 +455,7 @@ public class FrameTrackerTest { @Test public void testEndWhenSessionNeverBegun() { - FrameTracker tracker = spyFrameTracker( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX, /* surfaceOnly= */ false); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ false); tracker.end(FrameTracker.REASON_END_NORMAL); verify(tracker).removeObservers(); @@ -473,8 +463,7 @@ public class FrameTrackerTest { @Test public void testSurfaceOnlyOtherFrameJanky() { - FrameTracker tracker = spyFrameTracker( - CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -495,12 +484,12 @@ public class FrameTrackerTest { sendFrame(tracker, JANK_NONE, 103L); verify(mSurfaceControlWrapper).removeJankStatsListener(any()); - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), + eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)), eq(2L) /* totalFrames */, eq(1L) /* missedFrames */, eq(0L) /* maxFrameTimeNanos */, @@ -511,8 +500,7 @@ public class FrameTrackerTest { @Test public void testSurfaceOnlyFirstFrameJanky() { - FrameTracker tracker = spyFrameTracker( - CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -533,12 +521,12 @@ public class FrameTrackerTest { sendFrame(tracker, JANK_NONE, 103L); verify(mSurfaceControlWrapper).removeJankStatsListener(any()); - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), + eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(0L) /* maxFrameTimeNanos */, @@ -549,8 +537,7 @@ public class FrameTrackerTest { @Test public void testSurfaceOnlyLastFrameJanky() { - FrameTracker tracker = spyFrameTracker( - CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); @@ -571,12 +558,12 @@ public class FrameTrackerTest { sendFrame(tracker, JANK_APP_DEADLINE_MISSED, 103L); verify(mSurfaceControlWrapper).removeJankStatsListener(any()); - verify(tracker, never()).triggerPerfetto(); + verify(mTrackerListener, never()).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), + eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)), eq(2L) /* totalFrames */, eq(0L) /* missedFrames */, eq(0L) /* maxFrameTimeNanos */, @@ -587,8 +574,7 @@ public class FrameTrackerTest { @Test public void testMaxSuccessiveMissedFramesCount() { - FrameTracker tracker = spyFrameTracker( - CUJ_WALLPAPER_TRANSITION, CUJ_POSTFIX, /* surfaceOnly= */ true); + FrameTracker tracker = spyFrameTracker(/* surfaceOnly= */ true); when(mChoreographer.getVsyncId()).thenReturn(100L); tracker.begin(); mRunnableArgumentCaptor.getValue().run(); @@ -604,11 +590,11 @@ public class FrameTrackerTest { sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 106L); sendFrame(tracker, JANK_SURFACEFLINGER_DEADLINE_MISSED, 107L); verify(mSurfaceControlWrapper).removeJankStatsListener(any()); - verify(tracker).triggerPerfetto(); + verify(mTrackerListener).triggerPerfetto(any()); verify(mStatsLog).write(eq(UI_INTERACTION_FRAME_INFO_REPORTED), eq(42), /* displayId */ eq(DisplayRefreshRate.REFRESH_RATE_60_HZ), - eq(CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_WALLPAPER_TRANSITION]), + eq(Cuj.getStatsdInteractionType(CUJ_WALLPAPER_TRANSITION)), eq(6L) /* totalFrames */, eq(5L) /* missedFrames */, eq(0L) /* maxFrameTimeNanos */, diff --git a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java index b61f995724e5..68095e5eb46c 100644 --- a/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java +++ b/core/tests/coretests/src/com/android/internal/jank/InteractionJankMonitorTest.java @@ -20,23 +20,9 @@ import static android.text.TextUtils.formatSimple; import static com.android.internal.jank.FrameTracker.REASON_CANCEL_TIMEOUT; import static com.android.internal.jank.FrameTracker.REASON_END_NORMAL; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_ADD; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_APP_START; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_APPEAR; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_REMOVE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_EXPAND; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_ROW_SWIPE; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_NOTIFICATION_SHADE_SCROLL_FLING; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_TO_STATSD_INTERACTION_TYPE; -import static com.android.internal.jank.InteractionJankMonitor.MAX_LENGTH_OF_CUJ_NAME; -import static com.android.internal.jank.InteractionJankMonitor.getNameOfCuj; +import static com.android.internal.jank.InteractionJankMonitor.Configuration.generateSessionName; import static com.google.common.truth.Truth.assertThat; -import static com.google.common.truth.Truth.assertWithMessage; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -52,12 +38,11 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.SystemClock; import android.provider.DeviceConfig; -import android.util.SparseArray; import android.view.View; import android.view.ViewAttachTestActivity; +import androidx.test.ext.junit.rules.ActivityScenarioRule; import androidx.test.filters.SmallTest; -import androidx.test.rule.ActivityTestRule; import com.android.internal.jank.FrameTracker.ChoreographerWrapper; import com.android.internal.jank.FrameTracker.FrameMetricsWrapper; @@ -66,101 +51,54 @@ import com.android.internal.jank.FrameTracker.SurfaceControlWrapper; import com.android.internal.jank.FrameTracker.ThreadedRendererWrapper; import com.android.internal.jank.FrameTracker.ViewRootWrapper; import com.android.internal.jank.InteractionJankMonitor.Configuration; -import com.android.internal.jank.InteractionJankMonitor.Session; -import com.android.internal.util.FrameworkStatsLog; import com.google.common.truth.Expect; import org.junit.Before; -import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; -import java.lang.reflect.Field; -import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; @SmallTest public class InteractionJankMonitorTest { - private static final String CUJ_POSTFIX = ""; - private static final SparseArray<String> ENUM_NAME_EXCEPTION_MAP = new SparseArray<>(); - private static final String ENUM_NAME_PREFIX = - "UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__"; - - private static final ArrayList<String> DEPRECATED_VALUES = new ArrayList<>(); - private ViewAttachTestActivity mActivity; private View mView; + private Handler mHandler; private HandlerThread mWorker; @Rule - public ActivityTestRule<ViewAttachTestActivity> mRule = - new ActivityTestRule<>(ViewAttachTestActivity.class); + public ActivityScenarioRule<ViewAttachTestActivity> mRule = + new ActivityScenarioRule<>(ViewAttachTestActivity.class); @Rule public final Expect mExpect = Expect.create(); - @BeforeClass - public static void initialize() { - ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_ADD, getEnumName("SHADE_NOTIFICATION_ADD")); - ENUM_NAME_EXCEPTION_MAP.put(CUJ_NOTIFICATION_APP_START, getEnumName("SHADE_APP_LAUNCH")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_HEADS_UP_APPEAR, getEnumName("SHADE_HEADS_UP_APPEAR")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_HEADS_UP_DISAPPEAR, getEnumName("SHADE_HEADS_UP_DISAPPEAR")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_REMOVE, getEnumName("SHADE_NOTIFICATION_REMOVE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, getEnumName("NOTIFICATION_SHADE_SWIPE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_QS_EXPAND_COLLAPSE, getEnumName("SHADE_QS_EXPAND_COLLAPSE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_QS_SCROLL_SWIPE, getEnumName("SHADE_QS_SCROLL_SWIPE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_ROW_EXPAND, getEnumName("SHADE_ROW_EXPAND")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_ROW_SWIPE, getEnumName("SHADE_ROW_SWIPE")); - ENUM_NAME_EXCEPTION_MAP.put( - CUJ_NOTIFICATION_SHADE_SCROLL_FLING, getEnumName("SHADE_SCROLL_FLING")); - DEPRECATED_VALUES.add(ENUM_NAME_PREFIX + "IME_INSETS_ANIMATION"); - } - - private static String getEnumName(String name) { - return formatSimple("%s%s", ENUM_NAME_PREFIX, name); - } - @Before public void setup() { - // Prepare an activity for getting ThreadedRenderer later. - mActivity = mRule.getActivity(); + mRule.getScenario().onActivity(activity -> mActivity = activity); mView = mActivity.getWindow().getDecorView(); assertThat(mView.isAttachedToWindow()).isTrue(); - Handler handler = spy(new Handler(mActivity.getMainLooper())); - doReturn(true).when(handler).sendMessageAtTime(any(), anyLong()); + mHandler = spy(new Handler(mActivity.getMainLooper())); + doReturn(true).when(mHandler).sendMessageAtTime(any(), anyLong()); mWorker = mock(HandlerThread.class); - doReturn(handler).when(mWorker).getThreadHandler(); + doReturn(mHandler).when(mWorker).getThreadHandler(); } @Test public void testBeginEnd() { InteractionJankMonitor monitor = createMockedInteractionJankMonitor(); - FrameTracker tracker = createMockedFrameTracker(monitor, null); - doReturn(tracker).when(monitor).createFrameTracker(any(), any()); + FrameTracker tracker = createMockedFrameTracker(); + doReturn(tracker).when(monitor).createFrameTracker(any()); doNothing().when(tracker).begin(); doReturn(true).when(tracker).end(anyInt()); // Simulate a trace session and see if begin / end are invoked. - assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); + assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).begin(); - assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); + assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).end(REASON_END_NORMAL); } @@ -172,10 +110,10 @@ public class InteractionJankMonitorTest { propertiesValues.put("enabled", "false"); DeviceConfig.Properties properties = new DeviceConfig.Properties( DeviceConfig.NAMESPACE_INTERACTION_JANK_MONITOR, propertiesValues); - monitor.getPropertiesChangedListener().onPropertiesChanged(properties); + monitor.updateProperties(properties); - assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); - assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); } @Test @@ -185,145 +123,57 @@ public class InteractionJankMonitorTest { assertThat(view.isAttachedToWindow()).isFalse(); // Should return false if the view passed in is not attached to window yet. - assertThat(monitor.begin(view, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); - assertThat(monitor.end(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.begin(view, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); + assertThat(monitor.end(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isFalse(); } @Test public void testBeginTimeout() { ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class); InteractionJankMonitor monitor = createMockedInteractionJankMonitor(); - FrameTracker tracker = createMockedFrameTracker(monitor, null); - doReturn(tracker).when(monitor).createFrameTracker(any(), any()); + FrameTracker tracker = createMockedFrameTracker(); + doReturn(tracker).when(monitor).createFrameTracker(any()); doNothing().when(tracker).begin(); doReturn(true).when(tracker).cancel(anyInt()); - assertThat(monitor.begin(mView, CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); + assertThat(monitor.begin(mView, Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE)).isTrue(); verify(tracker).begin(); - verify(monitor).scheduleTimeoutAction(anyInt(), anyLong(), captor.capture()); + verify(monitor).scheduleTimeoutAction(any(), captor.capture()); Runnable runnable = captor.getValue(); assertThat(runnable).isNotNull(); - mWorker.getThreadHandler().removeCallbacks(runnable); runnable.run(); - verify(monitor).cancel(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT); + verify(monitor).cancel(Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, REASON_CANCEL_TIMEOUT); verify(tracker).cancel(REASON_CANCEL_TIMEOUT); } @Test - public void testCujTypeEnumCorrectlyDefined() throws Exception { - List<Field> cujEnumFields = - Arrays.stream(InteractionJankMonitor.class.getDeclaredFields()) - .filter(field -> field.getName().startsWith("CUJ_") - && Modifier.isStatic(field.getModifiers()) - && field.getType() == int.class) - .collect(Collectors.toList()); - - HashSet<Integer> allValues = new HashSet<>(); - for (Field field : cujEnumFields) { - int fieldValue = field.getInt(null); - assertWithMessage( - "Field %s must have a mapping to a value in CUJ_TO_STATSD_INTERACTION_TYPE", - field.getName()) - .that(fieldValue < CUJ_TO_STATSD_INTERACTION_TYPE.length) - .isTrue(); - assertWithMessage("All CujType values must be unique. Field %s repeats existing value.", - field.getName()) - .that(allValues.add(fieldValue)) - .isTrue(); - } - } - - @Test - public void testCujsMapToEnumsCorrectly() { - List<Field> cujs = Arrays.stream(InteractionJankMonitor.class.getDeclaredFields()) - .filter(f -> f.getName().startsWith("CUJ_") - && Modifier.isStatic(f.getModifiers()) - && f.getType() == int.class) - .collect(Collectors.toList()); - - Map<Integer, String> enumsMap = Arrays.stream(FrameworkStatsLog.class.getDeclaredFields()) - .filter(f -> f.getName().startsWith(ENUM_NAME_PREFIX) - && !DEPRECATED_VALUES.contains(f.getName()) - && Modifier.isStatic(f.getModifiers()) - && f.getType() == int.class) - .collect(Collectors.toMap(this::getIntFieldChecked, Field::getName)); - - assertThat(enumsMap.size() - 1).isEqualTo(cujs.size()); - - cujs.forEach(f -> { - final int cuj = getIntFieldChecked(f); - final String cujName = f.getName(); - final String expectedEnumName = ENUM_NAME_EXCEPTION_MAP.contains(cuj) - ? ENUM_NAME_EXCEPTION_MAP.get(cuj) - : formatSimple("%s%s", ENUM_NAME_PREFIX, cujName.substring(4)); - final int enumKey = CUJ_TO_STATSD_INTERACTION_TYPE[cuj]; - final String enumName = enumsMap.get(enumKey); - final String expectedNameOfCuj = formatSimple("CUJ_%s", getNameOfCuj(cuj)); - - mExpect - .withMessage(formatSimple( - "%s (%d) not matches %s (%d)", cujName, cuj, enumName, enumKey)) - .that(expectedEnumName.equals(enumName)) - .isTrue(); - mExpect - .withMessage( - formatSimple("getNameOfCuj(%d) not matches: %s, expected=%s", - cuj, cujName, expectedNameOfCuj)) - .that(cujName.equals(expectedNameOfCuj)) - .isTrue(); - }); - } - - @Test - public void testCujNameLimit() { - Arrays.stream(InteractionJankMonitor.class.getDeclaredFields()) - .filter(f -> f.getName().startsWith("CUJ_") - && Modifier.isStatic(f.getModifiers()) - && f.getType() == int.class) - .forEach(f -> { - final int cuj = getIntFieldChecked(f); - mExpect - .withMessage(formatSimple( - "Too long CUJ(%d) name: %s", cuj, getNameOfCuj(cuj))) - .that(getNameOfCuj(cuj).length()) - .isAtMost(MAX_LENGTH_OF_CUJ_NAME); - }); - } - - @Test public void testSessionNameLengthLimit() { - final int cujType = CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; - final String cujName = getNameOfCuj(cujType); + final int cujType = Cuj.CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE; + final String cujName = Cuj.getNameOfCuj(cujType); final String cujTag = "ThisIsTheCujTag"; final String tooLongTag = cujTag.repeat(10); // Normal case, no postfix. - Session noPostfix = new Session(cujType, ""); - assertThat(noPostfix.getName()).isEqualTo(formatSimple("J<%s>", cujName)); + assertThat(generateSessionName(cujName, "")).isEqualTo(formatSimple("J<%s>", cujName)); // Normal case, with postfix. - Session withPostfix = new Session(cujType, cujTag); - assertThat(withPostfix.getName()).isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag)); + assertThat(generateSessionName(cujName, cujTag)) + .isEqualTo(formatSimple("J<%s::%s>", cujName, cujTag)); // Since the length of the cuj name is tested in another test, no need to test it here. // Too long postfix case, should trim the postfix and keep the cuj name completed. final String expectedTrimmedName = formatSimple("J<%s::%s>", cujName, "ThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThisIsTheCujTagThi..."); - Session longPostfix = new Session(cujType, tooLongTag); - assertThat(longPostfix.getName()).isEqualTo(expectedTrimmedName); + assertThat(generateSessionName(cujName, tooLongTag)).isEqualTo(expectedTrimmedName); } private InteractionJankMonitor createMockedInteractionJankMonitor() { InteractionJankMonitor monitor = spy(new InteractionJankMonitor(mWorker)); - doReturn(true).when(monitor).shouldMonitor(anyInt()); + doReturn(true).when(monitor).shouldMonitor(); return monitor; } - private FrameTracker createMockedFrameTracker(InteractionJankMonitor monitor, - FrameTracker.FrameTrackerListener listener) { - Session session = spy(new Session(CUJ_NOTIFICATION_SHADE_EXPAND_COLLAPSE, CUJ_POSTFIX)); - doReturn(false).when(session).logToStatsd(); - + private FrameTracker createMockedFrameTracker() { ThreadedRendererWrapper threadedRenderer = mock(ThreadedRendererWrapper.class); doNothing().when(threadedRenderer).addObserver(any()); doNothing().when(threadedRenderer).removeObserver(any()); @@ -342,27 +192,19 @@ public class InteractionJankMonitorTest { Configuration configuration = mock(Configuration.class); when(configuration.isSurfaceOnly()).thenReturn(false); when(configuration.getView()).thenReturn(mView); - when(configuration.getHandler()).thenReturn(mView.getHandler()); when(configuration.getDisplayId()).thenReturn(42); + when(configuration.logToStatsd()).thenReturn(false); + when(configuration.getHandler()).thenReturn(mHandler); - FrameTracker tracker = spy(new FrameTracker(monitor, session, mWorker.getThreadHandler(), + FrameTracker tracker = spy(new FrameTracker(configuration, threadedRenderer, viewRoot, surfaceControl, choreographer, new FrameMetricsWrapper(), new StatsLogWrapper(null), /* traceThresholdMissedFrames= */ 1, - /* traceThresholdFrameTimeMillis= */ -1, listener, configuration)); + /* traceThresholdFrameTimeMillis= */ -1, + /* listener */ null)); doNothing().when(tracker).postTraceStartMarker(any()); - doNothing().when(tracker).triggerPerfetto(); - doReturn(configuration.getHandler()).when(tracker).getHandler(); return tracker; } - - private int getIntFieldChecked(Field field) { - try { - return field.getInt(null); - } catch (IllegalAccessException ex) { - throw new RuntimeException(ex); - } - } } diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json index 2237ba1924db..aaddf0e50ddd 100644 --- a/data/etc/services.core.protolog.json +++ b/data/etc/services.core.protolog.json @@ -169,6 +169,12 @@ "group": "WM_DEBUG_WINDOW_ORGANIZER", "at": "com\/android\/server\/wm\/TaskOrganizerController.java" }, + "-1961637874": { + "message": "DeferredDisplayUpdater: applying DisplayInfo immediately", + "level": "DEBUG", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" + }, "-1949279037": { "message": "Attempted to add input method window with bad token %s. Aborting.", "level": "WARN", @@ -313,6 +319,12 @@ "group": "WM_DEBUG_RESIZE", "at": "com\/android\/server\/wm\/WindowState.java" }, + "-1818910559": { + "message": "DeferredDisplayUpdater: applied DisplayInfo after deferring", + "level": "DEBUG", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" + }, "-1814361639": { "message": "Set IME snapshot position: (%d, %d)", "level": "INFO", @@ -595,6 +607,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "-1518132958": { + "message": "fractionRendered boundsOverSource=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-1517908912": { "message": "requestScrollCapture: caught exception dispatching to window.token=%s", "level": "WARN", @@ -961,6 +979,12 @@ "group": "WM_DEBUG_CONTENT_RECORDING", "at": "com\/android\/server\/wm\/ContentRecorder.java" }, + "-1209762265": { + "message": "Registering listener=%s with id=%d for window=%s with %s", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-1209252064": { "message": "Clear animatingExit: reason=clearAnimatingFlags win=%s", "level": "DEBUG", @@ -1333,6 +1357,12 @@ "group": "WM_DEBUG_WINDOW_TRANSITIONS", "at": "com\/android\/server\/wm\/Transition.java" }, + "-888703350": { + "message": "Skipping %s", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "-883738232": { "message": "Adding more than one toast window for UID at a time.", "level": "WARN", @@ -1909,6 +1939,12 @@ "group": "WM_DEBUG_FOCUS_LIGHT", "at": "com\/android\/server\/wm\/DisplayContent.java" }, + "-415346336": { + "message": "DeferredDisplayUpdater: partially applying DisplayInfo immediately", + "level": "DEBUG", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" + }, "-401282500": { "message": "destroyIfPossible: r=%s destroy returned removed=%s", "level": "DEBUG", @@ -1957,6 +1993,12 @@ "group": "WM_DEBUG_APP_TRANSITIONS", "at": "com\/android\/server\/wm\/AppTransitionController.java" }, + "-376950429": { + "message": "DeferredDisplayUpdater: deferring DisplayInfo update", + "level": "DEBUG", + "group": "WM_DEBUG_WINDOW_TRANSITIONS", + "at": "com\/android\/server\/wm\/DeferredDisplayUpdater.java" + }, "-374767836": { "message": "setAppVisibility(%s, visible=%b): %s visible=%b mVisibleRequested=%b Callers=%s", "level": "VERBOSE", @@ -2803,6 +2845,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "360319850": { + "message": "fractionRendered scale=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "364992694": { "message": "freezeDisplayRotation: current rotation=%d, new rotation=%d, caller=%s", "level": "VERBOSE", @@ -2983,6 +3031,12 @@ "group": "WM_DEBUG_BACK_PREVIEW", "at": "com\/android\/server\/wm\/BackNavigationController.java" }, + "532771960": { + "message": "Adding untrusted state listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "535103992": { "message": "Wallpaper may change! Adjusting", "level": "VERBOSE", @@ -3061,6 +3115,12 @@ "group": "WM_DEBUG_DREAM", "at": "com\/android\/server\/wm\/ActivityTaskManagerService.java" }, + "605179032": { + "message": "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "608694300": { "message": " NEW SURFACE SESSION %s", "level": "INFO", @@ -3289,6 +3349,12 @@ "group": "WM_SHOW_TRANSACTIONS", "at": "com\/android\/server\/wm\/WindowState.java" }, + "824532141": { + "message": "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f minFractionRendered=%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "829434921": { "message": "Draw state now committed in %s", "level": "VERBOSE", @@ -3583,6 +3649,12 @@ "group": "WM_SHOW_SURFACE_ALLOC", "at": "com\/android\/server\/wm\/ScreenRotationAnimation.java" }, + "1090378847": { + "message": "Checking %d windows", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1100065297": { "message": "Attempted to get IME policy of a display that does not exist: %d", "level": "WARN", @@ -3715,6 +3787,12 @@ "group": "WM_DEBUG_FOCUS", "at": "com\/android\/server\/wm\/ActivityRecord.java" }, + "1251721200": { + "message": "unregister failed, couldn't find deathRecipient for %s with id=%d", + "level": "ERROR", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1252594551": { "message": "Window types in WindowContext and LayoutParams.type should match! Type from LayoutParams is %d, but type from WindowContext is %d", "level": "WARN", @@ -3853,6 +3931,12 @@ "group": "WM_DEBUG_ORIENTATION", "at": "com\/android\/server\/wm\/TaskDisplayArea.java" }, + "1382634842": { + "message": "Unregistering listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1393721079": { "message": "Starting remote display change: from [rot = %d], to [%dx%d, rot = %d]", "level": "VERBOSE", @@ -3901,6 +3985,12 @@ "group": "WM_ERROR", "at": "com\/android\/server\/wm\/WindowManagerService.java" }, + "1445704347": { + "message": "coveredRegionsAbove updated with %s frame:%s region:%s", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1448683958": { "message": "Override pending remote transitionSet=%b adapter=%s", "level": "INFO", @@ -4201,6 +4291,12 @@ "group": "WM_DEBUG_RECENTS_ANIMATIONS", "at": "com\/android\/server\/wm\/RecentsAnimation.java" }, + "1786463281": { + "message": "Adding trusted state listener=%s with id=%d", + "level": "DEBUG", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1789321832": { "message": "Then token:%s is invalid. It might be removed", "level": "WARN", @@ -4375,6 +4471,12 @@ "group": "WM_DEBUG_TASKS", "at": "com\/android\/server\/wm\/RootWindowContainer.java" }, + "1955470028": { + "message": "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s scale=%f,%f", + "level": "VERBOSE", + "group": "WM_DEBUG_TPL", + "at": "com\/android\/server\/wm\/TrustedPresentationListenerController.java" + }, "1964565370": { "message": "Starting remote animation", "level": "INFO", @@ -4659,6 +4761,9 @@ "WM_DEBUG_TASKS": { "tag": "WindowManager" }, + "WM_DEBUG_TPL": { + "tag": "WindowManager" + }, "WM_DEBUG_WALLPAPER": { "tag": "WindowManager" }, diff --git a/graphics/java/android/graphics/Paint.java b/graphics/java/android/graphics/Paint.java index 92c4de6490a3..f10cdb82022e 100644 --- a/graphics/java/android/graphics/Paint.java +++ b/graphics/java/android/graphics/Paint.java @@ -1739,10 +1739,6 @@ public class Paint { /** * Get the elegant metrics flag. * - * From API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM}, the default value will be true by - * default if the app has a target SDK of API {@link Build.VERSION_CODES#VANILLA_ICE_CREAM} or - * later. - * * @return true if elegant metrics are enabled for text drawing. */ public boolean isElegantTextHeight() { 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 dc413b059fd7..a32b435ff99e 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 @@ -28,7 +28,7 @@ import android.view.RemoteAnimationTarget; import android.window.IBackAnimationRunner; import android.window.IOnBackInvokedCallback; -import com.android.internal.jank.InteractionJankMonitor; +import com.android.internal.jank.Cuj.CujType; import com.android.wm.shell.common.InteractionJankMonitorUtils; /** @@ -42,7 +42,7 @@ public class BackAnimationRunner { private final IOnBackInvokedCallback mCallback; private final IRemoteAnimationRunner mRunner; - private final @InteractionJankMonitor.CujType int mCujType; + private final @CujType int mCujType; private final Context mContext; // Whether we are waiting to receive onAnimationStart @@ -55,7 +55,7 @@ public class BackAnimationRunner { @NonNull IOnBackInvokedCallback callback, @NonNull IRemoteAnimationRunner runner, @NonNull Context context, - @InteractionJankMonitor.CujType int cujType) { + @CujType int cujType) { mCallback = callback; mRunner = runner; mCujType = cujType; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java index ec344d345139..86f00b83cadd 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/InteractionJankMonitorUtils.java @@ -23,6 +23,7 @@ import android.text.TextUtils; import android.view.SurfaceControl; import android.view.View; +import com.android.internal.jank.Cuj.CujType; import com.android.internal.jank.InteractionJankMonitor; /** Utils class for simplfy InteractionJank trancing call */ @@ -31,11 +32,11 @@ public class InteractionJankMonitorUtils { /** * Begin a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link CujType}. * @param view the view to trace * @param tag the tag to distinguish different flow of same type CUJ. */ - public static void beginTracing(@InteractionJankMonitor.CujType int cujType, + public static void beginTracing(@CujType int cujType, @NonNull View view, @Nullable String tag) { final InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withView(cujType, view); @@ -48,12 +49,12 @@ public class InteractionJankMonitorUtils { /** * Begin a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link CujType}. * @param context the context * @param surface the surface to trace * @param tag the tag to distinguish different flow of same type CUJ. */ - public static void beginTracing(@InteractionJankMonitor.CujType int cujType, + public static void beginTracing(@CujType int cujType, @NonNull Context context, @NonNull SurfaceControl surface, @Nullable String tag) { final InteractionJankMonitor.Configuration.Builder builder = InteractionJankMonitor.Configuration.Builder.withSurface(cujType, context, surface); @@ -66,18 +67,18 @@ public class InteractionJankMonitorUtils { /** * End a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link CujType}. */ - public static void endTracing(@InteractionJankMonitor.CujType int cujType) { + public static void endTracing(@CujType int cujType) { InteractionJankMonitor.getInstance().end(cujType); } /** * Cancel the trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link CujType}. */ - public static void cancelTracing(@InteractionJankMonitor.CujType int cujType) { + public static void cancelTracing(@CujType int cujType) { InteractionJankMonitor.getInstance().cancel(cujType); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java index 38ce16489b06..d157ca837608 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedDisplayAreaOrganizer.java @@ -16,8 +16,8 @@ package com.android.wm.shell.onehanded; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_ENTER_TRANSITION; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_ONE_HANDED_EXIT_TRANSITION; +import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_ENTER_TRANSITION; +import static com.android.internal.jank.Cuj.CUJ_ONE_HANDED_EXIT_TRANSITION; import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_EXIT; import static com.android.wm.shell.onehanded.OneHandedAnimationController.TRANSITION_DIRECTION_TRIGGER; @@ -37,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.android.internal.jank.Cuj.CujType; import com.android.internal.jank.InteractionJankMonitor; import com.android.wm.shell.R; import com.android.wm.shell.common.DisplayLayout; @@ -327,7 +328,7 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mTransitionCallbacks.add(callback); } - void beginCUJTracing(@InteractionJankMonitor.CujType int cujType, @Nullable String tag) { + void beginCUJTracing(@CujType int cujType, @Nullable String tag) { final Map.Entry<WindowContainerToken, SurfaceControl> firstEntry = getDisplayAreaTokenMap().entrySet().iterator().next(); final InteractionJankMonitor.Configuration.Builder builder = @@ -339,11 +340,11 @@ public class OneHandedDisplayAreaOrganizer extends DisplayAreaOrganizer { mJankMonitor.begin(builder); } - void endCUJTracing(@InteractionJankMonitor.CujType int cujType) { + void endCUJTracing(@CujType int cujType) { mJankMonitor.end(cujType); } - void cancelCUJTracing(@InteractionJankMonitor.CujType int cujType) { + void cancelCUJTracing(@CujType int cujType) { mJankMonitor.cancel(cujType); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java index 0f168c7b4ce6..0367ba160605 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java @@ -255,6 +255,7 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { private ArrayList<TaskState> mOpeningTasks = null; private WindowContainerToken mPipTask = null; + private int mPipTaskId = -1; private WindowContainerToken mRecentsTask = null; private int mRecentsTaskId = -1; private TransitionInfo mInfo = null; @@ -904,12 +905,14 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { @Override public void setFinishTaskTransaction(int taskId, PictureInPictureSurfaceTransaction finishTransaction, SurfaceControl overlay) { - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, - "[%d] RecentsController.setFinishTaskTransaction: taskId=%d", - mInstanceId, taskId); mExecutor.execute(() -> { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "[%d] RecentsController.setFinishTaskTransaction: taskId=%d," + + " [mFinishCB is non-null]=%b", + mInstanceId, taskId, mFinishCB != null); if (mFinishCB == null) return; mPipTransaction = finishTransaction; + mPipTaskId = taskId; }); } @@ -1003,10 +1006,35 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { // the leaf-tasks are not "independent" so aren't hidden by normal setup). t.hide(mPausingTasks.get(i).mTaskSurface); } - if (mPipTask != null && mPipTransaction != null && sendUserLeaveHint) { - t.show(mInfo.getChange(mPipTask).getLeash()); - PictureInPictureSurfaceTransaction.apply(mPipTransaction, - mInfo.getChange(mPipTask).getLeash(), t); + if (mPipTransaction != null && sendUserLeaveHint) { + SurfaceControl pipLeash = null; + if (mPipTask != null) { + pipLeash = mInfo.getChange(mPipTask).getLeash(); + } else if (mPipTaskId != -1) { + // find a task with taskId from #setFinishTaskTransaction() + for (TransitionInfo.Change change : mInfo.getChanges()) { + if (change.getTaskInfo() != null + && change.getTaskInfo().taskId == mPipTaskId) { + pipLeash = change.getLeash(); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.finishInner:" + + " found a change with taskId=%d", mPipTaskId); + } + } + } + if (pipLeash == null) { + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.finishInner: no valid PiP leash;" + + "mPipTransaction=%s, mPipTask=%s, mPipTaskId=%d", + mPipTransaction.toString(), mPipTask.toString(), mPipTaskId); + } else { + t.show(pipLeash); + PictureInPictureSurfaceTransaction.apply(mPipTransaction, pipLeash, t); + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, + "RecentsController.finishInner: PiP transaction %s merged", + mPipTransaction); + } + mPipTaskId = -1; mPipTask = null; mPipTransaction = null; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java index ce7fef2d1fdf..eb301192c90b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java @@ -50,7 +50,6 @@ import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; import com.android.wm.shell.activityembedding.ActivityEmbeddingController; import com.android.wm.shell.common.split.SplitScreenUtils; -import com.android.wm.shell.desktopmode.DesktopModeStatus; import com.android.wm.shell.desktopmode.DesktopTasksController; import com.android.wm.shell.keyguard.KeyguardTransitionHandler; import com.android.wm.shell.pip.PipTransitionController; @@ -298,7 +297,7 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, mixed.mLeftoversHandler = handler.first; mActiveTransitions.add(mixed); return handler.second; - } else if (mUnfoldHandler != null && mUnfoldHandler.hasUnfold(request)) { + } else if (mUnfoldHandler != null && mUnfoldHandler.shouldPlayUnfoldAnimation(request)) { final WindowContainerTransaction wct = mUnfoldHandler.handleRequest(transition, request); if (wct != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java index 07c54293111c..20ff79f7318e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -106,7 +106,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { - if (hasUnfold(info) && transition != mTransition) { + if (shouldPlayUnfoldAnimation(info) && transition != mTransition) { // Take over transition that has unfold, we might receive it if no other handler // accepted request in handleRequest, e.g. for rotation + unfold or // TRANSIT_NONE + unfold transitions @@ -213,14 +213,36 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene } /** Whether `request` contains an unfold action. */ - public boolean hasUnfold(@NonNull TransitionRequestInfo request) { + public boolean shouldPlayUnfoldAnimation(@NonNull TransitionRequestInfo request) { + // Unfold animation won't play when animations are disabled + if (!ValueAnimator.areAnimatorsEnabled()) return false; + return (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null - && request.getDisplayChange().isPhysicalDisplayChanged()); + && isUnfoldDisplayChange(request.getDisplayChange())); + } + + private boolean isUnfoldDisplayChange( + @NonNull TransitionRequestInfo.DisplayChange displayChange) { + if (!displayChange.isPhysicalDisplayChanged()) { + return false; + } + + if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) { + return false; + } + + // Handle only unfolding, currently we don't have an animation when folding + final int endArea = + displayChange.getEndAbsBounds().width() * displayChange.getEndAbsBounds().height(); + final int startArea = displayChange.getStartAbsBounds().width() + * displayChange.getStartAbsBounds().height(); + + return endArea > startArea; } /** Whether `transitionInfo` contains an unfold action. */ - public boolean hasUnfold(@NonNull TransitionInfo transitionInfo) { + public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) { // Unfold animation won't play when animations are disabled if (!ValueAnimator.areAnimatorsEnabled()) return false; @@ -250,7 +272,7 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public WindowContainerTransaction handleRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request) { - if (hasUnfold(request)) { + if (shouldPlayUnfoldAnimation(request)) { mTransition = transition; return new WindowContainerTransaction(); } diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt index d06cf775ca60..e272958d78f8 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt +++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt @@ -105,7 +105,8 @@ open class MapsEnterPipTest(flicker: LegacyFlickerTest) : AppsEnterPipTransition ) locationManager.setTestProviderEnabled(LocationManager.GPS_PROVIDER, true) mockLocationEnabled = true - mainHandler.post(updateLocation) + // postpone first location update to make sure GPS is set as test provider + mainHandler.postDelayed(updateLocation, 200) // normal app open through the Launcher All Apps // var mapsAddressOption = "Golden Gate Bridge" diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java index 6d73c12dc304..fc1fe1cd0acc 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java @@ -98,10 +98,13 @@ public class UnfoldTransitionHandlerTest { } @Test - public void handleRequest_physicalDisplayChange_handlesTransition() { + public void handleRequest_physicalDisplayChangeUnfold_handlesTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( - Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); + Display.DEFAULT_DISPLAY) + .setPhysicalDisplayChanged(true) + .setStartAbsBounds(new Rect(0, 0, 100, 100)) + .setEndAbsBounds(new Rect(0, 0, 200, 200)); TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); @@ -112,6 +115,23 @@ public class UnfoldTransitionHandlerTest { } @Test + public void handleRequest_physicalDisplayChangeFold_doesNotHandleTransition() { + ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); + TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( + Display.DEFAULT_DISPLAY) + .setPhysicalDisplayChanged(true) + .setStartAbsBounds(new Rect(0, 0, 200, 200)) + .setEndAbsBounds(new Rect(0, 0, 100, 100)); + TransitionRequestInfo requestInfo = new TransitionRequestInfo(TRANSIT_CHANGE, + triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); + + WindowContainerTransaction result = mUnfoldTransitionHandler.handleRequest(mTransition, + requestInfo); + + assertThat(result).isNull(); + } + + @Test public void handleRequest_noPhysicalDisplayChange_doesNotHandleTransition() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( @@ -306,7 +326,10 @@ public class UnfoldTransitionHandlerTest { private TransitionRequestInfo createUnfoldTransitionRequestInfo() { ActivityManager.RunningTaskInfo triggerTaskInfo = new ActivityManager.RunningTaskInfo(); TransitionRequestInfo.DisplayChange displayChange = new TransitionRequestInfo.DisplayChange( - Display.DEFAULT_DISPLAY).setPhysicalDisplayChanged(true); + Display.DEFAULT_DISPLAY) + .setPhysicalDisplayChanged(true) + .setStartAbsBounds(new Rect(0, 0, 100, 100)) + .setEndAbsBounds(new Rect(0, 0, 200, 200)); return new TransitionRequestInfo(TRANSIT_CHANGE, triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); } diff --git a/location/java/android/location/GnssNavigationMessage.java b/location/java/android/location/GnssNavigationMessage.java index 32e636f8658b..5e3f8033d116 100644 --- a/location/java/android/location/GnssNavigationMessage.java +++ b/location/java/android/location/GnssNavigationMessage.java @@ -78,7 +78,7 @@ public final class GnssNavigationMessage implements Parcelable { public static final int TYPE_GAL_F = 0x0602; /** * NavIC L5 C/A message contained in the structure. - * @deprecated Use {@link #TYPE_IRN_L5} instead. + * @deprecated deprecated. */ @Deprecated public static final int TYPE_IRN_L5CA = 0x0701; diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index 3c0b00262c70..07f63e5441af 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -62,3 +62,10 @@ flag { description: "Allows clients of privileged MediaRouter2 that hold INTERACT_ACROSS_USERS_FULL to control routing across users." bug: "288580225" } + +flag { + name: "enable_use_of_bluetooth_device_get_alias_for_mr2info_get_name" + namespace: "media_solutions" + description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos." + bug: "314324170" +} diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts index 60bf48c8b75e..8b136da04405 100644 --- a/packages/SettingsLib/Spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/build.gradle.kts @@ -29,7 +29,7 @@ val androidTop: String = File(rootDir, "../../../../..").canonicalPath allprojects { extra["androidTop"] = androidTop - extra["jetpackComposeVersion"] = "1.6.0-beta01" + extra["jetpackComposeVersion"] = "1.6.0-beta02" } subprojects { diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt index 90c7d46c3004..f4edb36b5e76 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt @@ -48,6 +48,7 @@ import com.android.settingslib.spa.gallery.preference.PreferencePageProvider import com.android.settingslib.spa.gallery.preference.SwitchPreferencePageProvider import com.android.settingslib.spa.gallery.preference.TwoTargetSwitchPreferencePageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider +import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider @@ -100,6 +101,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { SettingsExposedDropdownMenuCheckBoxProvider, SettingsTextFieldPasswordPageProvider, SearchScaffoldPageProvider, + SuwScaffoldPageProvider, CardPageProvider, CopyablePageProvider, ), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt index 1d897f77011e..6a2e5985a735 100644 --- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt @@ -43,6 +43,7 @@ import com.android.settingslib.spa.gallery.page.SettingsPagerPageProvider import com.android.settingslib.spa.gallery.page.SliderPageProvider import com.android.settingslib.spa.gallery.preference.PreferenceMainPageProvider import com.android.settingslib.spa.gallery.scaffold.SearchScaffoldPageProvider +import com.android.settingslib.spa.gallery.scaffold.SuwScaffoldPageProvider import com.android.settingslib.spa.gallery.ui.CategoryPageProvider import com.android.settingslib.spa.gallery.ui.CopyablePageProvider import com.android.settingslib.spa.gallery.ui.SpinnerPageProvider @@ -59,6 +60,7 @@ object HomePageProvider : SettingsPageProvider { OperateListPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), ArgumentPageProvider.buildInjectEntry("foo")!!.setLink(fromPage = owner).build(), SearchScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), + SuwScaffoldPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SliderPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SpinnerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), SettingsPagerPageProvider.buildInjectEntry().setLink(fromPage = owner).build(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt new file mode 100644 index 000000000000..6fc8de3ac49c --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/scaffold/SuwScaffoldPageProvider.kt @@ -0,0 +1,85 @@ +/* + * 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.spa.gallery.scaffold + +import android.os.Bundle +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.SignalCellularAlt +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import com.android.settingslib.spa.framework.common.SettingsEntryBuilder +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.common.createSettingsPage +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.gallery.R +import com.android.settingslib.spa.widget.illustration.Illustration +import com.android.settingslib.spa.widget.illustration.IllustrationModel +import com.android.settingslib.spa.widget.illustration.ResourceType +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.BottomAppBarButton +import com.android.settingslib.spa.widget.scaffold.SuwScaffold +import com.android.settingslib.spa.widget.ui.SettingsBody + +private const val TITLE = "Sample SuwScaffold" + +object SuwScaffoldPageProvider : SettingsPageProvider { + override val name = "SuwScaffold" + + private val owner = createSettingsPage() + + fun buildInjectEntry() = SettingsEntryBuilder.createInject(owner = owner) + .setUiLayoutFn { + Preference(object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + }) + } + + @Composable + override fun Page(arguments: Bundle?) { + Page() + } +} + +@Composable +private fun Page() { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = "Connect to mobile network", + actionButton = BottomAppBarButton("Next") {}, + dismissButton = BottomAppBarButton("Cancel") {}, + ) { + Column(Modifier.padding(SettingsDimension.itemPadding)) { + SettingsBody("To add another SIM, download a new eSIM.") + } + Illustration(object : IllustrationModel { + override val resId = R.drawable.accessibility_captioning_banner + override val resourceType = ResourceType.IMAGE + }) + Column(Modifier.padding(SettingsDimension.itemPadding)) { + SettingsBody("To add another SIM, download a new eSIM.") + } + Illustration(object : IllustrationModel { + override val resId = R.drawable.accessibility_captioning_banner + override val resourceType = ResourceType.IMAGE + }) + } +} diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts index acd90f3c4f4d..7eccfe5ed508 100644 --- a/packages/SettingsLib/Spa/spa/build.gradle.kts +++ b/packages/SettingsLib/Spa/spa/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { api("androidx.slice:slice-builders:1.1.0-alpha02") api("androidx.slice:slice-core:1.1.0-alpha02") api("androidx.slice:slice-view:1.1.0-alpha02") - api("androidx.compose.material3:material3:1.2.0-alpha11") + api("androidx.compose.material3:material3:1.2.0-alpha12") api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion") api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion") api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion") diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt index 5a1120e6c7a1..c143390f269c 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt @@ -37,11 +37,13 @@ object SettingsDimension { val itemPaddingAround = 8.dp val itemDividerHeight = 32.dp + val iconLarge = 48.dp + /** The size when app icon is displayed in list. */ val appIconItemSize = 32.dp /** The size when app icon is displayed in App info page. */ - val appIconInfoSize = 48.dp + val appIconInfoSize = iconLarge /** The vertical padding for buttons. */ val buttonPaddingVertical = 12.dp diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt index 3819a1075adb..e0dd4e17ce38 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsOutlinedTextField.kt @@ -34,6 +34,7 @@ import com.android.settingslib.spa.framework.theme.SettingsTheme fun SettingsOutlinedTextField( value: String, label: String, + errorMessage: String? = null, singleLine: Boolean = true, enabled: Boolean = true, onTextChange: (String) -> Unit, @@ -49,6 +50,12 @@ fun SettingsOutlinedTextField( }, singleLine = singleLine, enabled = enabled, + isError = errorMessage != null, + supportingText = { + if (errorMessage != null) { + Text(text = errorMessage) + } + } ) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt new file mode 100644 index 000000000000..354b95ddcbfe --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SuwScaffold.kt @@ -0,0 +1,144 @@ +/* + * 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.spa.widget.scaffold + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxWithConstraints +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.vector.ImageVector +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.toMediumWeight + +data class BottomAppBarButton( + val text: String, + val onClick: () -> Unit, +) + +@Composable +fun SuwScaffold( + imageVector: ImageVector, + title: String, + actionButton: BottomAppBarButton? = null, + dismissButton: BottomAppBarButton? = null, + content: @Composable ColumnScope.() -> Unit, +) { + ActivityTitle(title) + Scaffold { innerPadding -> + BoxWithConstraints( + Modifier + .padding(innerPadding) + .padding(top = SettingsDimension.itemPaddingAround) + ) { + // Use single column layout in portrait, two columns in landscape. + val useSingleColumn = maxWidth < maxHeight + if (useSingleColumn) { + Column { + Column( + Modifier + .weight(1f) + .verticalScroll(rememberScrollState()) + ) { + Header(imageVector, title) + content() + } + BottomBar(actionButton, dismissButton) + } + } else { + Column(Modifier.padding(horizontal = SettingsDimension.itemPaddingAround)) { + Row((Modifier.weight(1f))) { + Box(Modifier.weight(1f)) { + Header(imageVector, title) + } + Column( + Modifier + .weight(1f) + .verticalScroll(rememberScrollState())) { + content() + } + } + BottomBar(actionButton, dismissButton) + } + } + } + } +} + +@Composable +private fun Header( + imageVector: ImageVector, + title: String +) { + Column(Modifier.padding(SettingsDimension.itemPadding)) { + Icon( + imageVector = imageVector, + contentDescription = null, + modifier = Modifier + .padding(vertical = SettingsDimension.itemPaddingAround) + .size(SettingsDimension.iconLarge), + tint = MaterialTheme.colorScheme.primary, + ) + Text( + text = title, + modifier = Modifier.padding(vertical = SettingsDimension.itemPaddingVertical), + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.displaySmall, + ) + } +} + +@Composable +private fun BottomBar( + actionButton: BottomAppBarButton?, + dismissButton: BottomAppBarButton?, +) { + Row(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { + dismissButton?.apply { + TextButton(onClick) { + ActionText(text) + } + } + Spacer(modifier = Modifier.weight(1f)) + actionButton?.apply { + Button(onClick) { + ActionText(text) + } + } + } +} + +@Composable +private fun ActionText(text: String) { + Text( + text = text, + style = MaterialTheme.typography.bodyMedium.toMediumWeight(), + ) +} diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt new file mode 100644 index 000000000000..35c9f78ada68 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SuwScaffoldTest.kt @@ -0,0 +1,90 @@ +/* + * 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.spa.widget.scaffold + +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.SignalCellularAlt +import androidx.compose.material3.Text +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithText +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SuwScaffoldTest { + @get:Rule + val composeTestRule = createComposeRule() + + @Test + fun suwScaffold_titleIsDisplayed() { + composeTestRule.setContent { + SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) { + Text(text = "AAA") + Text(text = "BBB") + } + } + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + } + + @Test + fun suwScaffold_itemsAreDisplayed() { + composeTestRule.setContent { + SuwScaffold(imageVector = Icons.Outlined.SignalCellularAlt, title = TITLE) { + Text(text = "AAA") + Text(text = "BBB") + } + } + + composeTestRule.onNodeWithText("AAA").assertIsDisplayed() + composeTestRule.onNodeWithText("BBB").assertIsDisplayed() + } + + @Test + fun suwScaffold_actionButtonDisplayed() { + composeTestRule.setContent { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = TITLE, + actionButton = BottomAppBarButton(TEXT) {}, + ) {} + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + @Test + fun suwScaffold_dismissButtonDisplayed() { + composeTestRule.setContent { + SuwScaffold( + imageVector = Icons.Outlined.SignalCellularAlt, + title = TITLE, + dismissButton = BottomAppBarButton(TEXT) {}, + ) {} + } + + composeTestRule.onNodeWithText(TEXT).assertIsDisplayed() + } + + private companion object { + const val TITLE = "Title" + const val TEXT = "Text" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt index 3acc9ad83d2c..b6d92422c333 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictedMode.kt @@ -18,6 +18,7 @@ package com.android.settingslib.spaprivileged.model.enterprise import android.app.admin.DevicePolicyResources.Strings.Settings import android.content.Context +import android.content.Intent import com.android.settingslib.RestrictedLockUtils import com.android.settingslib.widget.restricted.R @@ -32,6 +33,11 @@ interface BlockedByAdmin : RestrictedMode { fun sendShowAdminSupportDetailsIntent() } +interface BlockedByEcm : RestrictedMode { + fun showRestrictedSettingsDetails() +} + + internal data class BlockedByAdminImpl( private val context: Context, private val enforcedAdmin: RestrictedLockUtils.EnforcedAdmin, @@ -55,3 +61,13 @@ internal data class BlockedByAdminImpl( RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, enforcedAdmin) } } + +internal data class BlockedByEcmImpl( + private val context: Context, + private val intent: Intent, +) : BlockedByEcm { + + override fun showRestrictedSettingsDetails() { + context.startActivity(intent) + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt index 550966beb0b8..9432d5995151 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/enterprise/RestrictionsProvider.kt @@ -29,10 +29,20 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flowOn +data class EnhancedConfirmation( + val key: String, + val uid: Int, + val packageName: String, +) data class Restrictions( val userId: Int = UserHandle.myUserId(), val keys: List<String>, -) + val enhancedConfirmation: EnhancedConfirmation? = null, +) { + fun isEmpty(): Boolean { + return keys.isEmpty() && enhancedConfirmation == null + } +} interface RestrictionsProvider { @Composable @@ -77,6 +87,14 @@ internal class RestrictionsProviderImpl( .checkIfRestrictionEnforced(context, key, restrictions.userId) ?.let { return BlockedByAdminImpl(context = context, enforcedAdmin = it) } } + + restrictions.enhancedConfirmation?.let { ec -> + RestrictedLockUtilsInternal + .checkIfRequiresEnhancedConfirmation(context, ec.key, + ec.uid, ec.packageName) + ?.let { intent -> return BlockedByEcmImpl(context = context, intent = intent) } + } + return NoRestricted } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt index 06b3eabfad26..25c3bc541249 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppOpPermissionAppList.kt @@ -47,6 +47,9 @@ abstract class AppOpPermissionListModel( abstract val appOp: Int abstract val permission: String + override val enhancedConfirmationKey: String? + get() = AppOpsManager.opToPublicName(appOp) + /** * When set, specifies the broader permission who trumps the [permission]. * diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt index 565543614866..1c830c1c5b06 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppInfoPage.kt @@ -41,6 +41,7 @@ import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.IPackageManagers import com.android.settingslib.spaprivileged.model.app.PackageManagers import com.android.settingslib.spaprivileged.model.app.toRoute +import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl @@ -154,7 +155,12 @@ internal fun <T : AppRecord> TogglePermissionAppListModel<T>.TogglePermissionApp override val changeable = { isChangeable } override val onCheckedChange: (Boolean) -> Unit = { setAllowed(record, it) } } - val restrictions = Restrictions(userId, switchRestrictionKeys) + val restrictions = Restrictions(userId = userId, + keys = switchRestrictionKeys, + enhancedConfirmation = enhancedConfirmationKey?.let { EnhancedConfirmation( + key = it, + uid = checkNotNull(applicationInfo).uid, + packageName = packageName) }) RestrictedSwitchPreference(switchModel, restrictions, restrictionsProviderFactory) } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt index 8704f20f5c4a..916d83af3f8f 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppList.kt @@ -36,6 +36,9 @@ interface TogglePermissionAppListModel<T : AppRecord> { val switchRestrictionKeys: List<String> get() = emptyList() + val enhancedConfirmationKey: String? + get() = null + /** * Loads the extra info for the App List, and generates the [AppRecord] List. * diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt index 36c91f463efe..4b474379c54b 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/TogglePermissionAppListPage.kt @@ -41,6 +41,7 @@ import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder import com.android.settingslib.spaprivileged.model.app.AppListModel import com.android.settingslib.spaprivileged.model.app.AppRecord import com.android.settingslib.spaprivileged.model.app.userId +import com.android.settingslib.spaprivileged.model.enterprise.EnhancedConfirmation import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl @@ -149,11 +150,17 @@ internal class TogglePermissionInternalAppListModel<T : AppRecord>( @Composable fun getSummary(record: T): () -> String { - val restrictions = remember(record.app.userId) { + val restrictions = remember(record.app.userId, + record.app.uid, record.app.packageName) { Restrictions( userId = record.app.userId, keys = listModel.switchRestrictionKeys, - ) + enhancedConfirmation = listModel.enhancedConfirmationKey?.let { + EnhancedConfirmation( + key = it, + uid = record.app.uid, + packageName = record.app.packageName) + }) } val restrictedMode by restrictionsProviderFactory.rememberRestrictedMode(restrictions) val allowed = listModel.isAllowed(record) diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt index d5c5574a0450..cd720252e485 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreference.kt @@ -40,7 +40,7 @@ internal fun RestrictedSwitchPreference( restrictions: Restrictions, restrictionsProviderFactory: RestrictionsProviderFactory, ) { - if (restrictions.keys.isEmpty()) { + if (restrictions.isEmpty()) { SwitchPreference(model) return } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt index fa44ecb92ed5..aba3460fc1b9 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/preference/RestrictedSwitchPreferenceModel.kt @@ -31,6 +31,7 @@ import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel import com.android.settingslib.spaprivileged.framework.compose.getPlaceholder import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin +import com.android.settingslib.spaprivileged.model.enterprise.BlockedByEcm import com.android.settingslib.spaprivileged.model.enterprise.NoRestricted import com.android.settingslib.spaprivileged.model.enterprise.RestrictedMode import com.android.settingslib.spaprivileged.model.enterprise.Restrictions @@ -56,6 +57,7 @@ internal class RestrictedSwitchPreferenceModel( is NoRestricted -> model.checked is BaseUserRestricted -> ({ false }) is BlockedByAdmin -> model.checked + is BlockedByEcm -> model.checked } override val changeable = if (restrictedMode is NoRestricted) model.changeable else ({ false }) @@ -68,24 +70,42 @@ internal class RestrictedSwitchPreferenceModel( is BaseUserRestricted -> model.onCheckedChange // Pass null since semantics ToggleableState is provided in RestrictionWrapper. is BlockedByAdmin -> null + is BlockedByEcm -> null } @Composable fun RestrictionWrapper(content: @Composable () -> Unit) { - if (restrictedMode !is BlockedByAdmin) { - content() - return + when (restrictedMode) { + is BlockedByAdmin -> { + Box( + Modifier + .clickable( + role = Role.Switch, + onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() }, + ) + .semantics { + this.toggleableState = ToggleableState(checked()) + }, + ) { content() } + } + + is BlockedByEcm -> { + Box( + Modifier + .clickable( + role = Role.Switch, + onClick = { restrictedMode.showRestrictedSettingsDetails() }, + ) + .semantics { + this.toggleableState = ToggleableState(checked()) + }, + ) { content() } + } + + else -> { + content() + } } - Box( - Modifier - .clickable( - role = Role.Switch, - onClick = { restrictedMode.sendShowAdminSupportDetailsIntent() }, - ) - .semantics { - this.toggleableState = ToggleableState(checked()) - }, - ) { content() } } private fun ToggleableState(value: Boolean?) = when (value) { @@ -123,6 +143,9 @@ internal class RestrictedSwitchPreferenceModel( context.getString(com.android.settingslib.R.string.disabled) is BlockedByAdmin -> restrictedMode.getSummary(checked()) + is BlockedByEcm -> + context.getString(com.android.settingslib.R.string.disabled) + null -> context.getPlaceholder() } } diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt index f9abefc11e24..977615b55a6a 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/scaffold/RestrictedMenuItem.kt @@ -21,6 +21,7 @@ import androidx.compose.runtime.Composable import com.android.settingslib.spa.widget.scaffold.MoreOptionsScope import com.android.settingslib.spaprivileged.model.enterprise.BaseUserRestricted import com.android.settingslib.spaprivileged.model.enterprise.BlockedByAdmin +import com.android.settingslib.spaprivileged.model.enterprise.BlockedByEcm import com.android.settingslib.spaprivileged.model.enterprise.Restrictions import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderFactory import com.android.settingslib.spaprivileged.model.enterprise.RestrictionsProviderImpl @@ -47,6 +48,7 @@ internal fun MoreOptionsScope.RestrictedMenuItemImpl( MenuItem(text = text, enabled = restrictedMode !== BaseUserRestricted) { when (restrictedMode) { is BlockedByAdmin -> restrictedMode.sendShowAdminSupportDetailsIntent() + is BlockedByEcm -> restrictedMode.showRestrictedSettingsDetails() else -> onClick() } } diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java index e77964c6fa0c..4454b710b7e4 100644 --- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java +++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java @@ -22,13 +22,16 @@ import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AF import static com.android.settingslib.Utils.getColorAttrDefaultColor; +import android.Manifest; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.AppGlobals; +import android.app.AppOpsManager; import android.app.admin.DevicePolicyManager; import android.content.ComponentName; import android.content.Context; +import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.UserInfo; @@ -39,10 +42,12 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.os.UserManager.EnforcingUser; +import android.provider.Settings; import android.text.SpannableStringBuilder; import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.ImageSpan; +import android.util.ArraySet; import android.util.Log; import android.view.MenuItem; import android.widget.TextView; @@ -53,6 +58,7 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.widget.LockPatternUtils; import java.util.List; +import java.util.Set; /** * Utility class to host methods usable in adding a restricted padlock icon and showing admin @@ -62,6 +68,16 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { private static final String LOG_TAG = "RestrictedLockUtils"; private static final boolean DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG); + private static final Set<String> ECM_KEYS = new ArraySet<>(); + + static { + if (android.security.Flags.extendEcmToAllSettings()) { + ECM_KEYS.add(AppOpsManager.OPSTR_SYSTEM_ALERT_WINDOW); + ECM_KEYS.add(AppOpsManager.OPSTR_GET_USAGE_STATS); + ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS); + ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN); + } + } /** * @return drawables for displaying with settings that are locked by a device admin. @@ -81,6 +97,38 @@ public class RestrictedLockUtilsInternal extends RestrictedLockUtils { } /** + * Checks if a given permission requires additional confirmation for the given package + * + * @return An intent to show the user if additional confirmation is required, null otherwise + */ + @Nullable + public static Intent checkIfRequiresEnhancedConfirmation(@NonNull Context context, + @NonNull String restriction, + int uid, + @Nullable String packageName) { + // TODO(b/297372999): Replace with call to mainline module once ready + + if (!ECM_KEYS.contains(restriction)) { + return null; + } + + final AppOpsManager appOps = (AppOpsManager) context + .getSystemService(Context.APP_OPS_SERVICE); + final int mode = appOps.noteOpNoThrow(AppOpsManager.OP_ACCESS_RESTRICTED_SETTINGS, + uid, packageName, null, null); + final boolean ecmEnabled = context.getResources().getBoolean( + com.android.internal.R.bool.config_enhancedConfirmationModeEnabled); + if (ecmEnabled && mode != AppOpsManager.MODE_ALLOWED) { + final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG); + intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName); + intent.putExtra(Intent.EXTRA_UID, uid); + return intent; + } + + return null; + } + + /** * Checks if a restriction is enforced on a user and returns the enforced admin and * admin userId. * diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java index 25833b3ddbba..5aee8cdb8b9e 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java @@ -16,7 +16,7 @@ package com.android.settingslib.core.instrumentation; -import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_TOGGLE; +import static com.android.internal.jank.Cuj.CUJ_SETTINGS_TOGGLE; import static com.android.settingslib.core.instrumentation.SettingsJankMonitor.MONITORED_ANIMATION_DURATION_MS; import static com.google.common.truth.Truth.assertThat; @@ -34,8 +34,8 @@ import androidx.preference.PreferenceGroupAdapter; import androidx.preference.SwitchPreference; import androidx.recyclerview.widget.RecyclerView; +import com.android.internal.jank.Cuj.CujType; import com.android.internal.jank.InteractionJankMonitor; -import com.android.internal.jank.InteractionJankMonitor.CujType; import com.android.settingslib.testutils.OverpoweredReflectionHelper; import com.android.settingslib.testutils.shadow.ShadowInteractionJankMonitor; diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp index e842967e6150..50ed7ab7c85a 100644 --- a/packages/SystemUI/aconfig/Android.bp +++ b/packages/SystemUI/aconfig/Android.bp @@ -23,6 +23,7 @@ package { default_visibility: [ "//visibility:override", "//frameworks/base/packages/SystemUI:__subpackages__", + "//platform_testing:__subpackages__" ], } diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt index 187d0739b734..168039ed5d3d 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt @@ -33,8 +33,8 @@ import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS import com.android.app.animation.Interpolators +import com.android.internal.jank.Cuj.CujType import com.android.internal.jank.InteractionJankMonitor -import com.android.internal.jank.InteractionJankMonitor.CujType import com.android.systemui.util.maybeForceFullscreen import com.android.systemui.util.registerAnimationOnBackInvoked import kotlin.math.roundToInt diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt index af35ea44322f..b738e2bc972b 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/GhostedViewLaunchAnimatorController.kt @@ -33,6 +33,7 @@ import android.view.View import android.view.ViewGroup import android.view.ViewGroupOverlay import android.widget.FrameLayout +import com.android.internal.jank.Cuj.CujType import com.android.internal.jank.InteractionJankMonitor import java.util.LinkedList import kotlin.math.min @@ -58,7 +59,7 @@ constructor( /** The view that will be ghosted and from which the background will be extracted. */ private val ghostedView: View, - /** The [InteractionJankMonitor.CujType] associated to this animation. */ + /** The [CujType] associated to this animation. */ private val cujType: Int? = null, private var interactionJankMonitor: InteractionJankMonitor = InteractionJankMonitor.getInstance(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt index 1a653c316db1..87c28862f6f6 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt @@ -203,7 +203,7 @@ private fun SceneScope.FoldSplittable( modifier: Modifier = Modifier, ) { val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() - val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() + val dialogMessage: String? by viewModel.dialogMessage.collectAsState() var dialog: Dialog? by remember { mutableStateOf(null) } val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() val splitRatio = @@ -320,7 +320,7 @@ private fun SceneScope.FoldSplittable( DialogInterface.BUTTON_NEUTRAL, context.getString(R.string.ok), ) { _, _ -> - viewModel.onThrottlingDialogDismissed() + viewModel.onDialogDismissed() } setCancelable(false) setCanceledOnTouchOutside(false) diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt index c49c19785624..12f1b301c836 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt @@ -27,11 +27,13 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.layout.LayoutCoordinates import androidx.compose.ui.layout.boundsInWindow import androidx.compose.ui.layout.onPlaced @@ -89,13 +91,18 @@ fun SceneScope.NotificationStack( isScrimVisible: Boolean, modifier: Modifier = Modifier, ) { + val cornerRadius by viewModel.cornerRadiusDp.collectAsState() + Box(modifier = modifier) { if (isScrimVisible) { Box( modifier = Modifier.element(Notifications.Elements.NotificationScrim) .fillMaxSize() - .clip(RoundedCornerShape(32.dp)) + .graphicsLayer { + shape = RoundedCornerShape(cornerRadius.dp) + clip = true + } .background(MaterialTheme.colorScheme.surface) ) } @@ -167,7 +174,9 @@ private fun SceneScope.NotificationPlaceholder( } val boundsInWindow = coordinates.boundsInWindow() viewModel.onBoundsChanged( + left = boundsInWindow.left, top = boundsInWindow.top, + right = boundsInWindow.right, bottom = boundsInWindow.bottom, ) } diff --git a/packages/SystemUI/docs/executors.md b/packages/SystemUI/docs/executors.md index 8520ce228c9d..2d9438cdcecf 100644 --- a/packages/SystemUI/docs/executors.md +++ b/packages/SystemUI/docs/executors.md @@ -14,10 +14,10 @@ Executor available, as well as our own sub-interface, [FakeExecutor][FakeExecutor] is available. [Executor]: https://developer.android.com/reference/java/util/concurrent/Executor.html -[Handler]: https://developer.android.com/reference/android/os/Handler +[Handler]: https://developer.android.com/reference/android/os/Handler.html [Runnable]: https://developer.android.com/reference/java/lang/Runnable.html [DelayableExecutor]: /packages/SystemUI/src/com/android/systemui/util/concurrency/DelayableExecutor.java -[FakeExecutor]: /packages/SystemUI/tests/src/com/android/systemui/util/concurrency/FakeExecutor.java +[FakeExecutor]: /packages/SystemUI/tests/utils/src/com/android/systemui/util/concurrency/FakeExecutor.java ## Rationale @@ -117,7 +117,7 @@ post() | execute() | execute() postDelayed() | `none` | executeDelayed() postAtTime() | `none` | executeAtTime() -There is one notable gap in this implementation: `Handler.postAtFrontOfQueue()`. +There are some notable gaps in this implementation: `Handler.postAtFrontOfQueue()`. If you require this method, or similar, please reach out. The idea of a PriorityQueueExecutor has been floated, but will not be implemented until there is a clear need. @@ -173,13 +173,20 @@ fields in your class. If you feel that you have a use case that this does not cover, please reach out. -### Handlers Are Still Necessary +### ContentObserver + +One notable place where Handlers have been a requirement in the past is with +[ContentObserver], which takes a Handler as an argument. However, we have created +[ExecutorContentObserver], which is a hidden API that accepts an [Executor] in its +constructor instead of a [Handler], and is otherwise identical. + +[ContentObserver]: https://developer.android.com/reference/android/database/ContentObserver.html +[ExecutorContentObserver]: /core/java/android/database/ExecutorContentObserver.java -Handlers aren't going away. There are Android APIs that still require them (even -if future API development discourages them). A simple example is -[ContentObserver][ContentObserver]. Use them where necessary. +### Handlers Are Still Necessary -[ContentObserver]: https://developer.android.com/reference/android/database/ContentObserver +Handlers aren't going away. There are other Android APIs that still require them. +Avoid Handlers when possible, but use them where necessary. ## Testing (FakeExecutor) @@ -314,6 +321,15 @@ clock.setUptimeMillis(500); The Runnables _will not_ interleave. All of one Executor's callbacks will run, then all of the other's. +### Testing Handlers without Loopers + +If a [Handler] is required because it is used by Android APIs, but is only +used in simple ways (i.e. just `Handler.post(Runnable)`), you may still +want the benefits of [FakeExecutor] when writing your tests, which +you can get by wrapping the [Executor] in a mock for testing. This can be +done with `com.android.systemui.util.concurrency.mockExecutorHandler` in +`MockExecutorHandler.kt`. + ### TestableLooper.RunWithLooper As long as you're using FakeExecutors in all the code under test (and no diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 543b2910bbda..695d888d94f5 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -465,29 +465,6 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test - fun showNextSecurityScreenOrFinish_setsSecurityScreenToPinAfterSimPinUnlock() { - // GIVEN the current security method is SimPin - whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) - whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) - .thenReturn(false) - underTest.showSecurityScreen(SecurityMode.SimPin) - - // WHEN a request is made from the SimPin screens to show the next security method - whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)).thenReturn(SecurityMode.PIN) - underTest.showNextSecurityScreenOrFinish( - /* authenticated= */ true, - TARGET_USER_ID, - /* bypassSecondaryLockScreen= */ true, - SecurityMode.SimPin - ) - - // THEN the next security method of PIN is set, and the keyguard is not marked as done - verify(viewMediatorCallback, never()).keyguardDonePending(anyInt()) - verify(viewMediatorCallback, never()).keyguardDone(anyInt()) - Truth.assertThat(underTest.currentSecurityMode).isEqualTo(SecurityMode.PIN) - } - - @Test fun showNextSecurityScreenOrFinish_DeviceNotSecure() { // GIVEN the current security method is SimPin whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) @@ -578,6 +555,57 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { } @Test + fun showNextSecurityScreenOrFinish_SimPin_Password() { + // GIVEN the current security method is SimPin + whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) + .thenReturn(false) + underTest.showSecurityScreen(SecurityMode.SimPin) + + // WHEN a request is made from the SimPin screens to show the next security method + whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) + .thenReturn(SecurityMode.Password) + // WHEN security method is SWIPE + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) + whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false) + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN we will not show the password screen. + verify(viewFlipperController, never()) + .getSecurityView(eq(SecurityMode.Password), any(), any()) + } + + @Test + fun showNextSecurityScreenOrFinish_SimPin_SimPin() { + // GIVEN the current security method is SimPin + whenever(keyguardUpdateMonitor.getUserHasTrust(anyInt())).thenReturn(false) + whenever(keyguardUpdateMonitor.getUserUnlockedWithBiometric(TARGET_USER_ID)) + .thenReturn(false) + underTest.showSecurityScreen(SecurityMode.SimPin) + + // WHEN a request is made from the SimPin screens to show the next security method + whenever(keyguardSecurityModel.getSecurityMode(TARGET_USER_ID)) + .thenReturn(SecurityMode.SimPin) + // WHEN security method is SWIPE + whenever(lockPatternUtils.isLockScreenDisabled(anyInt())).thenReturn(false) + whenever(deviceProvisionedController.isUserSetup(anyInt())).thenReturn(false) + underTest.showNextSecurityScreenOrFinish( + /* authenticated= */ true, + TARGET_USER_ID, + /* bypassSecondaryLockScreen= */ true, + SecurityMode.SimPin + ) + + // THEN we will not show the password screen. + verify(viewFlipperController).getSecurityView(eq(SecurityMode.SimPin), any(), any()) + } + + @Test fun onSwipeUp_forwardsItToFaceAuthInteractor() { val registeredSwipeListener = registeredSwipeListener setupGetSecurityView(SecurityMode.Password) diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt index 94c3bde29597..84d735430edd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSimPinViewControllerTest.kt @@ -34,11 +34,14 @@ import com.android.systemui.util.mockito.any import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentCaptor 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.never +import org.mockito.Mockito.reset import org.mockito.Mockito.verify import org.mockito.Mockito.`when` import org.mockito.MockitoAnnotations @@ -63,6 +66,8 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { @Mock private lateinit var keyguardMessageAreaController: KeyguardMessageAreaController<BouncerKeyguardMessageArea> + private val updateMonitorCallbackArgumentCaptor = + ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) @Before fun setup() { @@ -95,6 +100,9 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { mSelectedUserInteractor ) underTest.init() + underTest.onResume(0) + verify(keyguardUpdateMonitor) + .registerCallback(updateMonitorCallbackArgumentCaptor.capture()) } @Test @@ -111,6 +119,7 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { @Test fun onResume() { + reset(keyguardUpdateMonitor) underTest.onResume(KeyguardSecurityView.VIEW_REVEALED) verify(keyguardUpdateMonitor) .registerCallback(any(KeyguardUpdateMonitorCallback::class.java)) @@ -137,4 +146,22 @@ class KeyguardSimPinViewControllerTest : SysuiTestCase() { underTest.resetState() verify(keyguardMessageAreaController).setMessage("") } + + @Test + fun onSimStateChangedFromPinToPuk_showsCurrentSecurityScreen() { + updateMonitorCallbackArgumentCaptor.value.onSimStateChanged( + /* subId= */ 0, + /* slotId= */ 0, + TelephonyManager.SIM_STATE_PIN_REQUIRED + ) + verify(keyguardSecurityCallback, never()).showCurrentSecurityScreen() + + updateMonitorCallbackArgumentCaptor.value.onSimStateChanged( + /* subId= */ 0, + /* slotId= */ 0, + TelephonyManager.SIM_STATE_PUK_REQUIRED + ) + + verify(keyguardSecurityCallback).showCurrentSecurityScreen() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt index 661c3458574e..08cd7edba6af 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt @@ -21,9 +21,9 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat @@ -76,12 +76,13 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPin_succeeds() = testScope.runTest { - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test @@ -129,14 +130,15 @@ class AuthenticationInteractorTest : SysuiTestCase() { @Test fun authenticate_withCorrectPassword_succeeds() = testScope.runTest { - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) assertThat(underTest.authenticate("password".toList())) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test @@ -185,7 +187,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.apply { setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) @@ -201,7 +203,8 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test @@ -262,7 +265,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringThrottling_returnsNull() = + fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() = testScope.runTest { val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked) @@ -270,7 +273,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.apply { setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) - setThrottleDuration(42) + setLockoutDuration(42) } val authResult = @@ -313,65 +316,121 @@ class AuthenticationInteractorTest : SysuiTestCase() { } @Test - fun throttling() = + fun isAutoConfirmEnabled_featureDisabled_returnsFalse() = testScope.runTest { - val throttling by collectLastValue(underTest.throttling) + val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) + utils.authenticationRepository.setAutoConfirmFeatureEnabled(false) + + assertThat(isAutoConfirmEnabled).isFalse() + } + + @Test + fun isAutoConfirmEnabled_featureEnabled_returnsTrue() = + testScope.runTest { + val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) + utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + + assertThat(isAutoConfirmEnabled).isTrue() + } + + @Test + fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() = + testScope.runTest { + val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled) + val lockout by collectLastValue(underTest.lockout) + utils.authenticationRepository.setAutoConfirmFeatureEnabled(true) + + // The feature is enabled. + assertThat(isAutoConfirmEnabled).isTrue() + + // Make many wrong attempts to trigger lockout. + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { + underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN + } + assertThat(lockout).isNotNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) + + // Lockout disabled auto-confirm. + assertThat(isAutoConfirmEnabled).isFalse() + + // Move the clock forward one more second, to completely finish the lockout period: + advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L) + assertThat(lockout).isNull() + + // Auto-confirm is still disabled, because lockout occurred at least once in this + // session. + assertThat(isAutoConfirmEnabled).isFalse() + + // Correct PIN and unlocks successfully, resetting the 'session'. + assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) + .isEqualTo(AuthenticationResult.SUCCEEDED) + + // Auto-confirm is re-enabled. + assertThat(isAutoConfirmEnabled).isTrue() + + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) + } + + @Test + fun lockout() = + testScope.runTest { + val lockout by collectLastValue(underTest.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN) - assertThat(throttling).isNull() + assertThat(lockout).isNull() - // Make many wrong attempts, but just shy of what's needed to get throttled: - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1) { + // Make many wrong attempts, but just shy of what's needed to get locked out: + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(throttling).isNull() + assertThat(lockout).isNull() } - // Make one more wrong attempt, leading to throttling: + // Make one more wrong attempt, leading to lockout: underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN - assertThat(throttling) + assertThat(lockout) .isEqualTo( - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS, + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, + remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, ) ) + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1) - // Correct PIN, but throttled, so doesn't attempt it: + // Correct PIN, but locked out, so doesn't attempt it: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(throttling) + assertThat(lockout) .isEqualTo( - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS, + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, + remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, ) ) - // Move the clock forward to ALMOST skip the throttling, leaving one second to go: - repeat(FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - 1) { time -> + // Move the clock forward to ALMOST skip the lockout, leaving one second to go: + val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS + repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time -> advanceTimeBy(1000) - assertThat(throttling) + assertThat(lockout) .isEqualTo( - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = - FakeAuthenticationRepository - .MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = - FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS - (time + 1), + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, + remainingSeconds = lockoutTimeoutSec - (time + 1), ) ) } - // Move the clock forward one more second, to completely finish the throttling period: + // Move the clock forward one more second, to completely finish the lockout period: advanceTimeBy(1000) - assertThat(throttling).isNull() + assertThat(lockout).isNull() - // Correct PIN and no longer throttled so unlocks successfully: + // Correct PIN and no longer locked out so unlocks successfully: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt index 3e3a1a947bd4..9b1df7c0ffc0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt @@ -21,9 +21,9 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor import com.android.systemui.res.R @@ -246,46 +246,42 @@ class BouncerInteractorTest : SysuiTestCase() { } @Test - fun throttling() = + fun lockout() = testScope.runTest { - val throttling by collectLastValue(underTest.throttling) + val lockout by collectLastValue(underTest.lockout) val message by collectLastValue(underTest.message) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(throttling).isNull() - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { times -> + assertThat(lockout).isNull() + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times -> // Wrong PIN. assertThat(underTest.authenticate(listOf(6, 7, 8, 9))) .isEqualTo(AuthenticationResult.FAILED) - if ( - times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1 - ) { + if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { assertThat(message).isEqualTo(MESSAGE_WRONG_PIN) } } - assertThat(throttling) + assertThat(lockout) .isEqualTo( - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = - FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING, - remainingSeconds = FakeAuthenticationRepository.THROTTLE_DURATION_SECONDS, + FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT, + remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS, ) ) assertTryAgainMessage( message, - FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds - .toInt() + FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt() ) - // Correct PIN, but throttled, so doesn't change away from the bouncer scene: + // Correct PIN, but locked out, so doesn't change away from the bouncer scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SKIPPED) assertTryAgainMessage( message, - FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds - .toInt() + FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt() ) - throttling?.remainingSeconds?.let { seconds -> + lockout?.remainingSeconds?.let { seconds -> repeat(seconds) { time -> advanceTimeBy(1000) val remainingTimeSec = seconds - time - 1 @@ -295,12 +291,12 @@ class BouncerInteractorTest : SysuiTestCase() { } } assertThat(message).isEqualTo("") - assertThat(throttling).isNull() + assertThat(lockout).isNull() - // Correct PIN and no longer throttled so changes to the Gone scene: + // Correct PIN and no longer locked out so changes to the Gone scene: assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)) .isEqualTo(AuthenticationResult.SUCCEEDED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt index 45c186dc3a77..2f0843b202a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt @@ -35,7 +35,6 @@ class AuthMethodBouncerViewModelTest : SysuiTestCase() { private val utils = SceneTestUtils(this) private val testScope = utils.testScope - private val sceneInteractor = utils.sceneInteractor() private val bouncerInteractor = utils.bouncerInteractor( authenticationInteractor = utils.authenticationInteractor(), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt index 2b64d8eca032..16a935943dbf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt @@ -135,17 +135,17 @@ class BouncerViewModelTest : SysuiTestCase() { fun message() = testScope.runTest { val message by collectLastValue(underTest.message) - val throttling by collectLastValue(bouncerInteractor.throttling) + val lockout by collectLastValue(bouncerInteractor.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(message?.isUpdateAnimated).isTrue() - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } assertThat(message?.isUpdateAnimated).isFalse() - throttling?.remainingSeconds?.let { remainingSeconds -> + lockout?.remainingSeconds?.let { remainingSeconds -> advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds) } assertThat(message?.isUpdateAnimated).isTrue() @@ -160,37 +160,37 @@ class BouncerViewModelTest : SysuiTestCase() { authViewModel?.isInputEnabled ?: emptyFlow() } ) - val throttling by collectLastValue(bouncerInteractor.throttling) + val lockout by collectLastValue(bouncerInteractor.lockout) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) assertThat(isInputEnabled).isTrue() - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { // Wrong PIN. bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } assertThat(isInputEnabled).isFalse() - throttling?.remainingSeconds?.let { remainingSeconds -> + lockout?.remainingSeconds?.let { remainingSeconds -> advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds) } assertThat(isInputEnabled).isTrue() } @Test - fun throttlingDialogMessage() = + fun dialogMessage() = testScope.runTest { - val throttlingDialogMessage by collectLastValue(underTest.throttlingDialogMessage) + val dialogMessage by collectLastValue(underTest.dialogMessage) utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING) { + repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { // Wrong PIN. - assertThat(throttlingDialogMessage).isNull() + assertThat(dialogMessage).isNull() bouncerInteractor.authenticate(listOf(3, 4, 5, 6)) } - assertThat(throttlingDialogMessage).isNotEmpty() + assertThat(dialogMessage).isNotEmpty() - underTest.onThrottlingDialogDismissed() - assertThat(throttlingDialogMessage).isNull() + underTest.onDialogDismissed() + assertThat(dialogMessage).isNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index a217d93c79ae..6d6baa57bb9d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.ui.viewmodel import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.res.R @@ -243,12 +243,12 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } @Test - fun onImeVisibilityChanged_falseAfterTrue_whileThrottling_doesNothing() = + fun onImeVisibilityChanged_falseAfterTrue_whileLockedOut_doesNothing() = testScope.runTest { val events by collectValues(bouncerInteractor.onImeHiddenByUser) assertThat(events).isEmpty() underTest.onImeVisibilityChanged(isVisible = true) - setThrottling(true) + setLockout(true) underTest.onImeVisibilityChanged(isVisible = false) @@ -284,11 +284,11 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } @Test - fun isTextFieldFocusRequested_focusLostWhileThrottling_staysFalse() = + fun isTextFieldFocusRequested_focusLostWhileLockedOut_staysFalse() = testScope.runTest { val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) underTest.onTextFieldFocusChanged(isFocused = true) - setThrottling(true) + setLockout(true) underTest.onTextFieldFocusChanged(isFocused = false) @@ -296,14 +296,14 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { } @Test - fun isTextFieldFocusRequested_throttlingCountdownEnds_becomesTrue() = + fun isTextFieldFocusRequested_lockoutCountdownEnds_becomesTrue() = testScope.runTest { val isTextFieldFocusRequested by collectLastValue(underTest.isTextFieldFocusRequested) underTest.onTextFieldFocusChanged(isFocused = true) - setThrottling(true) + setLockout(true) underTest.onTextFieldFocusChanged(isFocused = false) - setThrottling(false) + setLockout(false) assertThat(isTextFieldFocusRequested).isTrue() } @@ -327,24 +327,24 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { switchToScene(SceneKey.Bouncer) } - private suspend fun TestScope.setThrottling( - isThrottling: Boolean, + private suspend fun TestScope.setLockout( + isLockedOut: Boolean, failedAttemptCount: Int = 5, ) { - if (isThrottling) { + if (isLockedOut) { repeat(failedAttemptCount) { authenticationRepository.reportAuthenticationAttempt(false) } val remainingTimeSeconds = 30 - authenticationRepository.setThrottleDuration(remainingTimeSeconds * 1000) - authenticationRepository.throttling.value = - AuthenticationThrottlingModel( + authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000) + authenticationRepository.lockout.value = + AuthenticationLockoutModel( failedAttemptCount = failedAttemptCount, remainingSeconds = remainingTimeSeconds, ) } else { authenticationRepository.reportAuthenticationAttempt(true) - authenticationRepository.throttling.value = null + authenticationRepository.lockout.value = null } runCurrent() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 862c39c9d4cc..8971423edd52 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -304,13 +304,12 @@ class PatternBouncerViewModelTest : SysuiTestCase() { fun onDragEnd_whenPatternTooShort() = testScope.runTest { val message by collectLastValue(bouncerViewModel.message) - val throttlingDialogMessage by - collectLastValue(bouncerViewModel.throttlingDialogMessage) + val dialogMessage by collectLastValue(bouncerViewModel.dialogMessage) lockDeviceAndOpenPatternBouncer() // Enter a pattern that's too short more than enough times that would normally trigger - // throttling if the pattern were not too short and wrong: - val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING + 1 + // lockout if the pattern were not too short and wrong: + val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT + 1 repeat(attempts) { attempt -> underTest.onDragStart() CORRECT_PATTERN.subList( @@ -328,7 +327,7 @@ class PatternBouncerViewModelTest : SysuiTestCase() { underTest.onDragEnd() assertWithMessage("Attempt #$attempt").that(message?.text).isEqualTo(WRONG_PATTERN) - assertWithMessage("Attempt #$attempt").that(throttlingDialogMessage).isNull() + assertWithMessage("Attempt #$attempt").that(dialogMessage).isNull() } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt index bc4bae0ed959..058b35e71023 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt @@ -49,8 +49,10 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever @@ -93,6 +95,8 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { private lateinit var dockManager: DockManagerFake private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + private val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -179,6 +183,7 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { underTest = KeyguardQuickAffordanceInteractor( keyguardInteractor = withDeps.keyguardInteractor, + shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt index 9226c0d61a3c..a346e8b45795 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt @@ -24,7 +24,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.Flags -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -48,7 +48,7 @@ class LockscreenToDreamingTransitionViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().apply { - featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope private val repository = kosmos.fakeKeyguardTransitionRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt index bcad72bef1e6..274bde1ccfdf 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt @@ -25,7 +25,7 @@ import com.android.systemui.common.ui.data.repository.fakeConfigurationRepositor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.Flags -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -49,7 +49,7 @@ import org.junit.runner.RunWith class LockscreenToOccludedTransitionViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().apply { - featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope private val repository = kosmos.fakeKeyguardTransitionRepository diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt index 78d87a680c5b..f027bc849e51 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt @@ -22,7 +22,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.Flags -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState @@ -43,7 +43,7 @@ import org.junit.runner.RunWith class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() { val kosmos = testKosmos().apply { - featureFlagsClassic.apply { + fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) set(Flags.FULL_SCREEN_USER_SWITCHER, false) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt index 00572d3163eb..7f7490d9ecc3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileDataInteractorTest.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor + import android.os.UserHandle import android.testing.LeakCheck import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -22,7 +24,6 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger -import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileDataInteractor import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel import com.android.systemui.utils.leaks.FakeFlashlightController import com.google.common.truth.Truth.assertThat diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt index f819f53838b8..28d43b369dd4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/interactor/FlashlightTileUserActionInteractorTest.kt @@ -14,12 +14,13 @@ * limitations under the License. */ +package com.android.systemui.qs.tiles.impl.flashlight.domain.interactor + import android.app.ActivityManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx.click -import com.android.systemui.qs.tiles.impl.flashlight.domain.interactor.FlashlightTileUserActionInteractor import com.android.systemui.qs.tiles.impl.flashlight.domain.model.FlashlightTileModel import com.android.systemui.statusbar.policy.FlashlightController import com.android.systemui.util.mockito.mock diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt index f04dfd1b8fdb..4cdb08afb22e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationStackAppearanceIntegrationTest.kt @@ -24,7 +24,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags @@ -51,7 +51,7 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { private val kosmos = testKosmos().apply { sceneContainerFlags = FakeSceneContainerFlags(enabled = true) - featureFlagsClassic.apply { + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) set(Flags.NSSL_DEBUG_LINES, false) } @@ -67,9 +67,24 @@ class NotificationStackAppearanceIntegrationTest : SysuiTestCase() { val bounds by collectLastValue(appearanceViewModel.stackBounds) val top = 200f + val left = 0f val bottom = 550f - placeholderViewModel.onBoundsChanged(top, bottom) - assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) + val right = 100f + placeholderViewModel.onBoundsChanged( + left = left, + top = top, + right = right, + bottom = bottom + ) + assertThat(bounds) + .isEqualTo( + NotificationContainerBounds( + left = left, + top = top, + right = right, + bottom = bottom + ) + ) } @Test diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml new file mode 100644 index 000000000000..bd604317bbb8 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_off.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z" + android:fillColor="#000000"/> +</vector> diff --git a/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml new file mode 100644 index 000000000000..fadaf7896ae4 --- /dev/null +++ b/packages/SystemUI/res/drawable/qs_record_issue_icon_on.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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 + --> +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24.0dp" + android:height="24.0dp" + android:viewportWidth="24.0" + android:viewportHeight="24.0"> + <path + android:pathData="M5.76 12.89c0 3.28 2.54 5.97 5.76 6.22v-8.26h-5.4c-.23.64-.36 1.33-.36 2.04zm9.27-5.45l1.01-1.59c.19-.29.1-.67-.19-.86-.29-.19-.68-.1-.86.19l-1.12 1.76c-.59-.19-1.22-.29-1.87-.29s-1.28.1-1.87.29L9.01 5.18c-.18-.29-.57-.38-.86-.19-.29.18-.38.57-.19.86l1.01 1.59c-1.02.57-1.86 1.43-2.43 2.45h10.92c-.57-1.02-1.41-1.88-2.43-2.45zm2.85 3.41h-5.4v8.26c3.22-.25 5.76-2.93 5.76-6.22 0-.71-.13-1.4-.36-2.04z" + android:fillColor="#FFFFFF"/> +</vector> diff --git a/packages/SystemUI/res/layout/keyguard_bottom_area.xml b/packages/SystemUI/res/layout/keyguard_bottom_area.xml index 66c57fc2a9ac..6d7ce0623817 100644 --- a/packages/SystemUI/res/layout/keyguard_bottom_area.xml +++ b/packages/SystemUI/res/layout/keyguard_bottom_area.xml @@ -106,5 +106,5 @@ </FrameLayout> <include layout="@layout/ambient_indication" - android:id="@+id/ambient_indication_container" /> + android:id="@id/ambient_indication_container" /> </com.android.systemui.statusbar.phone.KeyguardBottomAreaView> diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml index 1838795a57d6..cf63cc74521d 100644 --- a/packages/SystemUI/res/values/ids.xml +++ b/packages/SystemUI/res/values/ids.xml @@ -223,6 +223,8 @@ <item type="id" name="lock_icon_bg" /> <item type="id" name="burn_in_layer" /> <item type="id" name="communal_tutorial_indicator" /> + <item type="id" name="nssl_placeholder_barrier_bottom" /> + <item type="id" name="ambient_indication_container" /> <!-- Privacy dialog --> <item type="id" name="privacy_dialog_close_app_button" /> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 7ca0b6ee8d9f..78b701caa3b6 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -824,6 +824,13 @@ <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] --> <string name="quick_settings_screen_record_stop">Stop</string> + <!-- QuickSettings: Record Issue tile [CHAR LIMIT=NONE] --> + <string name="qs_record_issue_label">Record Issue</string> + <!-- QuickSettings: Text to prompt the user to begin a new recording [CHAR LIMIT=20] --> + <string name="qs_record_issue_start">Start</string> + <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] --> + <string name="qs_record_issue_stop">Stop</string> + <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] --> <string name="quick_settings_onehanded_label">One-handed mode</string> diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java index c505bd502985..df7182b90f12 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java @@ -21,6 +21,7 @@ import android.os.Build; import android.text.TextUtils; import android.view.View; +import com.android.internal.jank.Cuj; import com.android.internal.jank.InteractionJankMonitor; import com.android.internal.jank.InteractionJankMonitor.Configuration; @@ -29,40 +30,25 @@ import java.lang.annotation.RetentionPolicy; public final class InteractionJankMonitorWrapper { // Launcher journeys. - public static final int CUJ_APP_LAUNCH_FROM_RECENTS = - InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS; - public static final int CUJ_APP_LAUNCH_FROM_ICON = - InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON; - public static final int CUJ_APP_CLOSE_TO_HOME = - InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME; + public static final int CUJ_APP_LAUNCH_FROM_RECENTS = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_RECENTS; + public static final int CUJ_APP_LAUNCH_FROM_ICON = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_ICON; + public static final int CUJ_APP_CLOSE_TO_HOME = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME; public static final int CUJ_APP_CLOSE_TO_HOME_FALLBACK = - InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; - public static final int CUJ_APP_CLOSE_TO_PIP = - InteractionJankMonitor.CUJ_LAUNCHER_APP_CLOSE_TO_PIP; - public static final int CUJ_QUICK_SWITCH = - InteractionJankMonitor.CUJ_LAUNCHER_QUICK_SWITCH; - public static final int CUJ_OPEN_ALL_APPS = - InteractionJankMonitor.CUJ_LAUNCHER_OPEN_ALL_APPS; - public static final int CUJ_CLOSE_ALL_APPS_SWIPE = - InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE; - public static final int CUJ_CLOSE_ALL_APPS_TO_HOME = - InteractionJankMonitor.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME; - public static final int CUJ_ALL_APPS_SCROLL = - InteractionJankMonitor.CUJ_LAUNCHER_ALL_APPS_SCROLL; - public static final int CUJ_APP_LAUNCH_FROM_WIDGET = - InteractionJankMonitor.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET; - public static final int CUJ_SPLIT_SCREEN_ENTER = - InteractionJankMonitor.CUJ_SPLIT_SCREEN_ENTER; + Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK; + public static final int CUJ_APP_CLOSE_TO_PIP = Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_PIP; + public static final int CUJ_QUICK_SWITCH = Cuj.CUJ_LAUNCHER_QUICK_SWITCH; + public static final int CUJ_OPEN_ALL_APPS = Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS; + public static final int CUJ_CLOSE_ALL_APPS_SWIPE = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_SWIPE; + public static final int CUJ_CLOSE_ALL_APPS_TO_HOME = Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_TO_HOME; + public static final int CUJ_ALL_APPS_SCROLL = Cuj.CUJ_LAUNCHER_ALL_APPS_SCROLL; + public static final int CUJ_APP_LAUNCH_FROM_WIDGET = Cuj.CUJ_LAUNCHER_APP_LAUNCH_FROM_WIDGET; + public static final int CUJ_SPLIT_SCREEN_ENTER = Cuj.CUJ_SPLIT_SCREEN_ENTER; public static final int CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION = - InteractionJankMonitor.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; - public static final int CUJ_RECENTS_SCROLLING = - InteractionJankMonitor.CUJ_RECENTS_SCROLLING; - public static final int CUJ_APP_SWIPE_TO_RECENTS = - InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS; - public static final int CUJ_OPEN_SEARCH_RESULT = - InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT; - public static final int CUJ_LAUNCHER_UNFOLD_ANIM = - InteractionJankMonitor.CUJ_LAUNCHER_UNFOLD_ANIM; + Cuj.CUJ_LAUNCHER_UNLOCK_ENTRANCE_ANIMATION; + public static final int CUJ_RECENTS_SCROLLING = Cuj.CUJ_RECENTS_SCROLLING; + public static final int CUJ_APP_SWIPE_TO_RECENTS = Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS; + public static final int CUJ_OPEN_SEARCH_RESULT = Cuj.CUJ_LAUNCHER_OPEN_SEARCH_RESULT; + public static final int CUJ_LAUNCHER_UNFOLD_ANIM = Cuj.CUJ_LAUNCHER_UNFOLD_ANIM; @IntDef({ CUJ_APP_LAUNCH_FROM_RECENTS, @@ -89,7 +75,7 @@ public final class InteractionJankMonitorWrapper { * Begin a trace session. * * @param v an attached view. - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. */ public static void begin(View v, @CujType int cujType) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return; @@ -100,7 +86,7 @@ public final class InteractionJankMonitorWrapper { * Begin a trace session. * * @param v an attached view. - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. * @param timeout duration to cancel the instrumentation in ms */ public static void begin(View v, @CujType int cujType, long timeout) { @@ -115,7 +101,7 @@ public final class InteractionJankMonitorWrapper { * Begin a trace session. * * @param v an attached view. - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. * @param tag the tag to distinguish different flow of same type CUJ. */ public static void begin(View v, @CujType int cujType, String tag) { @@ -131,7 +117,7 @@ public final class InteractionJankMonitorWrapper { /** * End a trace session. * - * @param cujType the specific {@link InteractionJankMonitor.CujType}. + * @param cujType the specific {@link Cuj.CujType}. */ public static void end(@CujType int cujType) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) return; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java index be2c65fa4a45..cdd7b804fdf8 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardClockSwitchController.java @@ -72,7 +72,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.ui.SystemBarUtilsState; import com.android.systemui.util.ViewController; import com.android.systemui.util.concurrency.DelayableExecutor; import com.android.systemui.util.settings.SecureSettings; @@ -105,7 +105,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS private final NotificationIconContainerAlwaysOnDisplayViewModel mAodIconsViewModel; private final KeyguardRootViewModel mKeyguardRootViewModel; private final ConfigurationState mConfigurationState; - private final ConfigurationController mConfigurationController; + private final SystemBarUtilsState mSystemBarUtilsState; private final DozeParameters mDozeParameters; private final ScreenOffAnimationController mScreenOffAnimationController; private final AlwaysOnDisplayNotificationIconViewStore mAodIconViewStore; @@ -183,7 +183,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS KeyguardSliceViewController keyguardSliceViewController, NotificationIconAreaController notificationIconAreaController, LockscreenSmartspaceController smartspaceController, - ConfigurationController configurationController, + SystemBarUtilsState systemBarUtilsState, ScreenOffAnimationController screenOffAnimationController, StatusBarIconViewBindingFailureTracker iconViewBindingFailureTracker, KeyguardUnlockAnimationController keyguardUnlockAnimationController, @@ -208,7 +208,7 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mKeyguardSliceViewController = keyguardSliceViewController; mNotificationIconAreaController = notificationIconAreaController; mSmartspaceController = smartspaceController; - mConfigurationController = configurationController; + mSystemBarUtilsState = systemBarUtilsState; mScreenOffAnimationController = screenOffAnimationController; mIconViewBindingFailureTracker = iconViewBindingFailureTracker; mSecureSettings = secureSettings; @@ -619,13 +619,14 @@ public class KeyguardClockSwitchController extends ViewController<KeyguardClockS mAodIconsBindHandle.dispose(); } if (nic != null) { - final DisposableHandle viewHandle = NotificationIconContainerViewBinder.bind( - nic, - mAodIconsViewModel, - mConfigurationState, - mConfigurationController, - mIconViewBindingFailureTracker, - mAodIconViewStore); + final DisposableHandle viewHandle = + NotificationIconContainerViewBinder.bindWhileAttached( + nic, + mAodIconsViewModel, + mConfigurationState, + mSystemBarUtilsState, + mIconViewBindingFailureTracker, + mAodIconViewStore); final DisposableHandle visHandle = KeyguardRootViewBinder.bindAodIconVisibility( nic, mKeyguardRootViewModel.isNotifIconContainerVisible(), diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java index 38a8cd39a078..c4aa7a26be18 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityCallback.java @@ -104,4 +104,14 @@ public interface KeyguardSecurityCallback { */ default void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { } + + /** + * Shows the security screen that should be shown. + * + * This can be considered as a "refresh" of the bouncer view. Based on certain parameters, + * we might switch to a different bouncer screen. e.g. SimPin to SimPuk. + */ + default void showCurrentSecurityScreen() { + + } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java index f706301df1ca..0a4378e07b45 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java @@ -27,6 +27,8 @@ import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_SIM import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_PRIMARY; import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_SECONDARY_USER; import static com.android.keyguard.KeyguardSecurityContainer.USER_TYPE_WORK_PROFILE; +import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPin; +import static com.android.keyguard.KeyguardSecurityModel.SecurityMode.SimPuk; import static com.android.systemui.DejankUtils.whitelistIpcs; import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; import static com.android.systemui.flags.Flags.REVAMPED_BOUNCER_MESSAGES; @@ -99,6 +101,7 @@ import com.android.systemui.util.settings.GlobalSettings; import dagger.Lazy; import java.io.File; +import java.util.Arrays; import java.util.Optional; import javax.inject.Inject; @@ -164,8 +167,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } mCurrentUser = mSelectedUserInteractor.getSelectedUserId(); showPrimarySecurityScreen(false); - if (mCurrentSecurityMode != SecurityMode.SimPin - && mCurrentSecurityMode != SecurityMode.SimPuk) { + if (mCurrentSecurityMode != SimPin + && mCurrentSecurityMode != SimPuk) { reinflateViewFlipper((l) -> { }); } @@ -334,6 +337,11 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { mViewMediatorCallback.setNeedsInput(needsInput); } + + @Override + public void showCurrentSecurityScreen() { + showPrimarySecurityScreen(false); + } }; private final SwipeListener mSwipeListener = new SwipeListener() { @@ -888,7 +896,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard finish = true; eventSubtype = BOUNCER_DISMISS_SIM; uiEvent = BouncerUiEvent.BOUNCER_DISMISS_SIM; - } else { + } else if (Arrays.asList(SimPin, SimPuk).contains(securityMode)) { + // There are additional screens to the sim pin/puk flow. showSecurityScreen(securityMode); } break; @@ -1095,8 +1104,8 @@ public class KeyguardSecurityContainerController extends ViewController<Keyguard } private void configureMode() { - boolean useSimSecurity = mCurrentSecurityMode == SecurityMode.SimPin - || mCurrentSecurityMode == SecurityMode.SimPuk; + boolean useSimSecurity = mCurrentSecurityMode == SimPin + || mCurrentSecurityMode == SimPuk; int mode = KeyguardSecurityContainer.MODE_DEFAULT; if (canDisplayUserSwitcher() && !useSimSecurity) { mode = KeyguardSecurityContainer.MODE_USER_SWITCHER; diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java index 6e242084d68c..c5e70703cd2b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java @@ -16,6 +16,8 @@ package com.android.keyguard; +import static android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID; + import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat; import android.annotation.NonNull; @@ -60,7 +62,7 @@ public class KeyguardSimPinViewController // When this is true and when SIM card is PIN locked state, on PIN lock screen, message would // be displayed to inform user about the number of remaining PIN attempts left. private boolean mShowDefaultMessage; - private int mSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID; + private int mSubId = INVALID_SUBSCRIPTION_ID; private AlertDialog mRemainingAttemptsDialog; private ImageView mSimImageView; @@ -68,6 +70,12 @@ public class KeyguardSimPinViewController @Override public void onSimStateChanged(int subId, int slotId, int simState) { if (DEBUG) Log.v(TAG, "onSimStateChanged(subId=" + subId + ",state=" + simState + ")"); + // If subId has gone to PUK required then we need to go to the PUK screen. + if (subId == mSubId && simState == TelephonyManager.SIM_STATE_PUK_REQUIRED) { + getKeyguardSecurityCallback().showCurrentSecurityScreen(); + return; + } + if (simState == TelephonyManager.SIM_STATE_READY) { mRemainingAttempts = -1; resetState(); diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt index dd4ca92fcc02..fda23b7f2a9c 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt @@ -24,9 +24,9 @@ import android.os.UserHandle import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationResultModel -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application @@ -60,14 +60,6 @@ import kotlinx.coroutines.withContext /** Defines interface for classes that can access authentication-related application state. */ interface AuthenticationRepository { /** - * Whether the auto confirm feature is enabled for the currently-selected user. - * - * Note that the length of the PIN is also important to take into consideration, please see - * [hintedPinLength]. - */ - val isAutoConfirmFeatureEnabled: StateFlow<Boolean> - - /** * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user * in order to unlock the device. */ @@ -88,10 +80,22 @@ interface AuthenticationRepository { val isPatternVisible: StateFlow<Boolean> /** - * The current authentication throttling state, set when the user has to wait before being able - * to try another authentication attempt. `null` indicates throttling isn't active. + * The current authentication lockout (aka "throttling") state, set when the user has to wait + * before being able to try another authentication attempt. `null` indicates throttling isn't + * active. */ - val throttling: MutableStateFlow<AuthenticationThrottlingModel?> + val lockout: MutableStateFlow<AuthenticationLockoutModel?> + + /** Whether throttling has occurred at least once since the last successful authentication. */ + val hasLockoutOccurred: MutableStateFlow<Boolean> + + /** + * Whether the auto confirm feature is enabled for the currently-selected user. + * + * Note that the length of the PIN is also important to take into consideration, please see + * [hintedPinLength]. + */ + val isAutoConfirmFeatureEnabled: StateFlow<Boolean> /** * The currently-configured authentication method. This determines how the authentication @@ -135,22 +139,25 @@ interface AuthenticationRepository { /** Reports an authentication attempt. */ suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) + /** Reports that the user has entered a temporary device lockout (throttling). */ + suspend fun reportLockoutStarted(durationMs: Int) + /** Returns the current number of failed authentication attempts. */ suspend fun getFailedAuthenticationAttemptCount(): Int /** - * Returns the timestamp for when the current throttling will end, allowing the user to attempt + * Returns the timestamp for when the current lockout will end, allowing the user to attempt * authentication again. * * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime]. */ - suspend fun getThrottlingEndTimestamp(): Long + suspend fun getLockoutEndTimestamp(): Long /** - * Sets the throttling timeout duration (time during which the user should not be allowed to + * Sets the lockout timeout duration (time during which the user should not be allowed to * attempt authentication). */ - suspend fun setThrottleDuration(durationMs: Int) + suspend fun setLockoutDuration(durationMs: Int) /** * Checks the given [LockscreenCredential] to see if it's correct, returning an @@ -172,11 +179,6 @@ constructor( mobileConnectionsRepository: MobileConnectionsRepository, ) : AuthenticationRepository { - override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = - refreshingFlow( - initialValue = false, - getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, - ) override val authenticationChallengeResult = MutableSharedFlow<Boolean>() override val hintedPinLength: Int = 6 @@ -187,11 +189,15 @@ constructor( getFreshValue = lockPatternUtils::isVisiblePatternEnabled, ) - override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> = - MutableStateFlow(null) + override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null) - private val selectedUserId: Int - get() = userRepository.getSelectedUserInfo().id + override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false) + + override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = + refreshingFlow( + initialValue = false, + getFreshValue = lockPatternUtils::isAutoPinConfirmEnabled, + ) override val authenticationMethod: Flow<AuthenticationMethodModel> = combine(userRepository.selectedUserInfo, mobileConnectionsRepository.isAnySimSecure) { @@ -249,19 +255,25 @@ constructor( } } + override suspend fun reportLockoutStarted(durationMs: Int) { + return withContext(backgroundDispatcher) { + lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId) + } + } + override suspend fun getFailedAuthenticationAttemptCount(): Int { return withContext(backgroundDispatcher) { lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId) } } - override suspend fun getThrottlingEndTimestamp(): Long { + override suspend fun getLockoutEndTimestamp(): Long { return withContext(backgroundDispatcher) { lockPatternUtils.getLockoutAttemptDeadline(selectedUserId) } } - override suspend fun setThrottleDuration(durationMs: Int) { + override suspend fun setLockoutDuration(durationMs: Int) { withContext(backgroundDispatcher) { lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs) } @@ -273,13 +285,16 @@ constructor( return withContext(backgroundDispatcher) { try { val matched = lockPatternUtils.checkCredential(credential, selectedUserId) {} - AuthenticationResultModel(isSuccessful = matched, throttleDurationMs = 0) + AuthenticationResultModel(isSuccessful = matched, lockoutDurationMs = 0) } catch (ex: LockPatternUtils.RequestThrottledException) { - AuthenticationResultModel(isSuccessful = false, throttleDurationMs = ex.timeoutMs) + AuthenticationResultModel(isSuccessful = false, lockoutDurationMs = ex.timeoutMs) } } } + private val selectedUserId: Int + get() = userRepository.getSelectedUserInfo().id + /** * Returns a [StateFlow] that's automatically kept fresh. The passed-in [getFreshValue] is * invoked on a background thread every time the selected user is changed and every time a new diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 4e67771cba82..797154e85082 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -20,9 +20,9 @@ import com.android.app.tracing.TraceUtils.Companion.withContext import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.systemui.authentication.data.repository.AuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -85,25 +85,25 @@ constructor( val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod /** - * The current authentication throttling state, set when the user has to wait before being able - * to try another authentication attempt. `null` indicates throttling isn't active. + * The current authentication lockout (aka "throttling") state, set when the user has to wait + * before being able to try another authentication attempt. `null` indicates lockout isn't + * active. */ - val throttling: StateFlow<AuthenticationThrottlingModel?> = repository.throttling + val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout /** * Whether the auto confirm feature is enabled for the currently-selected user. * * Note that the length of the PIN is also important to take into consideration, please see * [hintedPinLength]. - * - * During throttling, this is always disabled (`false`). */ val isAutoConfirmEnabled: StateFlow<Boolean> = - combine(repository.isAutoConfirmFeatureEnabled, repository.throttling) { + combine(repository.isAutoConfirmFeatureEnabled, repository.hasLockoutOccurred) { featureEnabled, - throttling -> - // Disable auto-confirm during throttling. - featureEnabled && throttling == null + hasLockoutOccurred -> + // Disable auto-confirm if lockout occurred since the last successful + // authentication attempt. + featureEnabled && !hasLockoutOccurred } .stateIn( scope = applicationScope, @@ -140,7 +140,7 @@ constructor( /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */ val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled - private var throttlingCountdownJob: Job? = null + private var lockoutCountdownJob: Job? = null init { applicationScope.launch { @@ -189,8 +189,8 @@ constructor( val authMethod = getAuthenticationMethod() val skipCheck = when { - // Throttling is active, the UI layer should not have called this; skip the attempt. - throttling.value != null -> true + // Lockout is active, the UI layer should not have called this; skip the attempt. + lockout.value != null -> true // The input is too short; skip the attempt. input.isTooShort(authMethod) -> true // Auto-confirm attempt when the feature is not enabled; skip the attempt. @@ -216,18 +216,22 @@ constructor( ) } - // Check if we need to throttle and, if so, kick off the throttle countdown: - if (!authenticationResult.isSuccessful && authenticationResult.throttleDurationMs > 0) { - repository.setThrottleDuration( - durationMs = authenticationResult.throttleDurationMs, - ) - startThrottlingCountdown() + // Check if lockout should start and, if so, kick off the countdown: + if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) { + repository.apply { + setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs) + reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs) + hasLockoutOccurred.value = true + } + startLockoutCountdown() } if (authenticationResult.isSuccessful) { - // Since authentication succeeded, we should refresh throttling to make sure that our - // state is completely reflecting the upstream source of truth. - refreshThrottling() + // Since authentication succeeded, refresh lockout to make sure the state is completely + // reflecting the upstream source of truth. + refreshLockout() + + repository.hasLockoutOccurred.value = false } return if (authenticationResult.isSuccessful) { @@ -245,52 +249,52 @@ constructor( } } - /** Starts refreshing the throttling state every second. */ - private suspend fun startThrottlingCountdown() { - cancelThrottlingCountdown() - throttlingCountdownJob = + /** Starts refreshing the lockout state every second. */ + private suspend fun startLockoutCountdown() { + cancelLockoutCountdown() + lockoutCountdownJob = applicationScope.launch { - while (refreshThrottling()) { + while (refreshLockout()) { delay(1.seconds.inWholeMilliseconds) } } } - /** Cancels any throttling state countdown started in [startThrottlingCountdown]. */ - private fun cancelThrottlingCountdown() { - throttlingCountdownJob?.cancel() - throttlingCountdownJob = null + /** Cancels any lockout state countdown started in [startLockoutCountdown]. */ + private fun cancelLockoutCountdown() { + lockoutCountdownJob?.cancel() + lockoutCountdownJob = null } /** Notifies that the currently-selected user has changed. */ private suspend fun onSelectedUserChanged() { - cancelThrottlingCountdown() - if (refreshThrottling()) { - startThrottlingCountdown() + cancelLockoutCountdown() + if (refreshLockout()) { + startLockoutCountdown() } } /** - * Refreshes the throttling state, hydrating the repository with the latest state. + * Refreshes the lockout state, hydrating the repository with the latest state. * - * @return Whether throttling is active or not. + * @return Whether lockout is active or not. */ - private suspend fun refreshThrottling(): Boolean { - withContext("$TAG#refreshThrottling", backgroundDispatcher) { + private suspend fun refreshLockout(): Boolean { + withContext("$TAG#refreshLockout", backgroundDispatcher) { val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() } - val deadline = async { repository.getThrottlingEndTimestamp() } + val deadline = async { repository.getLockoutEndTimestamp() } val remainingMs = max(0, deadline.await() - clock.elapsedRealtime()) - repository.throttling.value = + repository.lockout.value = if (remainingMs > 0) { - AuthenticationThrottlingModel( + AuthenticationLockoutModel( failedAttemptCount = failedAttemptCount.await(), remainingSeconds = ceil(remainingMs / 1000f).toInt(), ) } else { - null // Throttling ended. + null // Lockout ended. } } - return repository.throttling.value != null + return repository.lockout.value != null } private fun AuthenticationMethodModel.createCredential( diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt index 8392528b86aa..8ee2d5e02bad 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationThrottlingModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationLockoutModel.kt @@ -16,17 +16,17 @@ package com.android.systemui.authentication.shared.model -/** Models a state for throttling the next authentication attempt. */ -data class AuthenticationThrottlingModel( +/** Models a state for temporarily locking out the next authentication attempt. */ +data class AuthenticationLockoutModel( - /** Number of failed authentication attempts so far. If not throttling this will be `0`. */ + /** Number of failed authentication attempts so far. If not locked out this will be `0`. */ val failedAttemptCount: Int = 0, /** * Remaining amount of time, in seconds, before another authentication attempt can be done. If - * not throttling this will be `0`. + * not locked out this will be `0`. * - * This number is changed throughout the timeout. + * This number is changed throughout the lockout. * * Note: this isn't precise (in milliseconds), but rounded up to ensure "at most" this amount of * seconds remains. diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt index f2a3e74700db..addc75e52fad 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationResultModel.kt @@ -21,5 +21,5 @@ data class AuthenticationResultModel( /** Whether authentication was successful. */ val isSuccessful: Boolean = false, /** If [isSuccessful] is `false`, how long the user must wait before trying again. */ - val throttleDurationMs: Int = 0, + val lockoutDurationMs: Int = 0, ) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt index 677f60d405b8..724c0fe1e4e4 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt @@ -19,8 +19,8 @@ package com.android.systemui.bouncer.domain.interactor import android.content.Context import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.authentication.domain.interactor.AuthenticationResult +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.bouncer.data.repository.BouncerRepository import com.android.systemui.classifier.FalsingClassifier import com.android.systemui.classifier.domain.interactor.FalsingInteractor @@ -60,24 +60,25 @@ constructor( /** The user-facing message to show in the bouncer. */ val message: StateFlow<String?> = - combine(repository.message, authenticationInteractor.throttling) { message, throttling -> - messageOrThrottlingMessage(message, throttling) + combine(repository.message, authenticationInteractor.lockout) { message, lockout -> + messageOrLockoutMessage(message, lockout) } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), initialValue = - messageOrThrottlingMessage( + messageOrLockoutMessage( repository.message.value, - authenticationInteractor.throttling.value, + authenticationInteractor.lockout.value, ) ) /** - * The current authentication throttling state, set when the user has to wait before being able - * to try another authentication attempt. `null` indicates throttling isn't active. + * The current authentication lockout (aka "throttling") state, set when the user has to wait + * before being able to try another authentication attempt. `null` indicates lockout isn't + * active. */ - val throttling: StateFlow<AuthenticationThrottlingModel?> = authenticationInteractor.throttling + val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout /** Whether the auto confirm feature is enabled for the currently-selected user. */ val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled @@ -102,9 +103,9 @@ constructor( init { if (flags.isEnabled()) { - // Clear the message if moved from throttling to no-longer throttling. + // Clear the message if moved from locked-out to no-longer locked-out. applicationScope.launch { - throttling.pairwise().collect { (previous, current) -> + lockout.pairwise().collect { (previous, current) -> if (previous != null && current == null) { clearMessage() } @@ -213,9 +214,9 @@ constructor( * Shows the error message. * * Callers should use this instead of [authenticate] when they know ahead of time that an auth - * attempt will fail but aren't interested in the other side effects like triggering throttling. + * attempt will fail but aren't interested in the other side effects like triggering lockout. * For example, if the user entered a pattern that's too short, the system can show the error - * message without having the attempt trigger throttling. + * message without having the attempt trigger lockout. */ private suspend fun showErrorMessage() { repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod())) @@ -250,15 +251,15 @@ constructor( } } - private fun messageOrThrottlingMessage( + private fun messageOrLockoutMessage( message: String?, - throttlingModel: AuthenticationThrottlingModel?, + lockoutModel: AuthenticationLockoutModel?, ): String { return when { - throttlingModel != null -> + lockoutModel != null -> applicationContext.getString( com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown, - throttlingModel.remainingSeconds, + lockoutModel.remainingSeconds, ) message != null -> message else -> "" diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt index e379dab918ef..0d7f6dcce1c7 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt @@ -50,12 +50,12 @@ sealed class AuthMethodBouncerViewModel( abstract val authenticationMethod: AuthenticationMethodModel /** - * String resource ID of the failure message to be shown during throttling. + * String resource ID of the failure message to be shown during lockout. * * The message must include 2 number parameters: the first one indicating how many unsuccessful - * attempts were made, and the second one indicating in how many seconds throttling will expire. + * attempts were made, and the second one indicating in how many seconds lockout will expire. */ - @get:StringRes abstract val throttlingMessageId: Int + @get:StringRes abstract val lockoutMessageId: Int /** Notifies that the UI has been shown to the user. */ fun onShown() { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt index fefc3e3411dd..4b1434323886 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt @@ -105,17 +105,17 @@ class BouncerViewModel( get() = bouncerInteractor.isUserSwitcherVisible private val isInputEnabled: StateFlow<Boolean> = - bouncerInteractor.throttling + bouncerInteractor.lockout .map { it == null } .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), - initialValue = bouncerInteractor.throttling.value == null, + initialValue = bouncerInteractor.lockout.value == null, ) // Handle to the scope of the child ViewModel (stored in [authMethod]). private var childViewModelScope: CoroutineScope? = null - private val _throttlingDialogMessage = MutableStateFlow<String?>(null) + private val _dialogMessage = MutableStateFlow<String?>(null) /** View-model for the current UI, based on the current authentication method. */ val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> = @@ -128,20 +128,20 @@ class BouncerViewModel( ) /** - * A message for a throttling dialog to show when the user has attempted the wrong credential - * too many times and now must wait a while before attempting again. + * A message for a dialog to show when the user has attempted the wrong credential too many + * times and now must wait a while before attempting again. * * If `null`, no dialog should be shown. * - * Once the dialog is shown, the UI should call [onThrottlingDialogDismissed] when the user - * dismisses this dialog. + * Once the dialog is shown, the UI should call [onDialogDismissed] when the user dismisses this + * dialog. */ - val throttlingDialogMessage: StateFlow<String?> = _throttlingDialogMessage.asStateFlow() + val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow() /** The user-facing message to show in the bouncer. */ val message: StateFlow<MessageViewModel> = - combine(bouncerInteractor.message, bouncerInteractor.throttling) { message, throttling -> - toMessageViewModel(message, isThrottled = throttling != null) + combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout -> + toMessageViewModel(message, isLockedOut = lockout != null) } .stateIn( scope = applicationScope, @@ -149,7 +149,7 @@ class BouncerViewModel( initialValue = toMessageViewModel( message = bouncerInteractor.message.value, - isThrottled = bouncerInteractor.throttling.value != null, + isLockedOut = bouncerInteractor.lockout.value != null, ), ) @@ -197,28 +197,28 @@ class BouncerViewModel( init { if (flags.isEnabled()) { applicationScope.launch { - combine(bouncerInteractor.throttling, authMethodViewModel) { - throttling, + combine(bouncerInteractor.lockout, authMethodViewModel) { + lockout, authMethodViewModel -> - if (throttling != null && authMethodViewModel != null) { + if (lockout != null && authMethodViewModel != null) { applicationContext.getString( - authMethodViewModel.throttlingMessageId, - throttling.failedAttemptCount, - throttling.remainingSeconds, + authMethodViewModel.lockoutMessageId, + lockout.failedAttemptCount, + lockout.remainingSeconds, ) } else { null } } .distinctUntilChanged() - .collect { dialogMessage -> _throttlingDialogMessage.value = dialogMessage } + .collect { dialogMessage -> _dialogMessage.value = dialogMessage } } } } - /** Notifies that a throttling dialog has been dismissed by the user. */ - fun onThrottlingDialogDismissed() { - _throttlingDialogMessage.value = null + /** Notifies that the dialog has been dismissed by the user. */ + fun onDialogDismissed() { + _dialogMessage.value = null } private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean { @@ -231,11 +231,11 @@ class BouncerViewModel( private fun toMessageViewModel( message: String?, - isThrottled: Boolean, + isLockedOut: Boolean, ): MessageViewModel { return MessageViewModel( text = message ?: "", - isUpdateAnimated = !isThrottled, + isUpdateAnimated = !isLockedOut, ) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt index 3b7e32140560..b68271767fc2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt @@ -46,7 +46,7 @@ class PasswordBouncerViewModel( override val authenticationMethod = AuthenticationMethodModel.Password - override val throttlingMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message + override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message /** Whether the input method editor (for example, the software keyboard) is visible. */ private var isImeVisible: Boolean = false @@ -56,13 +56,13 @@ class PasswordBouncerViewModel( /** Whether the UI should request focus on the text field element. */ val isTextFieldFocusRequested = - combine(interactor.throttling, isTextFieldFocused) { throttling, hasFocus -> + combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus -> throttling == null && !hasFocus } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(), - initialValue = interactor.throttling.value == null && !isTextFieldFocused.value, + initialValue = interactor.lockout.value == null && !isTextFieldFocused.value, ) override fun onHidden() { @@ -104,7 +104,7 @@ class PasswordBouncerViewModel( * hidden. */ suspend fun onImeVisibilityChanged(isVisible: Boolean) { - if (isImeVisible && !isVisible && interactor.throttling.value == null) { + if (isImeVisible && !isVisible && interactor.lockout.value == null) { interactor.onImeHiddenByUser() } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt index b1c5ab6122fe..69f8032ef4f2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt @@ -80,7 +80,7 @@ class PatternBouncerViewModel( override val authenticationMethod = AuthenticationMethodModel.Pattern - override val throttlingMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message + override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message /** Notifies that the user has started a drag gesture across the dot grid. */ fun onDragStart() { diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt index e25e82fe04c3..7f4a0296ebdc 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt @@ -104,7 +104,7 @@ class PinBouncerViewModel( override val authenticationMethod: AuthenticationMethodModel = authenticationMethod - override val throttlingMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message + override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message init { viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } } diff --git a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt index fdd98bec0a2d..3063ebd60b0c 100644 --- a/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt +++ b/packages/SystemUI/src/com/android/systemui/common/shared/model/NotificationContainerBounds.kt @@ -18,8 +18,12 @@ package com.android.systemui.common.shared.model /** Models the bounds of the notification container. */ data class NotificationContainerBounds( + /** The position of the left of the container in its window coordinate system, in pixels. */ + val left: Float = 0f, /** The position of the top of the container in its window coordinate system, in pixels. */ val top: Float = 0f, + /** The position of the right of the container in its window coordinate system, in pixels. */ + val right: Float = 0f, /** The position of the bottom of the container in its window coordinate system, in pixels. */ val bottom: Float = 0f, /** Whether any modifications to top/bottom should be smoothly animated. */ diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java index 0405ca43320f..ca8268dc89a3 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java @@ -82,6 +82,7 @@ import com.android.systemui.qs.FgsManagerControllerImpl; import com.android.systemui.qs.QSFragmentStartableModule; import com.android.systemui.qs.footer.dagger.FooterActionsModule; import com.android.systemui.recents.Recents; +import com.android.systemui.recordissue.RecordIssueModule; import com.android.systemui.retail.dagger.RetailModeModule; import com.android.systemui.scene.ui.view.WindowRootViewComponent; import com.android.systemui.screenrecord.ScreenRecordModule; @@ -209,6 +210,7 @@ import javax.inject.Named; PrivacyModule.class, QRCodeScannerModule.class, QSFragmentStartableModule.class, + RecordIssueModule.class, ReferenceModule.class, RetailModeModule.class, ScreenshotModule.class, diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt index 6a0e88246027..d5b95d6721f9 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt @@ -16,7 +16,10 @@ package com.android.systemui.flags +import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR +import com.android.systemui.Flags.keyguardBottomAreaRefactor import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor @@ -29,5 +32,9 @@ class FlagDependencies @Inject constructor(featureFlags: FeatureFlagsClassic, ha override fun defineDependencies() { NotificationsLiveDataStoreRefactor.token dependsOn NotificationIconContainerRefactor.token FooterViewRefactor.token dependsOn NotificationIconContainerRefactor.token + + val keyguardBottomAreaRefactor = FlagToken( + FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor()) + KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index cbfd17ff7ae4..9fe5c3f53d96 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -214,7 +214,7 @@ constructor( private fun listenForLockscreenToPrimaryBouncerDragging() { var transitionId: UUID? = null scope.launch("$TAG#listenForLockscreenToPrimaryBouncerDragging") { - shadeRepository.shadeModel + shadeRepository.legacyShadeExpansion .sample( combine( transitionInteractor.startedKeyguardTransitionStep, @@ -224,23 +224,23 @@ constructor( ), ::toQuad ) - .collect { (shadeModel, keyguardState, statusBarState, isKeyguardUnlocked) -> + .collect { (shadeExpansion, keyguardState, statusBarState, isKeyguardUnlocked) -> val id = transitionId if (id != null) { if (keyguardState.to == KeyguardState.PRIMARY_BOUNCER) { // An existing `id` means a transition is started, and calls to // `updateTransition` will control it until FINISHED or CANCELED var nextState = - if (shadeModel.expansionAmount == 0f) { + if (shadeExpansion == 0f) { TransitionState.FINISHED - } else if (shadeModel.expansionAmount == 1f) { + } else if (shadeExpansion == 1f) { TransitionState.CANCELED } else { TransitionState.RUNNING } transitionRepository.updateTransition( id, - 1f - shadeModel.expansionAmount, + 1f - shadeExpansion, nextState, ) @@ -274,7 +274,7 @@ constructor( // integrated into KeyguardTransitionRepository if ( keyguardState.to == KeyguardState.LOCKSCREEN && - shadeModel.isUserDragging && + shadeRepository.legacyShadeTracking.value && !isKeyguardUnlocked && statusBarState == KEYGUARD ) { diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 702386d3b498..c12efe875b0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -224,8 +224,8 @@ constructor( configurationInteractor .dimensionPixelSize(R.dimen.keyguard_translate_distance_on_swipe_up) .flatMapLatest { translationDistance -> - shadeRepository.shadeModel.map { - if (it.expansionAmount == 0f) { + shadeRepository.legacyShadeExpansion.map { + if (it == 0f) { // Reset the translation value 0f } else { @@ -233,7 +233,7 @@ constructor( MathUtils.lerp( translationDistance, 0, - Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it.expansionAmount) + Interpolators.FAST_OUT_LINEAR_IN.getInterpolation(it) ) } } 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 448411edb168..8dde399ed754 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 @@ -46,6 +46,7 @@ import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAfforda import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.policy.KeyguardStateController @@ -66,6 +67,7 @@ class KeyguardQuickAffordanceInteractor @Inject constructor( private val keyguardInteractor: KeyguardInteractor, + private val shadeInteractor: ShadeInteractor, private val lockPatternUtils: LockPatternUtils, private val keyguardStateController: KeyguardStateController, private val userTracker: UserTracker, @@ -100,9 +102,10 @@ constructor( quickAffordanceAlwaysVisible(position), keyguardInteractor.isDozing, keyguardInteractor.isKeyguardShowing, + shadeInteractor.anyExpansion, biometricSettingsRepository.isCurrentUserInLockdown, - ) { affordance, isDozing, isKeyguardShowing, isUserInLockdown -> - if (!isDozing && isKeyguardShowing && !isUserInLockdown) { + ) { affordance, isDozing, isKeyguardShowing, qsExpansion, isUserInLockdown -> + if (!isDozing && isKeyguardShowing && (qsExpansion < 1.0f) && !isUserInLockdown) { affordance } else { KeyguardQuickAffordanceModel.Hidden diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt index 96efb237047e..39a0547ded26 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt @@ -40,7 +40,7 @@ import com.android.systemui.statusbar.notification.icon.ui.viewmodel.Notificatio import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.phone.NotificationIconAreaController import com.android.systemui.statusbar.phone.NotificationIconContainer -import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.ui.SystemBarUtilsState import javax.inject.Inject import kotlinx.coroutines.DisposableHandle @@ -49,13 +49,13 @@ class AodNotificationIconsSection constructor( private val context: Context, private val configurationState: ConfigurationState, - private val configurationController: ConfigurationController, private val featureFlags: FeatureFlagsClassic, private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel, private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore, private val notificationIconAreaController: NotificationIconAreaController, private val smartspaceViewModel: KeyguardSmartspaceViewModel, + private val systemBarUtilsState: SystemBarUtilsState, ) : KeyguardSection() { private var nicBindingDisposable: DisposableHandle? = null @@ -89,11 +89,11 @@ constructor( if (NotificationIconContainerRefactor.isEnabled) { nicBindingDisposable?.dispose() nicBindingDisposable = - NotificationIconContainerViewBinder.bind( + NotificationIconContainerViewBinder.bindWhileAttached( nic, nicAodViewModel, configurationState, - configurationController, + systemBarUtilsState, iconBindingFailureTracker, nicAodIconViewStore, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt index a64a422a1924..e7b6e44450bd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultNotificationStackScrollLayoutSection.kt @@ -26,7 +26,6 @@ import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R @@ -92,13 +91,7 @@ constructor( connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) - val lockId = - if (DeviceEntryUdfpsRefactor.isEnabled) { - R.id.device_entry_icon_view - } else { - R.id.lock_icon_view - } - connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP) + addNotificationPlaceholderBarrier(this) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt index a25471cba66d..400d0dc2b242 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/NotificationStackScrollLayoutSection.kt @@ -20,7 +20,12 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context import android.view.View import android.view.ViewGroup +import androidx.constraintlayout.widget.Barrier import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.constraintlayout.widget.ConstraintSet.BOTTOM +import androidx.constraintlayout.widget.ConstraintSet.TOP +import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.shared.model.KeyguardSection import com.android.systemui.res.R @@ -54,6 +59,29 @@ constructor( private val placeHolderId = R.id.nssl_placeholder private var disposableHandle: DisposableHandle? = null + /** + * Align the notification placeholder bottom to the top of either the lock icon or the ambient + * indication area, whichever is higher. + */ + protected fun addNotificationPlaceholderBarrier(constraintSet: ConstraintSet) { + val lockId = + if (DeviceEntryUdfpsRefactor.isEnabled) { + R.id.device_entry_icon_view + } else { + R.id.lock_icon_view + } + + constraintSet.apply { + createBarrier( + R.id.nssl_placeholder_barrier_bottom, + Barrier.TOP, + 0, + *intArrayOf(lockId, R.id.ambient_indication_container) + ) + connect(R.id.nssl_placeholder, BOTTOM, R.id.nssl_placeholder_barrier_bottom, TOP) + } + } + override fun addViews(constraintLayout: ConstraintLayout) { if (!KeyguardShadeMigrationNssl.isEnabled) { return @@ -85,9 +113,9 @@ constructor( ) if (sceneContainerFlags.flexiNotifsEnabled()) { NotificationStackAppearanceViewBinder.bind( + context, sharedNotificationContainer, notificationStackAppearanceViewModel, - sceneContainerFlags, ambientState, controller, ) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt index f5963be55b2d..b0b5c81dd11c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt @@ -19,14 +19,12 @@ package com.android.systemui.keyguard.ui.view.layout.sections import android.content.Context import androidx.constraintlayout.widget.ConstraintSet -import androidx.constraintlayout.widget.ConstraintSet.BOTTOM import androidx.constraintlayout.widget.ConstraintSet.END import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import com.android.systemui.Flags.migrateClocksToBlueprint import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel import com.android.systemui.res.R @@ -97,13 +95,7 @@ constructor( connect(R.id.nssl_placeholder, START, PARENT_ID, START) connect(R.id.nssl_placeholder, END, PARENT_ID, END) - val lockId = - if (DeviceEntryUdfpsRefactor.isEnabled) { - R.id.device_entry_icon_view - } else { - R.id.lock_icon_view - } - connect(R.id.nssl_placeholder, BOTTOM, lockId, TOP) + addNotificationPlaceholderBarrier(this) } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt index 4588e02df10e..1d4520ff8f03 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt @@ -111,10 +111,21 @@ constructor( /** An observable for the alpha level for the entire keyguard root view. */ val alpha: Flow<Float> = - merge( - keyguardInteractor.keyguardAlpha.distinctUntilChanged(), - occludedToLockscreenTransitionViewModel.lockscreenAlpha, - ) + combine( + keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) }, + merge( + keyguardInteractor.keyguardAlpha, + occludedToLockscreenTransitionViewModel.lockscreenAlpha, + ) + ) { transitionToGone, alpha -> + if (transitionToGone == 1f) { + // Ensures content is not visible when in GONE state + 0f + } else { + alpha + } + } + .distinctUntilChanged() private fun burnIn(): Flow<BurnInModel> { val dozingAmount: Flow<Float> = @@ -229,7 +240,9 @@ constructor( .distinctUntilChanged() fun onNotificationContainerBoundsChanged(top: Float, bottom: Float) { - keyguardInteractor.setNotificationContainerBounds(NotificationContainerBounds(top, bottom)) + keyguardInteractor.setNotificationContainerBounds( + NotificationContainerBounds(top = top, bottom = bottom) + ) } /** Is there an expanded pulse, are we animating in response? */ diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt new file mode 100644 index 000000000000..a4088f81f062 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt @@ -0,0 +1,112 @@ +/* + * 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.tiles + +import android.content.Intent +import android.os.Handler +import android.os.Looper +import android.service.quicksettings.Tile +import android.text.TextUtils +import android.view.View +import android.widget.Switch +import androidx.annotation.VisibleForTesting +import com.android.internal.logging.MetricsLogger +import com.android.systemui.Flags.recordIssueQsTile +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.FalsingManager +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.QsEventLogger +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.res.R +import javax.inject.Inject + +class RecordIssueTile +@Inject +constructor( + host: QSHost, + uiEventLogger: QsEventLogger, + @Background backgroundLooper: Looper, + @Main mainHandler: Handler, + falsingManager: FalsingManager, + metricsLogger: MetricsLogger, + statusBarStateController: StatusBarStateController, + activityStarter: ActivityStarter, + qsLogger: QSLogger +) : + QSTileImpl<QSTile.BooleanState>( + host, + uiEventLogger, + backgroundLooper, + mainHandler, + falsingManager, + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger + ) { + + @VisibleForTesting var isRecording: Boolean = false + + override fun getTileLabel(): CharSequence = mContext.getString(R.string.qs_record_issue_label) + + override fun isAvailable(): Boolean = recordIssueQsTile() + + override fun newTileState(): QSTile.BooleanState = + QSTile.BooleanState().apply { + label = tileLabel + handlesLongClick = false + } + + override fun handleClick(view: View?) { + isRecording = !isRecording + refreshState() + } + + override fun getLongClickIntent(): Intent? = null + + @VisibleForTesting + public override fun handleUpdateState(qsTileState: QSTile.BooleanState, arg: Any?) { + qsTileState.apply { + if (isRecording) { + value = true + state = Tile.STATE_ACTIVE + forceExpandIcon = false + secondaryLabel = mContext.getString(R.string.qs_record_issue_stop) + icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_on) + } else { + value = false + state = Tile.STATE_INACTIVE + forceExpandIcon = true + secondaryLabel = mContext.getString(R.string.qs_record_issue_start) + icon = ResourceIcon.get(R.drawable.qs_record_issue_icon_off) + } + label = tileLabel + contentDescription = + if (TextUtils.isEmpty(secondaryLabel)) label else "$label, $secondaryLabel" + expandedAccessibilityClassName = Switch::class.java.name + } + } + + companion object { + const val TILE_SPEC = "record_issue" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt new file mode 100644 index 000000000000..d67cf4d3d098 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueModule.kt @@ -0,0 +1,33 @@ +/* + * 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.recordissue + +import com.android.systemui.qs.tileimpl.QSTileImpl +import com.android.systemui.qs.tiles.RecordIssueTile +import dagger.Binds +import dagger.Module +import dagger.multibindings.IntoMap +import dagger.multibindings.StringKey + +@Module +interface RecordIssueModule { + /** Inject RecordIssueTile into tileMap in QSModule */ + @Binds + @IntoMap + @StringKey(RecordIssueTile.TILE_SPEC) + fun bindRecordIssueTile(recordIssueTile: RecordIssueTile): QSTileImpl<*> +} diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt index e94a3eb5db22..2445bdb17955 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt @@ -15,25 +15,14 @@ */ package com.android.systemui.shade.data.repository -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging -import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.shade.ShadeExpansionChangeEvent -import com.android.systemui.shade.ShadeExpansionListener -import com.android.systemui.shade.ShadeExpansionStateManager -import com.android.systemui.shade.domain.model.ShadeModel import javax.inject.Inject -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged +/** Data for the shade, mostly related to expansion of the shade and quick settings. */ interface ShadeRepository { - /** ShadeModel information regarding shade expansion events */ - val shadeModel: Flow<ShadeModel> - /** * Amount qs has expanded, [0-1]. 0 means fully collapsed, 1 means fully expanded. Quick * Settings can be expanded without the full shade expansion. @@ -167,34 +156,7 @@ interface ShadeRepository { /** Business logic for shade interactions */ @SysUISingleton -class ShadeRepositoryImpl -@Inject -constructor(shadeExpansionStateManager: ShadeExpansionStateManager) : ShadeRepository { - override val shadeModel: Flow<ShadeModel> = - conflatedCallbackFlow { - val callback = - object : ShadeExpansionListener { - override fun onPanelExpansionChanged(event: ShadeExpansionChangeEvent) { - // Don't propagate ShadeExpansionChangeEvent.dragDownPxAmount field. - // It is too noisy and produces extra events that consumers won't care - // about - val info = - ShadeModel( - expansionAmount = event.fraction, - isExpanded = event.expanded, - isUserDragging = event.tracking - ) - trySendWithFailureLogging(info, TAG, "updated shade expansion info") - } - } - - val currentState = shadeExpansionStateManager.addExpansionListener(callback) - callback.onPanelExpansionChanged(currentState) - - awaitClose { shadeExpansionStateManager.removeExpansionListener(callback) } - } - .distinctUntilChanged() - +class ShadeRepositoryImpl @Inject constructor() : ShadeRepository { private val _qsExpansion = MutableStateFlow(0f) override val qsExpansion: StateFlow<Float> = _qsExpansion.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt deleted file mode 100644 index ce0f4283ff83..000000000000 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/model/ShadeModel.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2022 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ -package com.android.systemui.shade.domain.model - -import android.annotation.FloatRange - -/** Information about shade (NotificationPanel) expansion */ -data class ShadeModel( - /** 0 when collapsed, 1 when fully expanded. */ - @FloatRange(from = 0.0, to = 1.0) val expansionAmount: Float = 0f, - /** Whether the panel should be considered expanded */ - val isExpanded: Boolean = false, - /** Whether the user is actively dragging the panel. */ - val isUserDragging: Boolean = false, -) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java index faffb3e118fc..d23c85a6d796 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/AlertingNotificationManager.java @@ -19,7 +19,6 @@ package com.android.systemui.statusbar; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Handler; -import android.os.SystemClock; import android.util.ArrayMap; import android.util.ArraySet; import android.view.accessibility.AccessibilityEvent; @@ -29,6 +28,7 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.time.SystemClock; import java.util.stream.Stream; @@ -39,21 +39,23 @@ import java.util.stream.Stream; */ public abstract class AlertingNotificationManager { private static final String TAG = "AlertNotifManager"; - protected final Clock mClock = new Clock(); + protected final SystemClock mSystemClock; protected final ArrayMap<String, AlertEntry> mAlertEntries = new ArrayMap<>(); protected final HeadsUpManagerLogger mLogger; - public AlertingNotificationManager(HeadsUpManagerLogger logger, @Main Handler handler) { - mLogger = logger; - mHandler = handler; - } - protected int mMinimumDisplayTime; - protected int mStickyDisplayTime; - protected int mAutoDismissNotificationDecay; + protected int mStickyForSomeTimeAutoDismissTime; + protected int mAutoDismissTime; @VisibleForTesting public Handler mHandler; + public AlertingNotificationManager(HeadsUpManagerLogger logger, @Main Handler handler, + SystemClock systemClock) { + mLogger = logger; + mHandler = handler; + mSystemClock = systemClock; + } + /** * Called when posting a new notification that should alert the user and appear on screen. * Adds the notification to be managed. @@ -251,7 +253,7 @@ public abstract class AlertingNotificationManager { public long getEarliestRemovalTime(String key) { AlertEntry alerting = mAlertEntries.get(key); if (alerting != null) { - return Math.max(0, alerting.mEarliestRemovaltime - mClock.currentTimeMillis()); + return Math.max(0, alerting.mEarliestRemovalTime - mSystemClock.elapsedRealtime()); } return 0; } @@ -259,7 +261,7 @@ public abstract class AlertingNotificationManager { protected class AlertEntry implements Comparable<AlertEntry> { @Nullable public NotificationEntry mEntry; public long mPostTime; - public long mEarliestRemovaltime; + public long mEarliestRemovalTime; @Nullable protected Runnable mRemoveAlertRunnable; @@ -283,8 +285,8 @@ public abstract class AlertingNotificationManager { public void updateEntry(boolean updatePostTime, @Nullable String reason) { mLogger.logUpdateEntry(mEntry, updatePostTime, reason); - final long now = mClock.currentTimeMillis(); - mEarliestRemovaltime = now + mMinimumDisplayTime; + final long now = mSystemClock.elapsedRealtime(); + mEarliestRemovalTime = now + mMinimumDisplayTime; if (updatePostTime) { mPostTime = Math.max(mPostTime, now); @@ -318,7 +320,7 @@ public abstract class AlertingNotificationManager { * @return true if the notification has been on screen long enough */ public boolean wasShownLongEnough() { - return mEarliestRemovaltime < mClock.currentTimeMillis(); + return mEarliestRemovalTime < mSystemClock.elapsedRealtime(); } @Override @@ -351,7 +353,7 @@ public abstract class AlertingNotificationManager { if (mRemoveAlertRunnable != null) { removeAutoRemovalCallbacks("removeAsSoonAsPossible (will be rescheduled)"); - final long timeLeft = mEarliestRemovaltime - mClock.currentTimeMillis(); + final long timeLeft = mEarliestRemovalTime - mSystemClock.elapsedRealtime(); mHandler.postDelayed(mRemoveAlertRunnable, timeLeft); } } @@ -361,22 +363,16 @@ public abstract class AlertingNotificationManager { * @return the post time */ protected long calculatePostTime() { - return mClock.currentTimeMillis(); + return mSystemClock.elapsedRealtime(); } /** * @return When the notification should auto-dismiss itself, based on - * {@link SystemClock#elapsedRealTime()} + * {@link SystemClock#elapsedRealtime()} */ protected long calculateFinishTime() { // Overridden by HeadsUpManager HeadsUpEntry #calculateFinishTime return 0; } } - - protected final static class Clock { - public long currentTimeMillis() { - return SystemClock.elapsedRealtime(); - } - } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt index 22912df71334..85f4c366b370 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -20,6 +20,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.statusbar.data.StatusBarDataLayerModule import com.android.systemui.statusbar.phone.LightBarController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController +import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl import dagger.Binds import dagger.Module import dagger.multibindings.ClassKey @@ -33,7 +34,7 @@ import dagger.multibindings.IntoMap * ([com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule], * [com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule], etc.). */ -@Module(includes = [StatusBarDataLayerModule::class]) +@Module(includes = [StatusBarDataLayerModule::class, SystemBarUtilsProxyImpl.Module::class]) abstract class StatusBarModule { @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt index ecca9731f003..92391e7c76f0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt @@ -22,7 +22,6 @@ import android.widget.FrameLayout import androidx.annotation.ColorInt import androidx.collection.ArrayMap import androidx.lifecycle.lifecycleScope -import com.android.internal.policy.SystemBarUtils import com.android.internal.statusbar.StatusBarIcon import com.android.internal.util.ContrastColorUtil import com.android.systemui.common.ui.ConfigurationState @@ -39,10 +38,8 @@ import com.android.systemui.statusbar.notification.icon.ui.viewmodel.Notificatio import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData import com.android.systemui.statusbar.notification.icon.ui.viewmodel.NotificationIconsViewData.LimitType import com.android.systemui.statusbar.phone.NotificationIconContainer -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.statusbar.policy.onConfigChanged +import com.android.systemui.statusbar.ui.SystemBarUtilsState import com.android.systemui.util.kotlin.mapValuesNotNullTo -import com.android.systemui.util.kotlin.stateFlow import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.stopAnimating import com.android.systemui.util.ui.value @@ -51,7 +48,6 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.launch @@ -59,20 +55,20 @@ import kotlinx.coroutines.launch /** Binds a view-model to a [NotificationIconContainer]. */ object NotificationIconContainerViewBinder { @JvmStatic - fun bind( + fun bindWhileAttached( view: NotificationIconContainer, viewModel: NotificationIconContainerShelfViewModel, configuration: ConfigurationState, - configurationController: ConfigurationController, + systemBarUtilsState: SystemBarUtilsState, failureTracker: StatusBarIconViewBindingFailureTracker, - viewStore: ShelfNotificationIconViewStore, + viewStore: IconViewStore, ): DisposableHandle { return view.repeatWhenAttached { lifecycleScope.launch { viewModel.icons.bindIcons( view, configuration, - configurationController, + systemBarUtilsState, notifyBindingFailures = { failureTracker.shelfFailures = it }, viewStore, ) @@ -81,66 +77,87 @@ object NotificationIconContainerViewBinder { } @JvmStatic - fun bind( + fun bindWhileAttached( view: NotificationIconContainer, viewModel: NotificationIconContainerStatusBarViewModel, configuration: ConfigurationState, - configurationController: ConfigurationController, + systemBarUtilsState: SystemBarUtilsState, failureTracker: StatusBarIconViewBindingFailureTracker, - viewStore: StatusBarNotificationIconViewStore, - ): DisposableHandle { - val contrastColorUtil = ContrastColorUtil.getInstance(view.context) - return view.repeatWhenAttached { - lifecycleScope.run { - launch { - val iconColors: Flow<NotificationIconColors> = - viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) } - viewModel.icons.bindIcons( - view, - configuration, - configurationController, - notifyBindingFailures = { failureTracker.statusBarFailures = it }, - viewStore, - ) { _, sbiv -> - StatusBarIconViewBinder.bindIconColors( - sbiv, - iconColors, - contrastColorUtil, - ) - } - } - launch { viewModel.bindIsolatedIcon(view, viewStore) } - launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) } + viewStore: IconViewStore, + ): DisposableHandle = + view.repeatWhenAttached { + lifecycleScope.launch { + bind(view, viewModel, configuration, systemBarUtilsState, failureTracker, viewStore) + } + } + + suspend fun bind( + view: NotificationIconContainer, + viewModel: NotificationIconContainerStatusBarViewModel, + configuration: ConfigurationState, + systemBarUtilsState: SystemBarUtilsState, + failureTracker: StatusBarIconViewBindingFailureTracker, + viewStore: IconViewStore, + ): Unit = coroutineScope { + launch { + val contrastColorUtil = ContrastColorUtil.getInstance(view.context) + val iconColors: Flow<NotificationIconColors> = + viewModel.iconColors.mapNotNull { it.iconColors(view.viewBounds) } + viewModel.icons.bindIcons( + view, + configuration, + systemBarUtilsState, + notifyBindingFailures = { failureTracker.statusBarFailures = it }, + viewStore, + ) { _, sbiv -> + StatusBarIconViewBinder.bindIconColors( + sbiv, + iconColors, + contrastColorUtil, + ) } } + launch { viewModel.bindIsolatedIcon(view, viewStore) } + launch { viewModel.animationsEnabled.bindAnimationsEnabled(view) } } @JvmStatic - fun bind( + fun bindWhileAttached( view: NotificationIconContainer, viewModel: NotificationIconContainerAlwaysOnDisplayViewModel, configuration: ConfigurationState, - configurationController: ConfigurationController, + systemBarUtilsState: SystemBarUtilsState, failureTracker: StatusBarIconViewBindingFailureTracker, viewStore: IconViewStore, ): DisposableHandle { return view.repeatWhenAttached { lifecycleScope.launch { - view.setUseIncreasedIconScale(true) - launch { - viewModel.icons.bindIcons( - view, - configuration, - configurationController, - notifyBindingFailures = { failureTracker.aodFailures = it }, - viewStore, - ) { _, sbiv -> - viewModel.bindAodStatusBarIconView(sbiv, configuration) - } - } - launch { viewModel.areContainerChangesAnimated.bindAnimationsEnabled(view) } + bind(view, viewModel, configuration, systemBarUtilsState, failureTracker, viewStore) + } + } + } + + suspend fun bind( + view: NotificationIconContainer, + viewModel: NotificationIconContainerAlwaysOnDisplayViewModel, + configuration: ConfigurationState, + systemBarUtilsState: SystemBarUtilsState, + failureTracker: StatusBarIconViewBindingFailureTracker, + viewStore: IconViewStore, + ): Unit = coroutineScope { + view.setUseIncreasedIconScale(true) + launch { + viewModel.icons.bindIcons( + view, + configuration, + systemBarUtilsState, + notifyBindingFailures = { failureTracker.aodFailures = it }, + viewStore, + ) { _, sbiv -> + viewModel.bindAodStatusBarIconView(sbiv, configuration) } } + launch { viewModel.areContainerChangesAnimated.bindAnimationsEnabled(view) } } private suspend fun NotificationIconContainerAlwaysOnDisplayViewModel.bindAodStatusBarIconView( @@ -199,7 +216,7 @@ object NotificationIconContainerViewBinder { private suspend fun Flow<NotificationIconsViewData>.bindIcons( view: NotificationIconContainer, configuration: ConfigurationState, - configurationController: ConfigurationController, + systemBarUtilsState: SystemBarUtilsState, notifyBindingFailures: (Collection<String>) -> Unit, viewStore: IconViewStore, bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> }, @@ -210,12 +227,8 @@ object NotificationIconContainerViewBinder { ) val iconHorizontalPaddingFlow: Flow<Int> = configuration.getDimensionPixelSize(R.dimen.status_bar_icon_horizontal_margin) - val statusBarHeightFlow: StateFlow<Int> = - stateFlow(changedSignals = configurationController.onConfigChanged) { - SystemBarUtils.getStatusBarHeight(view.context) - } val layoutParams: Flow<FrameLayout.LayoutParams> = - combine(iconSizeFlow, iconHorizontalPaddingFlow, statusBarHeightFlow) { + combine(iconSizeFlow, iconHorizontalPaddingFlow, systemBarUtilsState.statusBarHeight) { iconSize, iconHPadding, statusBarHeight, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt index 5cdead407891..699e1406bc18 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shelf/ui/viewbinder/NotificationShelfViewBinder.kt @@ -29,7 +29,7 @@ import com.android.systemui.statusbar.notification.row.ui.viewbinder.Activatable import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor import com.android.systemui.statusbar.notification.shelf.ui.viewmodel.NotificationShelfViewModel import com.android.systemui.statusbar.phone.NotificationIconAreaController -import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.ui.SystemBarUtilsState import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.launch @@ -39,7 +39,7 @@ object NotificationShelfViewBinder { shelf: NotificationShelf, viewModel: NotificationShelfViewModel, configuration: ConfigurationState, - configurationController: ConfigurationController, + systemBarUtilsState: SystemBarUtilsState, falsingManager: FalsingManager, iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, notificationIconAreaController: NotificationIconAreaController, @@ -48,11 +48,11 @@ object NotificationShelfViewBinder { ActivatableNotificationViewBinder.bind(viewModel, shelf, falsingManager) shelf.apply { if (NotificationIconContainerRefactor.isEnabled) { - NotificationIconContainerViewBinder.bind( + NotificationIconContainerViewBinder.bindWhileAttached( shelfIcons, viewModel.icons, configuration, - configurationController, + systemBarUtilsState, iconViewBindingFailureTracker, shelfIconViewStore, ) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt index abf09ae9844c..e78a694735e0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/data/repository/NotificationStackAppearanceRepository.kt @@ -26,5 +26,8 @@ import kotlinx.coroutines.flow.MutableStateFlow @SysUISingleton class NotificationStackAppearanceRepository @Inject constructor() { /** The bounds of the notification stack in the current scene. */ - val stackBounds = MutableStateFlow(NotificationContainerBounds(0f, 0f)) + val stackBounds = MutableStateFlow(NotificationContainerBounds()) + + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp = MutableStateFlow(32f) } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt index 32e4e89c42c5..61a4dfcbd201 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/NotificationStackAppearanceInteractor.kt @@ -39,4 +39,7 @@ constructor( check(bounds.top <= bounds.bottom) { "Invalid bounds: $bounds" } repository.stackBounds.value = bounds } + + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp: StateFlow<Float> = repository.cornerRadiusDp.asStateFlow() } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt index a4e1a9c502f3..9373d497ffa7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinder.kt @@ -40,7 +40,7 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.notification.stack.ui.viewbinder.HideNotificationsBinder.bindHideList import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationListViewModel import com.android.systemui.statusbar.phone.NotificationIconAreaController -import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.ui.SystemBarUtilsState import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.combine @@ -53,12 +53,12 @@ constructor( private val viewModel: NotificationListViewModel, @Background private val backgroundDispatcher: CoroutineDispatcher, private val configuration: ConfigurationState, - private val configurationController: ConfigurationController, private val falsingManager: FalsingManager, private val iconAreaController: NotificationIconAreaController, private val iconViewBindingFailureTracker: StatusBarIconViewBindingFailureTracker, private val metricsLogger: MetricsLogger, private val shelfIconViewStore: ShelfNotificationIconViewStore, + private val systemBarUtilsState: SystemBarUtilsState, ) { fun bind( @@ -91,7 +91,7 @@ constructor( shelf, viewModel.shelf, configuration, - configurationController, + systemBarUtilsState, falsingManager, iconViewBindingFailureTracker, iconAreaController, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt index fa7a8fdb7495..a9b542dcce2d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationStackAppearanceViewBinder.kt @@ -16,14 +16,16 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder +import android.content.Context +import android.util.TypedValue import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.statusbar.notification.stack.AmbientState import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayoutController import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationStackAppearanceViewModel +import kotlin.math.roundToInt import kotlinx.coroutines.launch /** Binds the shared notification container to its view-model. */ @@ -31,9 +33,9 @@ object NotificationStackAppearanceViewBinder { @JvmStatic fun bind( + context: Context, view: SharedNotificationContainer, viewModel: NotificationStackAppearanceViewModel, - sceneContainerFlags: SceneContainerFlags, ambientState: AmbientState, controller: NotificationStackScrollLayoutController, ) { @@ -45,6 +47,14 @@ object NotificationStackAppearanceViewBinder { bounds.top, controller.isAddOrRemoveAnimationPending ) + controller.setRoundedClippingBounds( + it.left, + it.top, + it.right, + it.bottom, + viewModel.cornerRadiusDp.value.dpToPx(context), + viewModel.cornerRadiusDp.value.dpToPx(context), + ) } } launch { @@ -56,4 +66,13 @@ object NotificationStackAppearanceViewBinder { } } } + + private fun Float.dpToPx(context: Context): Int { + return TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + this, + context.resources.displayMetrics + ) + .roundToInt() + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt index f4c0e92b0e87..834d3ffe63c9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationStackAppearanceViewModel.kt @@ -23,6 +23,7 @@ import com.android.systemui.shade.domain.interactor.ShadeInteractor import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import javax.inject.Inject import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.StateFlow /** ViewModel which represents the state of the NSSL/Controller in the world of flexiglass */ @SysUISingleton @@ -37,4 +38,7 @@ constructor( /** The bounds of the notification stack in the current scene. */ val stackBounds: Flow<NotificationContainerBounds> = stackAppearanceInteractor.stackBounds + + /** The corner radius of the notification stack, in dp. */ + val cornerRadiusDp: StateFlow<Float> = stackAppearanceInteractor.cornerRadiusDp } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt index c6fd98ea2223..9f22118e3332 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt @@ -24,6 +24,7 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor import com.android.systemui.statusbar.notification.stack.shared.flexiNotifsEnabled import javax.inject.Inject +import kotlinx.coroutines.flow.StateFlow /** * ViewModel used by the Notification placeholders inside the scene container to update the @@ -55,9 +56,14 @@ constructor( * pixels. */ fun onBoundsChanged( + left: Float, top: Float, + right: Float, bottom: Float, ) { - interactor.setStackBounds(NotificationContainerBounds(top, bottom)) + interactor.setStackBounds(NotificationContainerBounds(left, top, right, bottom)) } + + /** The corner radius of the placeholder, in dp. */ + val cornerRadiusDp: StateFlow<Float> = interactor.cornerRadiusDp } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt index 5b854e6c8ee7..9594bc3bfd86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt @@ -177,8 +177,8 @@ constructor( } .stateIn( scope = applicationScope, - started = SharingStarted.WhileSubscribed(), - initialValue = NotificationContainerBounds(0f, 0f), + started = SharingStarted.Lazily, + initialValue = NotificationContainerBounds(), ) val alpha: Flow<Float> = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java index 3a95e6d053e8..644c8962b93d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java @@ -49,6 +49,8 @@ import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener; import com.android.systemui.statusbar.policy.OnHeadsUpPhoneListenerChange; import com.android.systemui.util.kotlin.JavaAdapter; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; import java.util.ArrayList; @@ -115,11 +117,14 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp VisualStabilityProvider visualStabilityProvider, ConfigurationController configurationController, @Main Handler handler, + GlobalSettings globalSettings, + SystemClock systemClock, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, JavaAdapter javaAdapter, ShadeInteractor shadeInteractor) { - super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger); + super(context, logger, handler, globalSettings, systemClock, accessibilityManagerWrapper, + uiEventLogger); Resources resources = mContext.getResources(); mExtensionTime = resources.getInteger(R.integer.ambient_notification_extension_time); statusBarStateController.addCallback(mStatusBarStateListener); @@ -206,7 +211,7 @@ public class HeadsUpManagerPhone extends BaseHeadsUpManager implements OnHeadsUp @Override public boolean shouldSwallowClick(@NonNull String key) { BaseHeadsUpManager.HeadsUpEntry entry = getHeadsUpEntry(key); - return entry != null && mClock.currentTimeMillis() < entry.mPostTime; + return entry != null && mSystemClock.elapsedRealtime() < entry.mPostTime; } public void onExpandingFinished() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java index 49880d4475da..cd999349d055 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java @@ -74,8 +74,8 @@ import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallListener; import com.android.systemui.statusbar.pipeline.shared.ui.binder.CollapsedStatusBarViewBinder; import com.android.systemui.statusbar.pipeline.shared.ui.binder.StatusBarVisibilityChangeListener; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.CollapsedStatusBarViewModel; -import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.ui.SystemBarUtilsState; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.util.CarrierConfigTracker; @@ -83,8 +83,6 @@ import com.android.systemui.util.CarrierConfigTracker.CarrierConfigChangedListen import com.android.systemui.util.CarrierConfigTracker.DefaultDataSubscriptionChangedListener; import com.android.systemui.util.settings.SecureSettings; -import kotlin.Unit; - import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; @@ -95,6 +93,8 @@ import java.util.concurrent.Executor; import javax.inject.Inject; +import kotlin.Unit; + /** * Contains the collapsed status bar and handles hiding/showing based on disable flags * and keyguard state. Also manages lifecycle to make sure the views it contains are being @@ -153,7 +153,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private final NotificationIconContainerStatusBarViewModel mStatusBarIconsViewModel; private final ConfigurationState mConfigurationState; - private final ConfigurationController mConfigurationController; + private final SystemBarUtilsState mSystemBarUtilsState; private final StatusBarNotificationIconViewStore mStatusBarIconViewStore; private final DemoModeController mDemoModeController; @@ -246,7 +246,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue KeyguardUpdateMonitor keyguardUpdateMonitor, NotificationIconContainerStatusBarViewModel statusBarIconsViewModel, ConfigurationState configurationState, - ConfigurationController configurationController, + SystemBarUtilsState systemBarUtilsState, StatusBarNotificationIconViewStore statusBarIconViewStore, DemoModeController demoModeController) { mStatusBarFragmentComponentFactory = statusBarFragmentComponentFactory; @@ -275,7 +275,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mKeyguardUpdateMonitor = keyguardUpdateMonitor; mStatusBarIconsViewModel = statusBarIconsViewModel; mConfigurationState = configurationState; - mConfigurationController = configurationController; + mSystemBarUtilsState = systemBarUtilsState; mStatusBarIconViewStore = statusBarIconViewStore; mDemoModeController = demoModeController; } @@ -466,11 +466,11 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue .inflate(R.layout.notification_icon_area, notificationIconArea, true); NotificationIconContainer notificationIcons = notificationIconArea.requireViewById(R.id.notificationIcons); - NotificationIconContainerViewBinder.bind( + NotificationIconContainerViewBinder.bindWhileAttached( notificationIcons, mStatusBarIconsViewModel, mConfigurationState, - mConfigurationController, + mSystemBarUtilsState, mIconViewBindingFailureTracker, mStatusBarIconViewStore); } else { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt index 99b123fbd702..ae58398753e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModel.kt @@ -161,13 +161,13 @@ constructor( if (it == null) { notConnectedFlow } else { - val secondary = it.contentDescription.toString() + val secondary = it.contentDescription flowOf( InternetTileModel.Active( - secondaryTitle = secondary, + secondaryLabel = secondary?.toText(), iconId = it.res, stateDescription = null, - contentDescription = ContentDescription.Loaded(secondary), + contentDescription = secondary, ) ) } @@ -241,5 +241,11 @@ constructor( string.substring(1, length - 1) } else string } + + private fun ContentDescription.toText(): Text = + when (this) { + is ContentDescription.Loaded -> Text.Loaded(this.description) + is ContentDescription.Resource -> Text.Resource(this.res) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index cec76f3140cd..8054b04529c8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -25,8 +25,6 @@ import android.content.Context; import android.content.res.Resources; import android.database.ContentObserver; import android.os.Handler; -import android.os.SystemClock; -import android.provider.Settings; import android.util.ArrayMap; import android.view.accessibility.AccessibilityManager; @@ -40,6 +38,8 @@ import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag; import com.android.systemui.util.ListenerSet; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.time.SystemClock; import java.io.PrintWriter; @@ -85,36 +85,40 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp public BaseHeadsUpManager(@NonNull final Context context, HeadsUpManagerLogger logger, @Main Handler handler, + GlobalSettings globalSettings, + SystemClock systemClock, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger) { - super(logger, handler); + super(logger, handler, systemClock); mContext = context; mAccessibilityMgr = accessibilityManagerWrapper; mUiEventLogger = uiEventLogger; Resources resources = context.getResources(); mMinimumDisplayTime = resources.getInteger(R.integer.heads_up_notification_minimum_time); - mStickyDisplayTime = resources.getInteger(R.integer.sticky_heads_up_notification_time); - mAutoDismissNotificationDecay = resources.getInteger(R.integer.heads_up_notification_decay); + mStickyForSomeTimeAutoDismissTime = resources.getInteger( + R.integer.sticky_heads_up_notification_time); + mAutoDismissTime = resources.getInteger(R.integer.heads_up_notification_decay); mTouchAcceptanceDelay = resources.getInteger(R.integer.touch_acceptance_delay); mSnoozedPackages = new ArrayMap<>(); int defaultSnoozeLengthMs = resources.getInteger(R.integer.heads_up_default_snooze_length_ms); - mSnoozeLengthMs = Settings.Global.getInt(context.getContentResolver(), - SETTING_HEADS_UP_SNOOZE_LENGTH_MS, defaultSnoozeLengthMs); + mSnoozeLengthMs = globalSettings.getInt(SETTING_HEADS_UP_SNOOZE_LENGTH_MS, + defaultSnoozeLengthMs); ContentObserver settingsObserver = new ContentObserver(handler) { @Override public void onChange(boolean selfChange) { - final int packageSnoozeLengthMs = Settings.Global.getInt( - context.getContentResolver(), SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1); + final int packageSnoozeLengthMs = globalSettings.getInt( + SETTING_HEADS_UP_SNOOZE_LENGTH_MS, -1); if (packageSnoozeLengthMs > -1 && packageSnoozeLengthMs != mSnoozeLengthMs) { mSnoozeLengthMs = packageSnoozeLengthMs; mLogger.logSnoozeLengthChange(packageSnoozeLengthMs); } } }; - context.getContentResolver().registerContentObserver( - Settings.Global.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), false, + globalSettings.registerContentObserver( + globalSettings.getUriFor(SETTING_HEADS_UP_SNOOZE_LENGTH_MS), + /* notifyForDescendants = */ false, settingsObserver); } @@ -231,7 +235,7 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp final String key = snoozeKey(packageName, mUser); Long snoozedUntil = mSnoozedPackages.get(key); if (snoozedUntil != null) { - if (snoozedUntil > mClock.currentTimeMillis()) { + if (snoozedUntil > mSystemClock.elapsedRealtime()) { mLogger.logIsSnoozedReturned(key); return true; } @@ -250,7 +254,7 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp String packageName = entry.mEntry.getSbn().getPackageName(); String snoozeKey = snoozeKey(packageName, mUser); mLogger.logPackageSnoozed(snoozeKey); - mSnoozedPackages.put(snoozeKey, mClock.currentTimeMillis() + mSnoozeLengthMs); + mSnoozedPackages.put(snoozeKey, mSystemClock.elapsedRealtime() + mSnoozeLengthMs); } } @@ -308,7 +312,7 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp protected void dumpInternal(@NonNull PrintWriter pw, @NonNull String[] args) { pw.print(" mTouchAcceptanceDelay="); pw.println(mTouchAcceptanceDelay); pw.print(" mSnoozeLengthMs="); pw.println(mSnoozeLengthMs); - pw.print(" now="); pw.println(mClock.currentTimeMillis()); + pw.print(" now="); pw.println(mSystemClock.elapsedRealtime()); pw.print(" mUser="); pw.println(mUser); for (AlertEntry entry: mAlertEntries.values()) { pw.print(" HeadsUpEntry="); pw.println(entry.mEntry); @@ -519,12 +523,12 @@ public abstract class BaseHeadsUpManager extends AlertingNotificationManager imp /** * @return When the notification should auto-dismiss itself, based on - * {@link SystemClock#elapsedRealTime()} + * {@link SystemClock#elapsedRealtime()} */ @Override protected long calculateFinishTime() { final long duration = getRecommendedHeadsUpTimeoutMs( - isStickyForSomeTime() ? mStickyDisplayTime : mAutoDismissNotificationDecay); + isStickyForSomeTime() ? mStickyForSomeTimeAutoDismissTime : mAutoDismissTime); return mPostTime + duration; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxy.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxy.kt new file mode 100644 index 000000000000..2b3fb70301e5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxy.kt @@ -0,0 +1,45 @@ +/* + * 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.statusbar.ui + +import android.content.Context +import com.android.internal.policy.SystemBarUtils +import com.android.systemui.dagger.qualifiers.Application +import dagger.Binds +import javax.inject.Inject + +/** + * Proxy interface to [SystemBarUtils], allowing injection of different logic for testing. + * + * Developers should almost always prefer [SystemBarUtilsState] instead. + */ +interface SystemBarUtilsProxy { + fun getStatusBarHeight(): Int +} + +class SystemBarUtilsProxyImpl +@Inject +constructor( + @Application private val context: Context, +) : SystemBarUtilsProxy { + override fun getStatusBarHeight(): Int = SystemBarUtils.getStatusBarHeight(context) + + @dagger.Module + interface Module { + @Binds fun bindImpl(impl: SystemBarUtilsProxyImpl): SystemBarUtilsProxy + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt new file mode 100644 index 000000000000..ce811e205436 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/ui/SystemBarUtilsState.kt @@ -0,0 +1,42 @@ +/* + * 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.statusbar.ui + +import com.android.internal.policy.SystemBarUtils +import com.android.systemui.statusbar.policy.ConfigurationController +import com.android.systemui.statusbar.policy.onConfigChanged +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onStart + +/** + * Tracks state from [SystemBarUtils]. Using this is both more efficient and more testable than + * using [SystemBarUtils] directly. + */ +class SystemBarUtilsState +@Inject +constructor( + configurationController: ConfigurationController, + proxy: SystemBarUtilsProxy, +) { + /** @see SystemBarUtils.getStatusBarHeight */ + val statusBarHeight: Flow<Int> = + configurationController.onConfigChanged + .onStart<Any> { emit(Unit) } + .map { proxy.getStatusBarHeight() } +} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java index 24917b37c8b1..88f63ad9c8cb 100644 --- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java +++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardClockSwitchControllerBaseTest.java @@ -63,7 +63,7 @@ import com.android.systemui.statusbar.phone.DozeParameters; import com.android.systemui.statusbar.phone.NotificationIconAreaController; import com.android.systemui.statusbar.phone.NotificationIconContainer; import com.android.systemui.statusbar.phone.ScreenOffAnimationController; -import com.android.systemui.statusbar.policy.ConfigurationController; +import com.android.systemui.statusbar.ui.SystemBarUtilsState; import com.android.systemui.util.concurrency.FakeExecutor; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; @@ -185,7 +185,7 @@ public class KeyguardClockSwitchControllerBaseTest extends SysuiTestCase { mKeyguardSliceViewController, mNotificationIconAreaController, mSmartspaceController, - mock(ConfigurationController.class), + mock(SystemBarUtilsState.class), mock(ScreenOffAnimationController.class), mock(StatusBarIconViewBindingFailureTracker.class), mKeyguardUnlockAnimationController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt index 575d8bf8248c..fa176728619b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DefaultUdfpsTouchOverlayViewModelTest.kt @@ -30,7 +30,6 @@ import com.android.systemui.keyguard.shared.model.StatusBarState import com.android.systemui.runCurrent import com.android.systemui.runTest import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.shade.domain.model.ShadeModel import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.user.domain.UserDomainLayerModule import com.android.systemui.util.mockito.mock @@ -83,23 +82,13 @@ class DefaultUdfpsTouchOverlayViewModelTest : SysuiTestCase() { private fun TestComponent.shadeExpanded(expanded: Boolean) { if (expanded) { - shadeRepository.setShadeModel( - ShadeModel( - expansionAmount = 1f, - isExpanded = true, - isUserDragging = false, - ) - ) + shadeRepository.setLegacyShadeExpansion(1f) + shadeRepository.setLegacyShadeTracking(false) shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(true) } else { keyguardRepository.setStatusBarState(StatusBarState.SHADE) - shadeRepository.setShadeModel( - ShadeModel( - expansionAmount = 0f, - isExpanded = false, - isUserDragging = false, - ) - ) + shadeRepository.setLegacyShadeExpansion(0f) + shadeRepository.setLegacyShadeTracking(false) shadeRepository.setLegacyExpandedOrAwaitingInputTransfer(false) } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt index f5f1622ac69b..863d9ebb41e4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/DeviceEntryUdfpsTouchOverlayViewModelTest.kt @@ -20,7 +20,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import com.android.systemui.keyguard.ui.viewmodel.deviceEntryIconViewModelTransitionsMock import com.android.systemui.kosmos.testScope @@ -49,7 +49,7 @@ import org.mockito.MockitoAnnotations class DeviceEntryUdfpsTouchOverlayViewModelTest : SysuiTestCase() { val kosmos = testKosmos().apply { - featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, true) } } val testScope = kosmos.testScope diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt index 40c9432d543d..076d72513633 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/CustomizationProviderTest.kt @@ -53,9 +53,11 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -101,6 +103,8 @@ class CustomizationProviderTest : SysuiTestCase() { private lateinit var underTest: CustomizationProvider private lateinit var testScope: TestScope + private val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -185,6 +189,7 @@ class CustomizationProviderTest : SysuiTestCase() { }, ) .keyguardInteractor, + shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt index 66c8a229f0a1..b4ae7e3a7ca9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt @@ -46,7 +46,9 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -242,6 +244,8 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository private lateinit var userTracker: UserTracker + private val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -311,6 +315,7 @@ class KeyguardQuickAffordanceInteractorParameterizedTest : SysuiTestCase() { featureFlags = featureFlags, ) .keyguardInteractor, + shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt index bf6d5c4535ec..976dc5f01ff9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionScenariosTest.kt @@ -43,7 +43,6 @@ import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.se import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest import com.android.systemui.power.domain.interactor.PowerInteractorFactory import com.android.systemui.shade.data.repository.FakeShadeRepository -import com.android.systemui.shade.domain.model.ShadeModel import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -1329,12 +1328,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // GIVEN the keyguard is showing locked keyguardRepository.setStatusBarState(StatusBarState.KEYGUARD) runCurrent() - shadeRepository.setShadeModel( - ShadeModel( - expansionAmount = .9f, - isUserDragging = true, - ) - ) + shadeRepository.setLegacyShadeTracking(true) + shadeRepository.setLegacyShadeExpansion(.9f) runCurrent() // THEN a transition from LOCKSCREEN => PRIMARY_BOUNCER should occur @@ -1350,12 +1345,8 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { // WHEN the user stops dragging and shade is back to expanded clearInvocations(transitionRepository) runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.PRIMARY_BOUNCER) - shadeRepository.setShadeModel( - ShadeModel( - expansionAmount = 1f, - isUserDragging = false, - ) - ) + shadeRepository.setLegacyShadeTracking(false) + shadeRepository.setLegacyShadeExpansion(1f) runCurrent() // THEN a transition from PRIMARY_BOUNCER => LOCKSCREEN should occur diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt index c9b14a41edca..daafe12514ba 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/BouncerToGoneFlowsTest.kt @@ -22,7 +22,7 @@ import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.Flags -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER @@ -55,7 +55,7 @@ class BouncerToGoneFlowsTest : SysuiTestCase() { private val kosmos = testKosmos().apply { - featureFlagsClassic.apply { + fakeFeatureFlagsClassic.apply { set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false) set(Flags.FULL_SCREEN_USER_SWITCHER, false) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt index 1584be0ab565..af38523c2fd3 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt @@ -54,9 +54,11 @@ import com.android.systemui.plugins.ActivityStarter import com.android.systemui.res.R import com.android.systemui.settings.UserFileManager import com.android.systemui.settings.UserTracker +import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper import com.android.systemui.statusbar.policy.KeyguardStateController +import com.android.systemui.testKosmos import com.android.systemui.util.FakeSharedPreferences import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock @@ -108,6 +110,8 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { private lateinit var dockManager: DockManagerFake private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository + private val kosmos = testKosmos() + @Before fun setUp() { MockitoAnnotations.initMocks(this) @@ -221,6 +225,7 @@ class KeyguardBottomAreaViewModelTest : SysuiTestCase() { quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, + shadeInteractor = kosmos.shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt index 0c30d10ea563..b6a661be8c74 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt @@ -264,12 +264,14 @@ class KeyguardQuickAffordancesCombinedViewModelTest : SysuiTestCase() { whenever(lockscreenToPrimaryBouncerTransitionViewModel.shortcutsAlpha) .thenReturn(emptyFlow()) whenever(shadeInteractor.qsExpansion).thenReturn(intendedShadeAlphaMutableStateFlow) + whenever(shadeInteractor.anyExpansion).thenReturn(MutableStateFlow(0f)) underTest = KeyguardQuickAffordancesCombinedViewModel( quickAffordanceInteractor = KeyguardQuickAffordanceInteractor( keyguardInteractor = keyguardInteractor, + shadeInteractor = shadeInteractor, lockPatternUtils = lockPatternUtils, keyguardStateController = keyguardStateController, userTracker = userTracker, diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt index 58624d356e60..687800714e05 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt @@ -144,19 +144,43 @@ class KeyguardRootViewModelTest : SysuiTestCase() { @Test fun alpha() = testScope.runTest { - val value = collectLastValue(underTest.alpha) - assertThat(value()).isEqualTo(0f) + val alpha by collectLastValue(underTest.alpha) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.OFF, + to = KeyguardState.LOCKSCREEN, + testScope = testScope, + ) repository.setKeyguardAlpha(0.1f) - assertThat(value()).isEqualTo(0.1f) + assertThat(alpha).isEqualTo(0.1f) repository.setKeyguardAlpha(0.5f) - assertThat(value()).isEqualTo(0.5f) + assertThat(alpha).isEqualTo(0.5f) repository.setKeyguardAlpha(0.2f) - assertThat(value()).isEqualTo(0.2f) + assertThat(alpha).isEqualTo(0.2f) repository.setKeyguardAlpha(0f) - assertThat(value()).isEqualTo(0f) + assertThat(alpha).isEqualTo(0f) occludedToLockscreenAlpha.value = 0.8f - assertThat(value()).isEqualTo(0.8f) + assertThat(alpha).isEqualTo(0.8f) + } + + @Test + fun alphaWhenGoneEqualsZero() = + testScope.runTest { + val alpha by collectLastValue(underTest.alpha) + + keyguardTransitionRepository.sendTransitionSteps( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.GONE, + testScope = testScope, + ) + + repository.setKeyguardAlpha(0.1f) + assertThat(alpha).isEqualTo(0f) + repository.setKeyguardAlpha(0.5f) + assertThat(alpha).isEqualTo(0f) + repository.setKeyguardAlpha(1f) + assertThat(alpha).isEqualTo(0f) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt index c15a2c6a3df7..e139466c8096 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToAodTransitionViewModelTest.kt @@ -23,7 +23,7 @@ import com.android.systemui.biometrics.data.repository.fingerprintPropertyReposi import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues import com.android.systemui.flags.Flags.FULL_SCREEN_USER_SWITCHER -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -47,7 +47,9 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class LockscreenToAodTransitionViewModelTest : SysuiTestCase() { private val kosmos = - testKosmos().apply { featureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } } + testKosmos().apply { + fakeFeatureFlagsClassic.apply { set(FULL_SCREEN_USER_SWITCHER, false) } + } private val testScope = kosmos.testScope private val repository = kosmos.fakeKeyguardTransitionRepository private val shadeRepository = kosmos.shadeRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt index b31968c79647..7a564aca00bb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToPrimaryBouncerTransitionViewModelTest.kt @@ -21,7 +21,7 @@ import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState @@ -45,7 +45,7 @@ import org.junit.runner.RunWith class LockscreenToPrimaryBouncerTransitionViewModelTest : SysuiTestCase() { private val kosmos = testKosmos().apply { - featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } private val testScope = kosmos.testScope private val repository = kosmos.fakeKeyguardTransitionRepository diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt new file mode 100644 index 000000000000..d8199c527cac --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt @@ -0,0 +1,122 @@ +/* + * 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.tiles + +import android.os.Handler +import android.service.quicksettings.Tile +import android.testing.AndroidTestingRunner +import android.testing.TestableLooper +import androidx.test.filters.SmallTest +import com.android.internal.logging.MetricsLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.classifier.FalsingManagerFake +import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.plugins.statusbar.StatusBarStateController +import com.android.systemui.qs.QSHost +import com.android.systemui.qs.QsEventLogger +import com.android.systemui.qs.logging.QSLogger +import com.android.systemui.res.R +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mock +import org.mockito.MockitoAnnotations + +/** + * This class tests the functionality of the RecordIssueTile. The initial state of the tile is + * always be inactive at the start of these tests. + */ +@RunWith(AndroidTestingRunner::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +class RecordIssueTileTest : SysuiTestCase() { + + @Mock private lateinit var host: QSHost + @Mock private lateinit var qsEventLogger: QsEventLogger + @Mock private lateinit var metricsLogger: MetricsLogger + @Mock private lateinit var statusBarStateController: StatusBarStateController + @Mock private lateinit var activityStarter: ActivityStarter + @Mock private lateinit var qsLogger: QSLogger + + private lateinit var tile: RecordIssueTile + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + whenever(host.context).thenReturn(mContext) + + val testableLooper = TestableLooper.get(this) + tile = + RecordIssueTile( + host, + qsEventLogger, + testableLooper.looper, + Handler(testableLooper.looper), + FalsingManagerFake(), + metricsLogger, + statusBarStateController, + activityStarter, + qsLogger + ) + } + + @Test + fun qsTileUi_shouldLookCorrect_whenInactive() { + tile.isRecording = false + + val testState = tile.newTileState() + tile.handleUpdateState(testState, null) + + assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE) + assertThat(testState.secondaryLabel.toString()) + .isEqualTo(mContext.getString(R.string.qs_record_issue_start)) + } + + @Test + fun qsTileUi_shouldLookCorrect_whenRecording() { + tile.isRecording = true + + val testState = tile.newTileState() + tile.handleUpdateState(testState, null) + + assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE) + assertThat(testState.secondaryLabel.toString()) + .isEqualTo(mContext.getString(R.string.qs_record_issue_stop)) + } + + @Test + fun inActiveQsTile_switchesToActive_whenClicked() { + tile.isRecording = false + + val testState = tile.newTileState() + tile.handleUpdateState(testState, null) + + assertThat(testState.state).isEqualTo(Tile.STATE_INACTIVE) + } + + @Test + fun activeQsTile_switchesToInActive_whenClicked() { + tile.isRecording = true + + val testState = tile.newTileState() + tile.handleUpdateState(testState, null) + + assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt index f8aa359b569d..750693c483a2 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeRepositoryImplTest.kt @@ -19,33 +19,20 @@ package com.android.systemui.shade.data.repository import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.shade.ShadeExpansionChangeEvent -import com.android.systemui.shade.ShadeExpansionStateManager -import com.android.systemui.shade.domain.model.ShadeModel -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.flow.launchIn -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.test.StandardTestDispatcher import kotlinx.coroutines.test.TestScope -import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest 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.Mockito.`when` -import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class ShadeRepositoryImplTest : SysuiTestCase() { - @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager private val testDispatcher = StandardTestDispatcher() private val testScope = TestScope(testDispatcher) @@ -53,57 +40,10 @@ class ShadeRepositoryImplTest : SysuiTestCase() { @Before fun setUp() { - MockitoAnnotations.initMocks(this) - - underTest = ShadeRepositoryImpl(shadeExpansionStateManager) - `when`(shadeExpansionStateManager.addExpansionListener(any())) - .thenReturn(ShadeExpansionChangeEvent(0f, false, false, 0f)) + underTest = ShadeRepositoryImpl() } @Test - fun shadeExpansionChangeEvent() = - testScope.runTest { - var latest: ShadeModel? = null - val job = underTest.shadeModel.onEach { latest = it }.launchIn(this) - runCurrent() - assertThat(latest?.expansionAmount).isEqualTo(0f) - assertThat(latest?.isExpanded).isEqualTo(false) - assertThat(latest?.isUserDragging).isEqualTo(false) - - val captor = withArgCaptor { - verify(shadeExpansionStateManager).addExpansionListener(capture()) - } - - captor.onPanelExpansionChanged( - ShadeExpansionChangeEvent( - fraction = 1f, - expanded = true, - tracking = false, - dragDownPxAmount = 0f, - ) - ) - runCurrent() - assertThat(latest?.expansionAmount).isEqualTo(1f) - assertThat(latest?.isExpanded).isEqualTo(true) - assertThat(latest?.isUserDragging).isEqualTo(false) - - captor.onPanelExpansionChanged( - ShadeExpansionChangeEvent( - fraction = .67f, - expanded = false, - tracking = true, - dragDownPxAmount = 0f, - ) - ) - runCurrent() - assertThat(latest?.expansionAmount).isEqualTo(.67f) - assertThat(latest?.isExpanded).isEqualTo(false) - assertThat(latest?.isUserDragging).isEqualTo(true) - - job.cancel() - } - - @Test fun updateQsExpansion() = testScope.runTest { assertThat(underTest.qsExpansion.value).isEqualTo(0f) diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java index b98dc0016066..a3cff87e63b8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/AlertingNotificationManagerTest.java @@ -39,12 +39,15 @@ import android.testing.TestableLooper; import androidx.test.filters.SmallTest; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; +import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; +import com.android.systemui.util.settings.FakeGlobalSettings; +import com.android.systemui.util.time.SystemClock; +import com.android.systemui.util.time.SystemClockImpl; import org.junit.After; import org.junit.Before; @@ -74,18 +77,26 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { protected final Runnable mTestTimeoutRunnable = () -> mTimedOut = true; protected Handler mTestHandler; + protected final FakeGlobalSettings mGlobalSettings = new FakeGlobalSettings(); + protected final SystemClock mSystemClock = new SystemClockImpl(); protected boolean mTimedOut = false; @Mock protected ExpandableNotificationRow mRow; + static { + assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME); + assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME); + assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME); + } + private static class TestableAlertingNotificationManager extends AlertingNotificationManager { private AlertEntry mLastCreatedEntry; - private TestableAlertingNotificationManager(Handler handler) { - super(new HeadsUpManagerLogger(logcatLogBuffer()), handler); + private TestableAlertingNotificationManager(Handler handler, SystemClock systemClock) { + super(new HeadsUpManagerLogger(logcatLogBuffer()), handler, systemClock); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; - mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; - mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME; + mAutoDismissTime = TEST_AUTO_DISMISS_TIME; + mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME; } @Override @@ -107,7 +118,7 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { } protected AlertingNotificationManager createAlertingNotificationManager() { - return new TestableAlertingNotificationManager(mTestHandler); + return new TestableAlertingNotificationManager(mTestHandler, mSystemClock); } protected StatusBarNotification createSbn(int id, Notification n) { @@ -167,10 +178,6 @@ public class AlertingNotificationManagerTest extends SysuiTestCase { @Before public void setUp() { mTestHandler = Handler.createAsync(Looper.myLooper()); - - assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME); - assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME); - assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_TIMEOUT_TIME); } @After diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt index 2209e5e64a6d..f0205b3f5974 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt @@ -26,7 +26,7 @@ import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.Flags -import com.android.systemui.flags.featureFlagsClassic +import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -55,7 +55,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val kosmos = testKosmos().apply { - featureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } + fakeFeatureFlagsClassic.apply { set(Flags.FULL_SCREEN_USER_SWITCHER, false) } } val testScope = kosmos.testScope val configurationRepository = kosmos.fakeConfigurationRepository @@ -413,7 +413,7 @@ class SharedNotificationContainerViewModelTest : SysuiTestCase() { val top = 123f val bottom = 456f keyguardRootViewModel.onNotificationContainerBoundsChanged(top, bottom) - assertThat(bounds).isEqualTo(NotificationContainerBounds(top, bottom)) + assertThat(bounds).isEqualTo(NotificationContainerBounds(top = top, bottom = bottom)) } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java index 48b95d407246..37ee32204f32 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhoneTest.java @@ -46,6 +46,8 @@ import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.HeadsUpManagerLogger; import com.android.systemui.util.kotlin.JavaAdapter; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.time.SystemClock; import org.junit.After; import org.junit.Before; @@ -87,6 +89,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { KeyguardBypassController keyguardBypassController, ConfigurationController configurationController, Handler handler, + GlobalSettings globalSettings, + SystemClock systemClock, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger, JavaAdapter javaAdapter, @@ -101,13 +105,15 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { visualStabilityProvider, configurationController, handler, + globalSettings, + systemClock, accessibilityManagerWrapper, uiEventLogger, javaAdapter, shadeInteractor ); mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; - mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; + mAutoDismissTime = TEST_AUTO_DISMISS_TIME; } } @@ -121,6 +127,8 @@ public class HeadsUpManagerPhoneTest extends AlertingNotificationManagerTest { mBypassController, mConfigurationController, mTestHandler, + mGlobalSettings, + mSystemClock, mAccessibilityManagerWrapper, mUiEventLogger, mJavaAdapter, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java index 17c29382b39a..1cc611c2df87 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java @@ -69,8 +69,8 @@ import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentCom import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewBinder; import com.android.systemui.statusbar.pipeline.shared.ui.viewmodel.FakeCollapsedStatusBarViewModel; -import com.android.systemui.statusbar.policy.ConfigurationController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.statusbar.ui.SystemBarUtilsState; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.statusbar.window.StatusBarWindowStateListener; import com.android.systemui.util.CarrierConfigTracker; @@ -717,7 +717,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { mKeyguardUpdateMonitor, mock(NotificationIconContainerStatusBarViewModel.class), mock(ConfigurationState.class), - mock(ConfigurationController.class), + mock(SystemBarUtilsState.class), mock(StatusBarNotificationIconViewStore.class), mock(DemoModeController.class)); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt index 22dce3a2ff64..1bdf64434fcb 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/InternetTileViewModelTest.kt @@ -21,6 +21,7 @@ import com.android.settingslib.AccessibilityContentDescriptions.WIFI_OTHER_DEVIC import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription import com.android.systemui.common.shared.model.Text +import com.android.systemui.common.shared.model.Text.Companion.loadText import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags @@ -355,14 +356,14 @@ class InternetTileViewModelTest : SysuiTestCase() { connectivityRepository.setEthernetConnected(default = true, validated = true) - assertThat(latest?.secondaryLabel).isNull() - assertThat(latest?.secondaryTitle) - .isEqualTo(ethernetIcon!!.contentDescription.toString()) + assertThat(latest?.secondaryLabel.loadText(context)) + .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context)) + assertThat(latest?.secondaryTitle).isNull() assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet_fully) assertThat(latest?.icon).isNull() assertThat(latest?.stateDescription).isNull() assertThat(latest?.contentDescription.loadContentDescription(context)) - .isEqualTo(latest?.secondaryTitle) + .isEqualTo(latest?.secondaryLabel.loadText(context)) } @Test @@ -373,14 +374,14 @@ class InternetTileViewModelTest : SysuiTestCase() { connectivityRepository.setEthernetConnected(default = true, validated = false) - assertThat(latest?.secondaryLabel).isNull() - assertThat(latest?.secondaryTitle) - .isEqualTo(ethernetIcon!!.contentDescription.toString()) + assertThat(latest?.secondaryLabel.loadText(context)) + .isEqualTo(ethernetIcon!!.contentDescription.loadContentDescription(context)) + assertThat(latest?.secondaryTitle).isNull() assertThat(latest?.iconId).isEqualTo(R.drawable.stat_sys_ethernet) assertThat(latest?.icon).isNull() assertThat(latest?.stateDescription).isNull() assertThat(latest?.contentDescription.loadContentDescription(context)) - .isEqualTo(latest?.secondaryTitle) + .isEqualTo(latest?.secondaryLabel.loadText(context)) } private fun setWifiNetworkWithHotspot(hotspot: WifiNetworkModel.HotspotDeviceType) { diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 4f3f56423eb0..2940c398c315 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HeadsUpManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -34,7 +34,6 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import static org.mockito.MockitoAnnotations.initMocks; import android.app.Notification; import android.app.PendingIntent; @@ -57,17 +56,25 @@ import com.android.systemui.statusbar.AlertingNotificationManager; import com.android.systemui.statusbar.AlertingNotificationManagerTest; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; +import com.android.systemui.util.settings.GlobalSettings; +import com.android.systemui.util.time.SystemClock; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper -public class HeadsUpManagerTest extends AlertingNotificationManagerTest { +public class BaseHeadsUpManagerTest extends AlertingNotificationManagerTest { + @Rule + public MockitoRule rule = MockitoJUnit.rule(); + private static final int TEST_TOUCH_ACCEPTANCE_TIME = 200; private static final int TEST_A11Y_AUTO_DISMISS_TIME = 1_000; private static final int TEST_A11Y_TIMEOUT_TIME = 3_000; @@ -76,17 +83,33 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer())); @Mock private AccessibilityManagerWrapper mAccessibilityMgr; + static { + assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME); + assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME); + assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME); + + assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan( + TEST_TIMEOUT_TIME); + assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan( + TEST_TIMEOUT_TIME); + assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan( + TEST_A11Y_TIMEOUT_TIME); + } + private final class TestableHeadsUpManager extends BaseHeadsUpManager { TestableHeadsUpManager(Context context, HeadsUpManagerLogger logger, Handler handler, + GlobalSettings globalSettings, + SystemClock systemClock, AccessibilityManagerWrapper accessibilityManagerWrapper, UiEventLogger uiEventLogger) { - super(context, logger, handler, accessibilityManagerWrapper, uiEventLogger); + super(context, logger, handler, globalSettings, systemClock, + accessibilityManagerWrapper, uiEventLogger); mTouchAcceptanceDelay = TEST_TOUCH_ACCEPTANCE_TIME; mMinimumDisplayTime = TEST_MINIMUM_DISPLAY_TIME; - mAutoDismissNotificationDecay = TEST_AUTO_DISMISS_TIME; - mStickyDisplayTime = TEST_STICKY_AUTO_DISMISS_TIME; + mAutoDismissTime = TEST_AUTO_DISMISS_TIME; + mStickyForSomeTimeAutoDismissTime = TEST_STICKY_AUTO_DISMISS_TIME; } // The following are only implemented by HeadsUpManagerPhone. If you need them, use that. @@ -160,8 +183,8 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { } private BaseHeadsUpManager createHeadsUpManager() { - return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mAccessibilityMgr, - mUiEventLoggerFake); + return new TestableHeadsUpManager(mContext, mLogger, mTestHandler, mGlobalSettings, + mSystemClock, mAccessibilityMgr, mUiEventLoggerFake); } @Override @@ -214,19 +237,7 @@ public class HeadsUpManagerTest extends AlertingNotificationManagerTest { @Before @Override public void setUp() { - initMocks(this); super.setUp(); - - assertThat(TEST_MINIMUM_DISPLAY_TIME).isLessThan(TEST_AUTO_DISMISS_TIME); - assertThat(TEST_AUTO_DISMISS_TIME).isLessThan(TEST_STICKY_AUTO_DISMISS_TIME); - assertThat(TEST_STICKY_AUTO_DISMISS_TIME).isLessThan(TEST_A11Y_AUTO_DISMISS_TIME); - - assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_AUTO_DISMISS_TIME).isLessThan( - TEST_TIMEOUT_TIME); - assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_STICKY_AUTO_DISMISS_TIME).isLessThan( - TEST_TIMEOUT_TIME); - assertThat(TEST_TOUCH_ACCEPTANCE_TIME + TEST_A11Y_AUTO_DISMISS_TIME).isLessThan( - TEST_A11Y_TIMEOUT_TIME); } @After diff --git a/packages/SystemUI/tests/utils/src/android/content/pm/LauncherAppsKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/pm/LauncherAppsKosmos.kt new file mode 100644 index 000000000000..94fc1fce7f6f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/android/content/pm/LauncherAppsKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.content.pm + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +val Kosmos.launcherApps by Kosmos.Fixture { mock<LauncherApps>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt index 03270870bccd..7c5696c716d8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt @@ -20,10 +20,10 @@ import com.android.internal.widget.LockPatternUtils import com.android.internal.widget.LockPatternView import com.android.internal.widget.LockscreenCredential import com.android.keyguard.KeyguardSecurityModel.SecurityMode +import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate import com.android.systemui.authentication.shared.model.AuthenticationResultModel -import com.android.systemui.authentication.shared.model.AuthenticationThrottlingModel import com.android.systemui.dagger.SysUISingleton import dagger.Binds import dagger.Module @@ -40,9 +40,6 @@ class FakeAuthenticationRepository( private val currentTime: () -> Long, ) : AuthenticationRepository { - private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false) - override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = - _isAutoConfirmFeatureEnabled.asStateFlow() override val authenticationChallengeResult = MutableSharedFlow<Boolean>() override val hintedPinLength: Int = HINTING_PIN_LENGTH @@ -50,8 +47,13 @@ class FakeAuthenticationRepository( private val _isPatternVisible = MutableStateFlow(true) override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow() - override val throttling: MutableStateFlow<AuthenticationThrottlingModel?> = - MutableStateFlow(null) + override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null) + + override val hasLockoutOccurred = MutableStateFlow(false) + + private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false) + override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> = + _isAutoConfirmFeatureEnabled.asStateFlow() private val _authenticationMethod = MutableStateFlow<AuthenticationMethodModel>(DEFAULT_AUTHENTICATION_METHOD) @@ -67,10 +69,12 @@ class FakeAuthenticationRepository( _isPinEnhancedPrivacyEnabled.asStateFlow() private var failedAttemptCount = 0 - private var throttlingEndTimestamp = 0L + private var lockoutEndTimestamp = 0L private var credentialOverride: List<Any>? = null private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode() + var lockoutStartedReportCount = 0 + override suspend fun getAuthenticationMethod(): AuthenticationMethodModel { return authenticationMethod.value } @@ -89,6 +93,10 @@ class FakeAuthenticationRepository( authenticationChallengeResult.emit(isSuccessful) } + override suspend fun reportLockoutStarted(durationMs: Int) { + lockoutStartedReportCount++ + } + override suspend fun getPinLength(): Int { return (credentialOverride ?: DEFAULT_PIN).size } @@ -97,16 +105,19 @@ class FakeAuthenticationRepository( return failedAttemptCount } - override suspend fun getThrottlingEndTimestamp(): Long { - return throttlingEndTimestamp + override suspend fun getLockoutEndTimestamp(): Long { + return lockoutEndTimestamp } fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) { _isAutoConfirmFeatureEnabled.value = isEnabled } - override suspend fun setThrottleDuration(durationMs: Int) { - throttlingEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 + override suspend fun setLockoutDuration(durationMs: Int) { + lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0 + if (durationMs > 0) { + hasLockoutOccurred.value = true + } } override suspend fun checkCredential( @@ -125,17 +136,16 @@ class FakeAuthenticationRepository( else -> error("Unexpected credential type ${credential.type}!") } - return if ( - isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING - 1 - ) { + return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { + hasLockoutOccurred.value = false AuthenticationResultModel( isSuccessful = isSuccessful, - throttleDurationMs = 0, + lockoutDurationMs = 0, ) } else { AuthenticationResultModel( isSuccessful = false, - throttleDurationMs = THROTTLE_DURATION_MS, + lockoutDurationMs = LOCKOUT_DURATION_MS, ) } } @@ -165,9 +175,9 @@ class FakeAuthenticationRepository( AuthenticationPatternCoordinate(0, 1), AuthenticationPatternCoordinate(0, 2), ) - const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5 - const val THROTTLE_DURATION_SECONDS = 30 - const val THROTTLE_DURATION_MS = THROTTLE_DURATION_SECONDS * 1000 + const val MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT = 5 + const val LOCKOUT_DURATION_SECONDS = 30 + const val LOCKOUT_DURATION_MS = LOCKOUT_DURATION_SECONDS * 1000 const val HINTING_PIN_LENGTH = 6 val DEFAULT_PIN = buildList { repeat(HINTING_PIN_LENGTH) { add(it + 1) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt index e6b7f62c7d5f..abadaf754c30 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/FeatureFlagsClassicKosmos.kt @@ -16,6 +16,45 @@ package com.android.systemui.flags +import android.content.res.mainResources import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock -val Kosmos.featureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() } +/** + * Main fixture for supplying a [FeatureFlagsClassic]. Should be used by other fixtures. Unless + * overridden in the test, this by default uses [fakeFeatureFlagsClassic]. + */ +var Kosmos.featureFlagsClassic: FeatureFlagsClassic by Kosmos.Fixture { fakeFeatureFlagsClassic } + +/** + * Fixture supplying a shared [FakeFeatureFlagsClassic] instance. Can be accessed in tests in order + * to override flag values. + */ +val Kosmos.fakeFeatureFlagsClassic by Kosmos.Fixture { FakeFeatureFlagsClassic() } + +/** + * Fixture supplying a real [FeatureFlagsClassicRelease] instance, for use by tests that want to + * reflect the current state of the device in release builds (example: screenshot tests). + * + * By default, this fixture is unused; tests should override [featureFlagsClassic] in order to + * utilize this fixture: + * ```kotlin + * val kosmos = Kosmos() + * kosmos.featureFlagsClassic = kosmos.featureFlagsClassicRelease + * ``` + */ +val Kosmos.featureFlagsClassicRelease by + Kosmos.Fixture { + FeatureFlagsClassicRelease( + /* resources = */ mainResources, + /* systemProperties = */ systemPropertiesHelper, + /* serverFlagReader = */ serverFlagReader, + /* allFlags = */ FlagsCommonModule.providesAllFlags(), + /* restarter = */ restarter, + ) + } + +val Kosmos.systemPropertiesHelper by Kosmos.Fixture { SystemPropertiesHelper() } +var Kosmos.serverFlagReader: ServerFlagReader by Kosmos.Fixture { serverFlagReaderFake } +val Kosmos.serverFlagReaderFake by Kosmos.Fixture { ServerFlagReaderFake() } +var Kosmos.restarter: Restarter by Kosmos.Fixture { mock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt index 9c108487e4c5..f7005ab45ea0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/data/repository/FakeShadeRepository.kt @@ -18,21 +18,14 @@ package com.android.systemui.shade.data.repository import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.shade.domain.model.ShadeModel import dagger.Binds import dagger.Module import javax.inject.Inject -import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow /** Fake implementation of [ShadeRepository] */ @SysUISingleton class FakeShadeRepository @Inject constructor() : ShadeRepository { - - private val _shadeModel = MutableStateFlow(ShadeModel()) - override val shadeModel: Flow<ShadeModel> = _shadeModel - private val _qsExpansion = MutableStateFlow(0f) override val qsExpansion = _qsExpansion @@ -114,10 +107,6 @@ class FakeShadeRepository @Inject constructor() : ShadeRepository { _legacyIsClosing.value = isClosing } - fun setShadeModel(model: ShadeModel) { - _shadeModel.value = model - } - override fun setQsExpansion(qsExpansion: Float) { _qsExpansion.value = qsExpansion } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt new file mode 100644 index 000000000000..a48b27015c02 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.statusbar.notification.collection + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.notifPipeline by Kosmos.Fixture { mock<NotifPipeline>() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollectionKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollectionKosmos.kt new file mode 100644 index 000000000000..f00538e3fbcc --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollectionKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.statusbar.notification.collection.notifcollection + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.collection.notifPipeline + +var Kosmos.commonNotifCollection by Kosmos.Fixture { notifPipeline } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProviderKosmos.kt new file mode 100644 index 000000000000..e7c4085cabfd --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/provider/SectionStyleProviderKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.statusbar.notification.collection.provider + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock + +var Kosmos.sectionStyleProvider: SectionStyleProvider by Kosmos.Fixture { mock() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt new file mode 100644 index 000000000000..f7acae9846df --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/domain/interactor/RenderNotificationListInteractorKosmos.kt @@ -0,0 +1,26 @@ +/* + * 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.statusbar.notification.domain.interactor + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.collection.provider.sectionStyleProvider +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository + +val Kosmos.renderNotificationListInteractor by + Kosmos.Fixture { + RenderNotificationListInteractor(activeNotificationListRepository, sectionStyleProvider) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconBuilderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconBuilderKosmos.kt new file mode 100644 index 000000000000..4535652eb8c5 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconBuilderKosmos.kt @@ -0,0 +1,22 @@ +/* + * 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.statusbar.notification.icon + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos + +val Kosmos.iconBuilder by Kosmos.Fixture { IconBuilder(applicationContext) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt new file mode 100644 index 000000000000..d3a8e0c5970c --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/IconManagerKosmos.kt @@ -0,0 +1,24 @@ +/* + * 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.statusbar.notification.icon + +import android.content.pm.launcherApps +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.collection.notifcollection.commonNotifCollection + +val Kosmos.iconManager by + Kosmos.Fixture { IconManager(commonNotifCollection, launcherApps, iconBuilder) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt index 75e5aeafd52d..ca5b4010fd7d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationListViewBinderKosmos.kt @@ -26,18 +26,18 @@ import com.android.systemui.statusbar.notification.icon.ui.viewbinder.shelfNotif import com.android.systemui.statusbar.notification.icon.ui.viewbinder.statusBarIconViewBindingFailureTracker import com.android.systemui.statusbar.notification.stack.ui.viewmodel.notificationListViewModel import com.android.systemui.statusbar.phone.notificationIconAreaController -import com.android.systemui.statusbar.policy.configurationController +import com.android.systemui.statusbar.ui.systemBarUtilsState val Kosmos.notificationListViewBinder by Fixture { NotificationListViewBinder( viewModel = notificationListViewModel, backgroundDispatcher = testDispatcher, configuration = configurationState, - configurationController = configurationController, falsingManager = falsingManager, iconAreaController = notificationIconAreaController, iconViewBindingFailureTracker = statusBarIconViewBindingFailureTracker, metricsLogger = metricsLogger, shelfIconViewStore = shelfNotificationIconViewStore, + systemBarUtilsState = systemBarUtilsState, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/FakeSystemBarUtilsProxy.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/FakeSystemBarUtilsProxy.kt new file mode 100644 index 000000000000..d38baba8876f --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/FakeSystemBarUtilsProxy.kt @@ -0,0 +1,21 @@ +/* + * 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.statusbar.ui + +class FakeSystemBarUtilsProxy(private var statusBarHeight: Int) : SystemBarUtilsProxy { + override fun getStatusBarHeight(): Int = statusBarHeight +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxyKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxyKosmos.kt new file mode 100644 index 000000000000..f24037d4aea2 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsProxyKosmos.kt @@ -0,0 +1,51 @@ +/* + * 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.statusbar.ui + +import android.content.applicationContext +import android.content.res.mainResources +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.res.R + +/** + * Main fixture for supplying a [SystemBarUtilsProxy]. Should be used by other fixtures. Unless + * overridden in the test, this by default uses [fakeSystemBarUtilsProxy]. + */ +var Kosmos.systemBarUtilsProxy: SystemBarUtilsProxy by Fixture { fakeSystemBarUtilsProxy } + +/** + * Fixture supplying a real [SystemBarUtilsProxyImpl] instance, for use by tests that want to use + * the real device logic to determine system bar properties. Note this this real instance does *not* + * support Robolectric tests; by opting in, you are explicitly opting-out of using Robolectric. + * + * By default, this fixture is unused; tests should override [systemBarUtilsProxy] in order to + * utilize this fixture: + * ```kotlin + * val kosmos = Kosmos() + * kosmos.systemBarUtilsProxy = kosmos.systemBarUtilsProxyImpl + * ``` + */ +val Kosmos.systemBarUtilsProxyImpl by Fixture { SystemBarUtilsProxyImpl(applicationContext) } + +/** + * Fixture supplying a shared [FakeSystemBarUtilsProxy] instance. Can be accessed or overridden in + * tests in order to provide custom results. + */ +var Kosmos.fakeSystemBarUtilsProxy by Fixture { + FakeSystemBarUtilsProxy(mainResources.getDimensionPixelSize(R.dimen.status_bar_height)) +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt new file mode 100644 index 000000000000..e208add32efa --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/ui/SystemBarUtilsStateKosmos.kt @@ -0,0 +1,23 @@ +/* + * 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.statusbar.ui + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.policy.configurationController + +val Kosmos.systemBarUtilsState by + Kosmos.Fixture { SystemBarUtilsState(configurationController, systemBarUtilsProxy) } diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp index e9bb763e143a..b8cf13b11534 100644 --- a/services/accessibility/Android.bp +++ b/services/accessibility/Android.bp @@ -32,6 +32,22 @@ java_library_static { ], } +java_library_static { + name: "AccessibilityGestureUtils", + srcs: [ + "java/**/gestures/GestureMatcher.java", + "java/**/gestures/GestureManifold.java", + "java/**/gestures/MultiFingerMultiTap.java", + "java/**/gestures/TouchState.java", + ], + static_libs: [ + "services.accessibility", + ], + libs: [ + "androidx.annotation_annotation", + ], +} + aconfig_declarations { name: "com_android_server_accessibility_flags", package: "com.android.server.accessibility", diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java index 903a07140eae..e54f0c12c0ca 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureManifold.java @@ -82,7 +82,7 @@ import java.util.List; * detector. Gesture matchers are tied to a single gesture. It calls listener callback functions * when a gesture starts or completes. */ -class GestureManifold implements GestureMatcher.StateChangeListener { +public class GestureManifold implements GestureMatcher.StateChangeListener { private static final String LOG_TAG = "GestureManifold"; @@ -111,7 +111,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener { // Shared state information. private TouchState mState; - GestureManifold(Context context, Listener listener, TouchState state, Handler handler) { + public GestureManifold(Context context, Listener listener, TouchState state, Handler handler) { mContext = context; mHandler = handler; mListener = listener; @@ -222,7 +222,7 @@ class GestureManifold implements GestureMatcher.StateChangeListener { * @return True if the event has been appropriately handled by the gesture manifold and related * callback functions, false if it should be handled further by the calling function. */ - boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { + public boolean onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { if (mState.isClear()) { if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { // Validity safeguard: if touch state is clear, then matchers should always be clear diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java index f55ecb05c55f..0a2a780adf45 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -981,17 +981,22 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH transitionToDelegatingStateAndClear(); } transitToSinglePanningStateAndClear(); - } else { + } else if (!mIsTwoFingerCountReached) { + // If it is a two-finger gesture, do not transition to the + // delegating state to ensure the reachability of + // the two-finger triple tap (triggerable with ACTION_UP) transitionToDelegatingStateAndClear(); } } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation) && distanceClosestPointerToPoint( - mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance - // If mCompleteTapCount is not zero, it means that it is a multi tap - // gesture. So, we should not transit to the PanningScalingState. - && mCompletedTapCount == 0) { + mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) { // Second pointer is swiping, so transit to PanningScalingState - transitToPanningScalingStateAndClear(); + // Delay an ACTION_MOVE for tap timeout to ensure it is not trigger from + // multi finger multi tap + storePointerDownLocation(mSecondPointerDownLocation, event); + mHandler.sendEmptyMessageDelayed( + MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE, + ViewConfiguration.getTapTimeout()); } } break; diff --git a/services/companion/java/com/android/server/companion/virtual/InputController.java b/services/companion/java/com/android/server/companion/virtual/InputController.java index c111ec3213d9..1f89e57b90d7 100644 --- a/services/companion/java/com/android/server/companion/virtual/InputController.java +++ b/services/companion/java/com/android/server/companion/virtual/InputController.java @@ -253,13 +253,8 @@ class InputController { mInputManagerInternal.setDisplayEligibilityForPointerCapture(displayId, isEligible); } - void setLocalIme(int displayId) { - // WM throws a SecurityException if the display is untrusted. - if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) - == Display.FLAG_TRUSTED) { - mWindowManager.setDisplayImePolicy(displayId, - WindowManager.DISPLAY_IME_POLICY_LOCAL); - } + void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { + mWindowManager.setDisplayImePolicy(displayId, policy); } @GuardedBy("mLock") diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index 13c79248eb38..58aa2c303345 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -899,6 +899,24 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub @Override // Binder call @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) + public void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { + super.setDisplayImePolicy_enforcePermission(); + synchronized (mVirtualDeviceLock) { + if (!mVirtualDisplays.contains(displayId)) { + throw new SecurityException("Display ID " + displayId + + " not found for this virtual device"); + } + } + final long ident = Binder.clearCallingIdentity(); + try { + mInputController.setDisplayImePolicy(displayId, policy); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override // Binder call + @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) @Nullable public List<VirtualSensor> getVirtualSensorList() { super.getVirtualSensorList_enforcePermission(); @@ -1095,7 +1113,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mInputController.setPointerAcceleration(1f, displayId); mInputController.setDisplayEligibilityForPointerCapture(/* isEligible= */ false, displayId); - mInputController.setLocalIme(displayId); + // WM throws a SecurityException if the display is untrusted. + if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED) + == Display.FLAG_TRUSTED) { + mInputController.setDisplayImePolicy(displayId, + WindowManager.DISPLAY_IME_POLICY_LOCAL); + } } finally { Binder.restoreCallingIdentity(token); } diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index b9a3c0c41841..33726d17da62 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -17,6 +17,7 @@ package com.android.server; import static android.os.Flags.stateOfHealthPublic; +import static android.os.Flags.batteryServiceSupportCurrentAdbCommand; import static com.android.internal.logging.nano.MetricsProto.MetricsEvent; import static com.android.server.health.Utils.copyV1Battery; @@ -927,9 +928,12 @@ public final class BatteryService extends SystemService { pw.println("Battery service (battery) commands:"); pw.println(" help"); pw.println(" Print this help text."); - pw.println(" get [-f] [ac|usb|wireless|dock|status|level|temp|present|counter|invalid]"); - pw.println(" set [-f] " - + "[ac|usb|wireless|dock|status|level|temp|present|counter|invalid] <value>"); + String getSetOptions = "ac|usb|wireless|dock|status|level|temp|present|counter|invalid"; + if (batteryServiceSupportCurrentAdbCommand()) { + getSetOptions += "|current_now|current_average"; + } + pw.println(" get [-f] [" + getSetOptions + "]"); + pw.println(" set [-f] [" + getSetOptions + "] <value>"); pw.println(" Force a battery property value, freezing battery state."); pw.println(" -f: force a battery change broadcast be sent, prints new sequence."); pw.println(" unplug [-f]"); @@ -1001,6 +1005,16 @@ public final class BatteryService extends SystemService { case "counter": pw.println(mHealthInfo.batteryChargeCounterUah); break; + case "current_now": + if (batteryServiceSupportCurrentAdbCommand()) { + pw.println(mHealthInfo.batteryCurrentMicroamps); + } + break; + case "current_average": + if (batteryServiceSupportCurrentAdbCommand()) { + pw.println(mHealthInfo.batteryCurrentAverageMicroamps); + } + break; case "temp": pw.println(mHealthInfo.batteryTemperatureTenthsCelsius); break; @@ -1058,6 +1072,16 @@ public final class BatteryService extends SystemService { case "counter": mHealthInfo.batteryChargeCounterUah = Integer.parseInt(value); break; + case "current_now": + if (batteryServiceSupportCurrentAdbCommand()) { + mHealthInfo.batteryCurrentMicroamps = Integer.parseInt(value); + } + break; + case "current_average": + if (batteryServiceSupportCurrentAdbCommand()) { + mHealthInfo.batteryCurrentAverageMicroamps = + Integer.parseInt(value); + } case "temp": mHealthInfo.batteryTemperatureTenthsCelsius = Integer.parseInt(value); break; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 3e533a6ce601..ac173f3b5b7a 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -11972,6 +11972,7 @@ public class ActivityManagerService extends IActivityManager.Stub boolean dumpSwapPss; boolean dumpProto; boolean mDumpPrivateDirty; + boolean mDumpAllocatorStats; } @NeverCompile // Avoid size overhead of debugging code. @@ -11991,6 +11992,7 @@ public class ActivityManagerService extends IActivityManager.Stub opts.dumpSwapPss = false; opts.dumpProto = asProto; opts.mDumpPrivateDirty = false; + opts.mDumpAllocatorStats = false; int opti = 0; while (opti < args.length) { @@ -12027,7 +12029,8 @@ public class ActivityManagerService extends IActivityManager.Stub opts.isCheckinRequest = true; } else if ("--proto".equals(opt)) { opts.dumpProto = true; - + } else if ("--logstats".equals(opt)) { + opts.mDumpAllocatorStats = true; } else if ("-h".equals(opt)) { pw.println("meminfo dump options: [-a] [-d] [-c] [-s] [--oom] [process]"); pw.println(" -a: include all available information for each process."); @@ -12238,7 +12241,8 @@ public class ActivityManagerService extends IActivityManager.Stub try { thread.dumpMemInfo(tp.getWriteFd(), mi, opts.isCheckinRequest, opts.dumpFullDetails, - opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, innerArgs); + opts.dumpDalvik, opts.dumpSummaryOnly, opts.dumpUnreachable, + opts.mDumpAllocatorStats, innerArgs); tp.go(fd, opts.dumpUnreachable ? 30000 : 5000); } finally { tp.kill(); @@ -17510,6 +17514,8 @@ public class ActivityManagerService extends IActivityManager.Stub * other {@code ActivityManager#USER_OP_*} codes for failure. * */ + // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows + // delayed locking behavior once the private space flag is finalized. @Override public int stopUserWithDelayedLocking(final int userId, boolean force, final IStopUserCallback callback) { diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java index 47a99fe24ec4..a6b532cdef09 100644 --- a/services/core/java/com/android/server/am/UserController.java +++ b/services/core/java/com/android/server/am/UserController.java @@ -356,6 +356,8 @@ class UserController implements Handler.Callback { * Once total number of unlocked users reach mMaxRunningUsers, least recently used user * will be locked. */ + // TODO(b/302662311): Add javadoc changes corresponding to the user property that allows + // delayed locking behavior once the private space flag is finalized. @GuardedBy("mLock") private boolean mDelayUserDataLocking; @@ -365,11 +367,12 @@ class UserController implements Handler.Callback { private volatile boolean mAllowUserUnlocking; /** - * Keep track of last active users for mDelayUserDataLocking. - * The latest stopped user is placed in front while the least recently stopped user in back. + * Keep track of last active users for delayUserDataLocking. + * The most recently stopped user with delayed locking is placed in front, while the least + * recently stopped user in back. */ @GuardedBy("mLock") - private final ArrayList<Integer> mLastActiveUsers = new ArrayList<>(); + private final ArrayList<Integer> mLastActiveUsersForDelayedLocking = new ArrayList<>(); /** * Map of userId to {@link UserCompletedEventType} event flags, indicating which as-yet- @@ -1011,20 +1014,21 @@ class UserController implements Handler.Callback { Slogf.i(TAG, "stopSingleUserLU userId=" + userId); final UserState uss = mStartedUsers.get(userId); if (uss == null) { // User is not started - // If mDelayUserDataLocking is set and allowDelayedLocking is not set, we need to lock - // the requested user as the client wants to stop and lock the user. On the other hand, - // having keyEvictedCallback set will lead into locking user if mDelayUserDataLocking - // is set as that means client wants to lock the user immediately. - // If mDelayUserDataLocking is not set, the user was already locked when it was stopped - // and no further action is necessary. - if (mDelayUserDataLocking) { + // If canDelayDataLockingForUser() is true and allowDelayedLocking is false, we need + // to lock the requested user as the client wants to stop and lock the user. On the + // other hand, having keyEvictedCallback set will lead into locking user if + // canDelayDataLockingForUser() is true as that means client wants to lock the user + // immediately. + // If canDelayDataLockingForUser() is false, the user was already locked when it was + // stopped and no further action is necessary. + if (canDelayDataLockingForUser(userId)) { if (allowDelayedLocking && keyEvictedCallback != null) { Slogf.wtf(TAG, "allowDelayedLocking set with KeyEvictedCallback, ignore it" + " and lock user:" + userId, new RuntimeException()); allowDelayedLocking = false; } if (!allowDelayedLocking) { - if (mLastActiveUsers.remove(Integer.valueOf(userId))) { + if (mLastActiveUsersForDelayedLocking.remove(Integer.valueOf(userId))) { // should lock the user, user is already gone final ArrayList<KeyEvictedCallback> keyEvictedCallbacks; if (keyEvictedCallback != null) { @@ -1354,14 +1358,21 @@ class UserController implements Handler.Callback { @GuardedBy("mLock") private int updateUserToLockLU(@UserIdInt int userId, boolean allowDelayedLocking) { int userIdToLock = userId; - if (mDelayUserDataLocking && allowDelayedLocking && !getUserInfo(userId).isEphemeral() + // TODO: Decouple the delayed locking flows from mMaxRunningUsers or rename the property to + // state maximum running unlocked users specifically + if (canDelayDataLockingForUser(userIdToLock) && allowDelayedLocking + && !getUserInfo(userId).isEphemeral() && !hasUserRestriction(UserManager.DISALLOW_RUN_IN_BACKGROUND, userId)) { - mLastActiveUsers.remove((Integer) userId); // arg should be object, not index - mLastActiveUsers.add(0, userId); - int totalUnlockedUsers = mStartedUsers.size() + mLastActiveUsers.size(); + // arg should be object, not index + mLastActiveUsersForDelayedLocking.remove((Integer) userId); + mLastActiveUsersForDelayedLocking.add(0, userId); + int totalUnlockedUsers = mStartedUsers.size() + + mLastActiveUsersForDelayedLocking.size(); if (totalUnlockedUsers > mMaxRunningUsers) { // should lock a user - userIdToLock = mLastActiveUsers.get(mLastActiveUsers.size() - 1); - mLastActiveUsers.remove(mLastActiveUsers.size() - 1); + userIdToLock = mLastActiveUsersForDelayedLocking.get( + mLastActiveUsersForDelayedLocking.size() - 1); + mLastActiveUsersForDelayedLocking + .remove(mLastActiveUsersForDelayedLocking.size() - 1); Slogf.i(TAG, "finishUserStopped, stopping user:" + userId + " lock user:" + userIdToLock); } else { @@ -1374,6 +1385,24 @@ class UserController implements Handler.Callback { } /** + * Returns whether the user can have its CE storage left unlocked, even when it is stopped, + * either due to a global device configuration or an individual user's property. + */ + private boolean canDelayDataLockingForUser(@UserIdInt int userIdToLock) { + if (allowBiometricUnlockForPrivateProfile()) { + final UserProperties userProperties = getUserProperties(userIdToLock); + return (mDelayUserDataLocking || (userProperties != null + && userProperties.getAllowStoppingUserWithDelayedLocking())); + } + return mDelayUserDataLocking; + } + + private boolean allowBiometricUnlockForPrivateProfile() { + return android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace(); + } + + /** * Determines the list of users that should be stopped together with the specified * {@code userId}. The returned list includes {@code userId}. */ @@ -3161,7 +3190,7 @@ class UserController implements Handler.Callback { pw.println(" mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds)); pw.println(" mCurrentUserId:" + mCurrentUserId); pw.println(" mTargetUserId:" + mTargetUserId); - pw.println(" mLastActiveUsers:" + mLastActiveUsers); + pw.println(" mLastActiveUsersForDelayedLocking:" + mLastActiveUsersForDelayedLocking); pw.println(" mDelayUserDataLocking:" + mDelayUserDataLocking); pw.println(" mAllowUserUnlocking:" + mAllowUserUnlocking); pw.println(" shouldStopUserOnSwitch():" + shouldStopUserOnSwitch()); diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 9cfcb1679429..9cf15184027b 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -2037,9 +2037,8 @@ public class AudioDeviceBroker { } break; case MSG_L_UPDATED_ADI_DEVICE_STATE: - synchronized (mDeviceStateLock) { - mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj); - } break; + mAudioService.onUpdatedAdiDeviceState((AdiDeviceState) msg.obj); + break; default: Log.wtf(TAG, "Invalid message " + msg.what); diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index f7b7aaa60a35..44cb1367928d 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -13779,6 +13779,11 @@ public class AudioService extends IAudioService.Stub return mDeviceBroker.getDeviceAddresses(device); } + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + MusicFxHelper getMusicFxHelper() { + return mMusicFxHelper; + } + //====================== // misc //====================== diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java index 5f4e4c3bc4e0..85b3b49ecf78 100644 --- a/services/core/java/com/android/server/audio/MusicFxHelper.java +++ b/services/core/java/com/android/server/audio/MusicFxHelper.java @@ -17,9 +17,11 @@ package com.android.server.audio; import static android.content.pm.PackageManager.MATCH_ANY_USER; + import static com.android.server.audio.AudioService.MUSICFX_HELPER_MSG_START; import android.annotation.NonNull; +import android.annotation.RequiresPermission; import android.app.ActivityManager; import android.app.IUidObserver; import android.app.UidObserver; @@ -48,7 +50,6 @@ import java.util.List; /** * MusicFx management. - * . */ public class MusicFxHelper { private static final String TAG = "AS.MusicFxHelper"; @@ -60,14 +61,113 @@ public class MusicFxHelper { // Synchronization UidSessionMap access between UidObserver and AudioServiceBroadcastReceiver. private final Object mClientUidMapLock = new Object(); + private final String mPackageName = this.getClass().getPackage().getName(); + + private final String mMusicFxPackageName = "com.android.musicfx"; + + /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1; + // The binder token identifying the UidObserver registration. private IBinder mUidObserverToken = null; + // Package name and list of open audio sessions for this package + private static class PackageSessions { + String mPackageName; + List<Integer> mSessions; + } + + /* + * Override of SparseArray class to add bind/unbind and UID observer in the put/remove methods. + * + * put: + * - the first key/value set put into MySparseArray will trigger a procState bump (bindService) + * - if no valid observer token exist, will call registerUidObserver for put + * - for each new uid put into array, it will be added to uid observer list + * + * remove: + * - for each uid removed from array, it will be removed from uid observer list as well + * - if it's the last uid in array, no more MusicFx procState bump (unbindService), uid + * observer will also be removed, and observer token reset to null + */ + private class MySparseArray extends SparseArray<PackageSessions> { + private final String mMusicFxPackageName = "com.android.musicfx"; + + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }) + @Override + public void put(int uid, PackageSessions pkgSessions) { + if (size() == 0) { + int procState = ActivityManager.PROCESS_STATE_NONEXISTENT; + try { + procState = ActivityManager.getService().getPackageProcessState( + mMusicFxPackageName, mPackageName); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException with getPackageProcessState: " + e); + } + if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + Intent bindIntent = new Intent().setClassName(mMusicFxPackageName, + "com.android.musicfx.KeepAliveService"); + mContext.bindServiceAsUser( + bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE, + UserHandle.of(getCurrentUserId())); + Log.i(TAG, "bindService to " + mMusicFxPackageName); + } + + Log.i(TAG, mMusicFxPackageName + " procState " + procState); + } + try { + if (mUidObserverToken == null) { + mUidObserverToken = ActivityManager.getService().registerUidObserverForUids( + mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE, + ActivityManager.PROCESS_STATE_UNKNOWN, mPackageName, + new int[]{uid}); + Log.i(TAG, "registered to observer with UID " + uid); + } else if (get(uid) == null) { // addUidToObserver if this is a new UID + ActivityManager.getService().addUidToObserver(mUidObserverToken, mPackageName, + uid); + Log.i(TAG, " UID " + uid + " add to observer"); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException with UID observer add/register: " + e); + } + + super.put(uid, pkgSessions); + } + + @Override + public void remove(int uid) { + if (get(uid) != null) { + try { + ActivityManager.getService().removeUidFromObserver(mUidObserverToken, + mPackageName, uid); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException with removeUidFromObserver: " + e); + } + } + + super.remove(uid); + + // stop foreground service delegate and unregister UID observers with the last UID + if (size() == 0) { + try { + ActivityManager.getService().unregisterUidObserver(mEffectUidObserver); + } catch (RemoteException e) { + Log.e(TAG, "RemoteException with unregisterUidObserver: " + e); + } + mUidObserverToken = null; + mContext.unbindService(mMusicFxBindConnection); + Log.i(TAG, "last session closed, unregister UID observer, and unbind " + + mMusicFxPackageName); + } + } + } + // Hashmap of UID and list of open sessions for this UID. @GuardedBy("mClientUidMapLock") - private SparseArray<List<Integer>> mClientUidSessionMap = new SparseArray<>(); - - /*package*/ static final int MSG_EFFECT_CLIENT_GONE = MUSICFX_HELPER_MSG_START + 1; + private MySparseArray mClientUidSessionMap = new MySparseArray(); // UID observer for effect MusicFx clients private final IUidObserver mEffectUidObserver = new UidObserver() { @@ -102,23 +202,27 @@ public class MusicFxHelper { * Handle the broadcast {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION} and * {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} intents. * + * Only intents without target application package {@link android.content.Intent#getPackage} + * will be handled by the MusicFxHelper, all intents handled and forwarded by MusicFxHelper + * will have the target application package. + * * If the intent is {@link #ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION}: - * - If the MusicFx process is not running, call bindService with AUTO_CREATE to create. - * - If this is the first audio session in MusicFx, call set foreground service delegate. + * - If the MusicFx process is not running, call bindServiceAsUser with AUTO_CREATE to create. + * - If this is the first audio session of MusicFx, call set foreground service delegate. * - If this is the first audio session for a given UID, add the UID into observer. * - * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION}: - * - MusicFx will not be foreground delegated anymore. - * - The KeepAliveService of MusicFx will be unbound. - * - The UidObserver will be removed. + * If the intent is {@link #ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION} + * - The KeepAliveService of MusicFx will be unbound, and MusicFx will not be foreground + * delegated anymore if the last session of the last package was closed. + * - The Uid Observer will be removed when the last session of a package was closed. */ + @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS}) public void handleAudioEffectBroadcast(Context context, Intent intent) { String target = intent.getPackage(); if (target != null) { Log.w(TAG, "effect broadcast already targeted to " + target); return; } - intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); final PackageManager pm = context.getPackageManager(); // TODO this should target a user-selected panel List<ResolveInfo> ril = pm.queryBroadcastReceivers(intent, 0 /* flags */); @@ -126,14 +230,14 @@ public class MusicFxHelper { ResolveInfo ri = ril.get(0); final String senderPackageName = intent.getStringExtra(AudioEffect.EXTRA_PACKAGE_NAME); try { - final int senderUid = pm.getPackageUidAsUser(senderPackageName, - PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); if (ri != null && ri.activityInfo != null && ri.activityInfo.packageName != null) { + final int senderUid = pm.getPackageUidAsUser(senderPackageName, + PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); + intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); intent.setPackage(ri.activityInfo.packageName); - synchronized (mClientUidMapLock) { - setMusicFxServiceWithObserver(context, intent, senderUid); + if (setMusicFxServiceWithObserver(intent, senderUid, senderPackageName)) { + context.sendBroadcastAsUser(intent, UserHandle.ALL); } - context.sendBroadcastAsUser(intent, UserHandle.ALL); return; } } catch (PackageManager.NameNotFoundException e) { @@ -144,127 +248,105 @@ public class MusicFxHelper { Log.w(TAG, "couldn't find receiver package for effect intent"); } - /** - * Handle the UidObserver onUidGone callback of MusicFx clients. - * All open audio sessions of this UID will be closed. - * If this is the last UID for MusicFx: - * - MusicFx will not be foreground delegated anymore. - * - The KeepAliveService of MusicFx will be unbound. - * - The UidObserver will be removed. - */ - public void handleEffectClientUidGone(int uid) { - synchronized (mClientUidMapLock) { - Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE"); - // Once the uid is no longer running, close all remain audio session(s) for this UID - if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) { - final List<Integer> sessions = - new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid))); - Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions"); - for (Integer session : sessions) { - Intent intent = new Intent( - AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); - intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, session); - setMusicFxServiceWithObserver(mContext, intent, uid); - Log.i(TAG, "Close session " + session + " of UID " + uid); - } - mClientUidSessionMap.remove(Integer.valueOf(uid)); + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }) + @GuardedBy("mClientUidMapLock") + private boolean handleAudioEffectSessionOpen( + int senderUid, String senderPackageName, int sessionId) { + Log.d(TAG, senderPackageName + " UID " + senderUid + " open MusicFx session " + sessionId); + + PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); + if (pkgSessions != null && pkgSessions.mSessions != null) { + if (pkgSessions.mSessions.contains(sessionId)) { + Log.e(TAG, "Audio session " + sessionId + " already open for UID: " + + senderUid + ", package: " + senderPackageName + ", abort"); + return false; + } + if (pkgSessions.mPackageName != senderPackageName) { + Log.w(TAG, "Inconsistency package names for UID open: " + senderUid + " prev: " + + pkgSessions.mPackageName + ", now: " + senderPackageName); + return false; } + } else { + // first session for this UID, create a new Package/Sessions pair + pkgSessions = new PackageSessions(); + pkgSessions.mSessions = new ArrayList(); + pkgSessions.mPackageName = senderPackageName; } + + pkgSessions.mSessions.add(Integer.valueOf(sessionId)); + mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions); + return true; } + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }) @GuardedBy("mClientUidMapLock") - private void setMusicFxServiceWithObserver(Context context, Intent intent, int senderUid) { - PackageManager pm = context.getPackageManager(); - try { - final int audioSession = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, - AudioManager.AUDIO_SESSION_ID_GENERATE); - if (AudioManager.AUDIO_SESSION_ID_GENERATE == audioSession) { - Log.e(TAG, "Intent missing audio session: " + audioSession); - return; + private boolean handleAudioEffectSessionClose( + int senderUid, String senderPackageName, int sessionId) { + Log.d(TAG, senderPackageName + " UID " + senderUid + " close MusicFx session " + sessionId); + + PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); + if (pkgSessions == null) { + Log.e(TAG, senderPackageName + " UID " + senderUid + " does not exist in map, abort"); + return false; + } + if (pkgSessions.mPackageName != senderPackageName) { + Log.w(TAG, "Inconsistency package names for UID " + senderUid + " close, prev: " + + pkgSessions.mPackageName + ", now: " + senderPackageName); + return false; + } + + if (pkgSessions.mSessions != null && pkgSessions.mSessions.size() != 0) { + if (!pkgSessions.mSessions.contains(sessionId)) { + Log.e(TAG, senderPackageName + " UID " + senderUid + " session " + sessionId + + " does not exist in map, abort"); + return false; } - // only apply to com.android.musicfx and KeepAliveService for now - final String musicFxPackageName = "com.android.musicfx"; - final String musicFxKeepAliveService = "com.android.musicfx.KeepAliveService"; - final int musicFxUid = pm.getPackageUidAsUser(musicFxPackageName, - PackageManager.PackageInfoFlags.of(MATCH_ANY_USER), getCurrentUserId()); + pkgSessions.mSessions.remove(Integer.valueOf(sessionId)); + } - if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) { - List<Integer> sessions = new ArrayList<>(); - Log.d(TAG, "UID " + senderUid + ", open MusicFx session " + audioSession); - // start foreground service delegate and register UID observer with the first - // session of first UID open - if (0 == mClientUidSessionMap.size()) { - final int procState = ActivityManager.getService().getPackageProcessState( - musicFxPackageName, this.getClass().getPackage().getName()); - // if musicfx process not in binding state, call bindService with AUTO_CREATE - if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { - Intent bindIntent = new Intent().setClassName(musicFxPackageName, - musicFxKeepAliveService); - context.bindServiceAsUser( - bindIntent, mMusicFxBindConnection, Context.BIND_AUTO_CREATE, - UserHandle.of(getCurrentUserId())); - Log.i(TAG, "bindService to " + musicFxPackageName); - } + if (pkgSessions.mSessions == null || pkgSessions.mSessions.size() == 0) { + // remove UID from map as well as the UID observer with the last session close + mClientUidSessionMap.remove(Integer.valueOf(senderUid)); + } else { + mClientUidSessionMap.put(Integer.valueOf(senderUid), pkgSessions); + } - Log.i(TAG, "Package " + musicFxPackageName + " uid " + musicFxUid - + " procState " + procState); - } else if (mClientUidSessionMap.get(Integer.valueOf(senderUid)) != null) { - sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); - if (sessions.contains(audioSession)) { - Log.e(TAG, "Audio session " + audioSession + " already exist for UID " - + senderUid + ", abort"); - return; - } - } - // first session of this UID - if (sessions.size() == 0) { - // call registerUidObserverForUids with the first UID and first session - if (mClientUidSessionMap.size() == 0 || mUidObserverToken == null) { - mUidObserverToken = ActivityManager.getService().registerUidObserverForUids( - mEffectUidObserver, ActivityManager.UID_OBSERVER_GONE, - ActivityManager.PROCESS_STATE_UNKNOWN, null, new int[]{senderUid}); - Log.i(TAG, "UID " + senderUid + " registered to observer"); - } else { - // add UID to observer for each new UID - ActivityManager.getService().addUidToObserver(mUidObserverToken, TAG, - senderUid); - Log.i(TAG, "UID " + senderUid + " addeded to observer"); - } - } + return true; + } - sessions.add(Integer.valueOf(audioSession)); - mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions); - } else { - if (mClientUidSessionMap.get(senderUid) != null) { - Log.d(TAG, "UID " + senderUid + ", close MusicFx session " + audioSession); - List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(senderUid)); - sessions.remove(Integer.valueOf(audioSession)); - if (0 == sessions.size()) { - mClientUidSessionMap.remove(Integer.valueOf(senderUid)); - } else { - mClientUidSessionMap.put(Integer.valueOf(senderUid), sessions); - } + /** + * @return true if the intent is validated and handled successfully, false with any error + * (invalid sender/intent for example). + */ + @RequiresPermission(anyOf = { + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + android.Manifest.permission.INTERACT_ACROSS_USERS, + android.Manifest.permission.INTERACT_ACROSS_PROFILES + }) + private boolean setMusicFxServiceWithObserver( + Intent intent, int senderUid, String packageName) { + final int session = intent.getIntExtra(AudioEffect.EXTRA_AUDIO_SESSION, + AudioManager.AUDIO_SESSION_ID_GENERATE); + if (AudioManager.AUDIO_SESSION_ID_GENERATE == session) { + Log.e(TAG, packageName + " intent have no invalid audio session"); + return false; + } - // stop foreground service delegate and unregister UID observer with the - // last session of last UID close - if (0 == mClientUidSessionMap.size()) { - ActivityManager.getService().unregisterUidObserver(mEffectUidObserver); - mClientUidSessionMap.clear(); - context.unbindService(mMusicFxBindConnection); - Log.i(TAG, " remove all sessions, unregister UID observer, and unbind " - + musicFxPackageName); - } - } else { - // if the audio session already closed, print an error - Log.e(TAG, "UID " + senderUid + " close audio session " + audioSession - + " which does not exist"); - } + synchronized (mClientUidMapLock) { + if (intent.getAction().equals(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION)) { + return handleAudioEffectSessionOpen(senderUid, packageName, session); + } else { + return handleAudioEffectSessionClose(senderUid, packageName, session); } - } catch (PackageManager.NameNotFoundException e) { - Log.e(TAG, "Not able to find UID from package: " + e); - } catch (RemoteException e) { - Log.e(TAG, "RemoteException " + e + " with handling intent"); } } @@ -281,6 +363,39 @@ public class MusicFxHelper { return UserHandle.USER_SYSTEM; } + + /** + * Handle the UidObserver onUidGone callback of MusicFx clients. + * Send close intent for all open audio sessions of this UID. The mClientUidSessionMap will be + * updated with the handling of close intent in setMusicFxServiceWithObserver. + */ + @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS}) + private void handleEffectClientUidGone(int uid) { + synchronized (mClientUidMapLock) { + Log.d(TAG, "handle MSG_EFFECT_CLIENT_GONE uid: " + uid + " mapSize: " + + mClientUidSessionMap.size()); + // Once the uid is no longer running, close all remain audio session(s) for this UID + final PackageSessions pkgSessions = mClientUidSessionMap.get(Integer.valueOf(uid)); + if (pkgSessions != null) { + Log.i(TAG, "UID " + uid + " gone, closing all sessions"); + + // send close intent for each open session of the gone UID + for (Integer sessionId : pkgSessions.mSessions) { + Intent closeIntent = + new Intent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION); + closeIntent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, pkgSessions.mPackageName); + closeIntent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId); + closeIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES); + // set broadcast target + closeIntent.setPackage(mMusicFxPackageName); + mContext.sendBroadcastAsUser(closeIntent, UserHandle.ALL); + } + mClientUidSessionMap.remove(Integer.valueOf(uid)); + } + } + } + + @RequiresPermission(allOf = {android.Manifest.permission.INTERACT_ACROSS_USERS}) /*package*/ void handleMessage(Message msg) { switch (msg.what) { case MSG_EFFECT_CLIENT_GONE: @@ -292,5 +407,4 @@ public class MusicFxHelper { break; } } - } diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 3b7d80c844de..1e5e147749a2 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -1292,7 +1292,11 @@ public class SyncManager { */ private boolean isPackageStopped(String packageName, int userId) { if (android.content.pm.Flags.stayStopped()) { - return mPackageManagerInternal.isPackageStopped(packageName, userId); + try { + return mPackageManagerInternal.isPackageStopped(packageName, userId); + } catch (IllegalArgumentException e) { + Log.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName); + } } return false; } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 09c388f08c7b..09107c1bb4d3 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -5626,7 +5626,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @Override public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) { // TODO(b/287269288): validate that id belongs to a valid virtual device instead. - Preconditions.checkArgument(deviceId == Context.DEVICE_ID_DEFAULT, + Preconditions.checkArgument(deviceId != Context.DEVICE_ID_DEFAULT, "DeviceId " + deviceId + " does not belong to a virtual device."); synchronized (ImfLock.class) { if (imeId == null) { diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java index d359280c2a6f..bab3cbea108e 100644 --- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java +++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java @@ -424,6 +424,7 @@ public abstract class IContextHubWrapper { // 9a17008d-6bf1-445a-9011-6d21bd985b6c private static final byte[] UUID = {-102, 23, 0, -115, 107, -15, 68, 90, -112, 17, 109, 33, -67, -104, 91, 108}; + private static final String NAME = "ContextHubService"; ContextHubAidlCallback(int contextHubId, ICallback callback) { mContextHubId = contextHubId; @@ -466,10 +467,14 @@ public abstract class IContextHubWrapper { // TODO(271471342): Implement } - public byte[] getUuid() throws RemoteException { + public byte[] getUuid() { return UUID; } + public String getName() { + return NAME; + } + @Override public String getInterfaceHash() { return android.hardware.contexthub.IContextHubCallback.HASH; diff --git a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java index a00999d08b5b..7cf3983e57f6 100644 --- a/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/AudioPoliciesBluetoothRouteController.java @@ -39,6 +39,7 @@ import android.util.SparseBooleanArray; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; +import com.android.media.flags.Flags; import java.util.ArrayList; import java.util.HashMap; @@ -219,7 +220,10 @@ import java.util.stream.Collectors; BluetoothRouteInfo newBtRoute = new BluetoothRouteInfo(); newBtRoute.mBtDevice = device; - String deviceName = device.getName(); + String deviceName = + Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName() + ? device.getAlias() + : device.getName(); if (TextUtils.isEmpty(deviceName)) { deviceName = mContext.getResources().getText(R.string.unknownName).toString(); } diff --git a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java index 041fceaf8d3d..ede2d274563e 100644 --- a/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java +++ b/services/core/java/com/android/server/media/LegacyBluetoothRouteController.java @@ -43,6 +43,7 @@ import android.util.SparseBooleanArray; import android.util.SparseIntArray; import com.android.internal.R; +import com.android.media.flags.Flags; import java.util.ArrayList; import java.util.HashMap; @@ -283,7 +284,10 @@ class LegacyBluetoothRouteController implements BluetoothRouteController { newBtRoute.mBtDevice = device; String routeId = device.getAddress(); - String deviceName = device.getName(); + String deviceName = + Flags.enableUseOfBluetoothDeviceGetAliasForMr2infoGetName() + ? device.getAlias() + : device.getName(); if (TextUtils.isEmpty(deviceName)) { deviceName = mContext.getResources().getText(R.string.unknownName).toString(); } diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java index bdcec3a33221..82622d9a4ea8 100644 --- a/services/core/java/com/android/server/pm/AppsFilterImpl.java +++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java @@ -54,6 +54,7 @@ import android.util.ArrayMap; import android.util.ArraySet; import android.util.Slog; import android.util.SparseBooleanArray; +import android.util.SparseSetArray; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; @@ -1030,14 +1031,18 @@ public final class AppsFilterImpl extends AppsFilterLocked implements Watchable, private void recomputeComponentVisibility( ArrayMap<String, ? extends PackageStateInternal> existingSettings) { final WatchedArraySet<String> protectedBroadcasts; + final WatchedArraySet<Integer> forceQueryable; synchronized (mProtectedBroadcastsLock) { protectedBroadcasts = mProtectedBroadcasts.snapshot(); } + synchronized (mForceQueryableLock) { + forceQueryable = mForceQueryable.snapshot(); + } final ParallelComputeComponentVisibility computer = new ParallelComputeComponentVisibility( - existingSettings, mForceQueryable, protectedBroadcasts); + existingSettings, forceQueryable, protectedBroadcasts); + SparseSetArray<Integer> queriesViaComponent = computer.execute(); synchronized (mQueriesViaComponentLock) { - mQueriesViaComponent.clear(); - computer.execute(mQueriesViaComponent); + mQueriesViaComponent.copyFrom(queriesViaComponent); } mQueriesViaComponentRequireRecompute.set(false); diff --git a/services/core/java/com/android/server/pm/AppsFilterUtils.java b/services/core/java/com/android/server/pm/AppsFilterUtils.java index f3f64c5010ee..200734b37269 100644 --- a/services/core/java/com/android/server/pm/AppsFilterUtils.java +++ b/services/core/java/com/android/server/pm/AppsFilterUtils.java @@ -26,6 +26,7 @@ import android.content.IntentFilter; import android.util.ArrayMap; import android.util.ArraySet; import android.util.Pair; +import android.util.SparseSetArray; import com.android.internal.pm.pkg.component.ParsedComponent; import com.android.internal.pm.pkg.component.ParsedIntentInfo; @@ -37,7 +38,6 @@ import com.android.server.pm.pkg.AndroidPackage; import com.android.server.pm.pkg.PackageState; import com.android.server.pm.pkg.PackageStateInternal; import com.android.server.utils.WatchedArraySet; -import com.android.server.utils.WatchedSparseSetArray; import java.util.ArrayList; import java.util.List; @@ -213,7 +213,9 @@ final class AppsFilterUtils { /** * Computes component visibility of all packages in parallel from a thread pool. */ - void execute(@NonNull WatchedSparseSetArray<Integer> outQueriesViaComponent) { + @NonNull + SparseSetArray<Integer> execute() { + final SparseSetArray<Integer> queriesViaComponent = new SparseSetArray<>(); final ExecutorService pool = ConcurrentUtils.newFixedThreadPool( MAX_THREADS, ParallelComputeComponentVisibility.class.getSimpleName(), THREAD_PRIORITY_DEFAULT); @@ -239,7 +241,7 @@ final class AppsFilterUtils { try { final ArraySet<Integer> visibleList = future.get(); if (visibleList.size() != 0) { - outQueriesViaComponent.addAll(appId, visibleList); + queriesViaComponent.addAll(appId, visibleList); } } catch (InterruptedException | ExecutionException e) { throw new IllegalStateException(e); @@ -248,6 +250,7 @@ final class AppsFilterUtils { } finally { pool.shutdownNow(); } + return queriesViaComponent; } /** diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java index 92d469ccbfac..c36c8caf8b19 100644 --- a/services/core/java/com/android/server/pm/Computer.java +++ b/services/core/java/com/android/server/pm/Computer.java @@ -405,9 +405,19 @@ public interface Computer extends PackageDataSnapshot { boolean isInstallDisabledForPackage(@NonNull String packageName, int uid, @UserIdInt int userId); - @Nullable - List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo, - @PackageManager.PackageInfoFlagsBits long flags, int callingUid, @UserIdInt int userId); + /** + * Returns a Pair that contains a list of packages that depend on the target library and the + * package library dependency information. The List<VersionedPackage> indicates a list of + * packages that depend on the target library, it may be null if no package depends on + * the target library. The List<Boolean> indicates whether each VersionedPackage in + * the List<VersionedPackage> optionally depends on the target library, where true means + * optional and false means required. It may be null if no package depends on + * the target library or without dependency information, e.g. uses-static-library. + */ + @NonNull + Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary( + @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags, + int callingUid, @UserIdInt int userId); @Nullable ParceledListSlice<SharedLibraryInfo> getDeclaredSharedLibraries( diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java index 139c7c0849b0..2ae10051182d 100644 --- a/services/core/java/com/android/server/pm/ComputerEngine.java +++ b/services/core/java/com/android/server/pm/ComputerEngine.java @@ -895,6 +895,9 @@ public class ComputerEngine implements Computer { @PackageManager.ResolveInfoFlagsBits long flags, int filterCallingUid, int userId) { ParsedActivity a = mComponentResolver.getActivity(component); + // Allow to match activities of quarantined packages. + flags |= PackageManager.MATCH_QUARANTINED_COMPONENTS; + if (DEBUG_PACKAGE_INFO) Log.v(TAG, "getActivityInfo " + component + ": " + a); AndroidPackage pkg = a == null ? null : mPackages.get(a.getPackageName()); @@ -3862,11 +3865,13 @@ public class ComputerEngine implements Computer { Binder.restoreCallingIdentity(identity); } + var usingSharedLibraryPair = + getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId); SharedLibraryInfo resLibInfo = new SharedLibraryInfo(libInfo.getPath(), libInfo.getPackageName(), libInfo.getAllCodePaths(), libInfo.getName(), libInfo.getLongVersion(), libInfo.getType(), declaringPackage, - getPackagesUsingSharedLibrary(libInfo, flags, callingUid, userId), + usingSharedLibraryPair.first, (libInfo.getDependencies() == null ? null : new ArrayList<>(libInfo.getDependencies())), @@ -3935,13 +3940,15 @@ public class ComputerEngine implements Computer { return false; } + @Override - public List<VersionedPackage> getPackagesUsingSharedLibrary(@NonNull SharedLibraryInfo libInfo, - @PackageManager.PackageInfoFlagsBits long flags, int callingUid, - @UserIdInt int userId) { + public Pair<List<VersionedPackage>, List<Boolean>> getPackagesUsingSharedLibrary( + @NonNull SharedLibraryInfo libInfo, @PackageManager.PackageInfoFlagsBits long flags, + int callingUid, @UserIdInt int userId) { List<VersionedPackage> versionedPackages = null; final ArrayMap<String, ? extends PackageStateInternal> packageStates = getPackageStates(); final int packageCount = packageStates.size(); + List<Boolean> usesLibsOptional = null; for (int i = 0; i < packageCount; i++) { PackageStateInternal ps = packageStates.valueAt(i); if (ps == null) { @@ -3958,12 +3965,15 @@ public class ComputerEngine implements Computer { libInfo.isStatic() ? ps.getUsesStaticLibraries() : ps.getUsesSdkLibraries(); final long[] libsVersions = libInfo.isStatic() ? ps.getUsesStaticLibrariesVersions() : ps.getUsesSdkLibrariesVersionsMajor(); + final boolean[] libsOptional = libInfo.isSdk() + ? ps.getUsesSdkLibrariesOptional() : null; final int libIdx = ArrayUtils.indexOf(libs, libName); if (libIdx < 0) { continue; } if (libsVersions[libIdx] != libInfo.getLongVersion()) { + // Not expected StaticLib/SdkLib version continue; } if (shouldFilterApplication(ps, callingUid, userId)) { @@ -3972,6 +3982,9 @@ public class ComputerEngine implements Computer { if (versionedPackages == null) { versionedPackages = new ArrayList<>(); } + if (usesLibsOptional == null) { + usesLibsOptional = new ArrayList<>(); + } // If the dependent is a static shared lib, use the public package name String dependentPackageName = ps.getPackageName(); if (ps.getPkg() != null && ps.getPkg().isStaticSharedLibrary()) { @@ -3979,6 +3992,7 @@ public class ComputerEngine implements Computer { } versionedPackages.add(new VersionedPackage(dependentPackageName, ps.getVersionCode())); + usesLibsOptional.add(libsOptional != null && libsOptional[libIdx]); } else if (ps.getPkg() != null) { if (ArrayUtils.contains(ps.getPkg().getUsesLibraries(), libName) || ArrayUtils.contains(ps.getPkg().getUsesOptionalLibraries(), libName)) { @@ -3994,7 +4008,7 @@ public class ComputerEngine implements Computer { } } - return versionedPackages; + return new Pair<>(versionedPackages, usesLibsOptional); } @Nullable @@ -4053,13 +4067,14 @@ public class ComputerEngine implements Computer { Binder.restoreCallingIdentity(identity); } + var usingSharedLibraryPair = + getPackagesUsingSharedLibrary(libraryInfo, flags, callingUid, userId); SharedLibraryInfo resultLibraryInfo = new SharedLibraryInfo( libraryInfo.getPath(), libraryInfo.getPackageName(), libraryInfo.getAllCodePaths(), libraryInfo.getName(), libraryInfo.getLongVersion(), libraryInfo.getType(), libraryInfo.getDeclaringPackage(), - getPackagesUsingSharedLibrary( - libraryInfo, flags, callingUid, userId), + usingSharedLibraryPair.first, libraryInfo.getDependencies() == null ? null : new ArrayList<>(libraryInfo.getDependencies()), libraryInfo.isNative()); diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java index 80e6c833dfb2..93836266d1f4 100644 --- a/services/core/java/com/android/server/pm/DeletePackageHelper.java +++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java @@ -18,7 +18,6 @@ package com.android.server.pm; import static android.Manifest.permission.CONTROL_KEYGUARD; import static android.Manifest.permission.MANAGE_PROFILE_AND_DEVICE_OWNERS; -import static android.content.pm.Flags.sdkLibIndependence; 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_KEEP_DATA; @@ -40,6 +39,7 @@ import android.app.ApplicationExitInfo; import android.app.ApplicationPackageManager; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.Flags; import android.content.pm.IPackageDeleteObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -72,8 +72,6 @@ import com.android.server.wm.ActivityTaskManagerInternal; import dalvik.system.VMRuntime; -import java.util.List; - /** * Deletes a package. Uninstall if installed, or at least deletes the base directory if it's called * from a failed installation. Fixes user state after deletion. @@ -181,16 +179,36 @@ final class DeletePackageHelper { } if (libraryInfo != null) { + boolean flagSdkLibIndependence = Flags.sdkLibIndependence(); for (int currUserId : allUsers) { if (removeUser != UserHandle.USER_ALL && removeUser != currUserId) { continue; } - List<VersionedPackage> libClientPackages = - computer.getPackagesUsingSharedLibrary(libraryInfo, - MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId); - boolean allowSdkLibIndependence = - (pkg.getSdkLibraryName() != null) && sdkLibIndependence(); - if (!ArrayUtils.isEmpty(libClientPackages) && !allowSdkLibIndependence) { + var libClientPackagesPair = computer.getPackagesUsingSharedLibrary( + libraryInfo, MATCH_KNOWN_PACKAGES, Process.SYSTEM_UID, currUserId); + var libClientPackages = libClientPackagesPair.first; + var libClientOptional = libClientPackagesPair.second; + // We by default don't allow removing a package if the host lib is still be + // used by other client packages + boolean allowLibIndependence = false; + // Only when the sdkLibIndependence flag is enabled we will respect the + // "optional" attr in uses-sdk-library. Only allow to remove sdk-lib host + // package if no required clients depend on it + if ((pkg.getSdkLibraryName() != null) + && !ArrayUtils.isEmpty(libClientPackages) + && !ArrayUtils.isEmpty(libClientOptional) + && (libClientPackages.size() == libClientOptional.size()) + && flagSdkLibIndependence) { + allowLibIndependence = true; + for (int i = 0; i < libClientPackages.size(); i++) { + boolean usesSdkLibOptional = libClientOptional.get(i); + if (!usesSdkLibOptional) { + allowLibIndependence = false; + break; + } + } + } + if (!ArrayUtils.isEmpty(libClientPackages) && !allowLibIndependence) { Slog.w(TAG, "Not removing package " + pkg.getManifestPackageName() + " hosting lib " + libraryInfo.getName() + " version " + libraryInfo.getLongVersion() + " used by " + libClientPackages diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f27c462700be..2880f84c6445 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -7113,9 +7113,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService if (info == null) { continue; } - final List<VersionedPackage> dependents = - computer.getPackagesUsingSharedLibrary(info, 0, Process.SYSTEM_UID, - userId); + var usingSharedLibraryPair = computer.getPackagesUsingSharedLibrary(info, 0, + Process.SYSTEM_UID, userId); + final List<VersionedPackage> dependents = usingSharedLibraryPair.first; if (dependents == null) { continue; } diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java index 174df44c4263..7d0a1f6afe1d 100644 --- a/services/core/java/com/android/server/pm/PackageSetting.java +++ b/services/core/java/com/android/server/pm/PackageSetting.java @@ -123,11 +123,15 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @Nullable private Map<String, Set<String>> mimeGroups; + // TODO(b/314036181): encapsulate all these fields for usesSdk, instead of having three + // separate arrays. @Nullable private String[] usesSdkLibraries; @Nullable private long[] usesSdkLibrariesVersionsMajor; + @Nullable + private boolean[] usesSdkLibrariesOptional; @Nullable private String[] usesStaticLibraries; @@ -701,6 +705,9 @@ public class PackageSetting extends SettingBase implements PackageStateInternal usesSdkLibrariesVersionsMajor = other.usesSdkLibrariesVersionsMajor != null ? Arrays.copyOf(other.usesSdkLibrariesVersionsMajor, other.usesSdkLibrariesVersionsMajor.length) : null; + usesSdkLibrariesOptional = other.usesSdkLibrariesOptional != null + ? Arrays.copyOf(other.usesSdkLibrariesOptional, + other.usesSdkLibrariesOptional.length) : null; usesStaticLibraries = other.usesStaticLibraries != null ? Arrays.copyOf(other.usesStaticLibraries, @@ -1344,6 +1351,12 @@ public class PackageSetting extends SettingBase implements PackageStateInternal @NonNull @Override + public boolean[] getUsesSdkLibrariesOptional() { + return usesSdkLibrariesOptional == null ? EmptyArray.BOOLEAN : usesSdkLibrariesOptional; + } + + @NonNull + @Override public String[] getUsesStaticLibraries() { return usesStaticLibraries == null ? EmptyArray.STRING : usesStaticLibraries; } @@ -1444,6 +1457,12 @@ public class PackageSetting extends SettingBase implements PackageStateInternal return this; } + public PackageSetting setUsesSdkLibrariesOptional(boolean[] usesSdkLibrariesOptional) { + this.usesSdkLibrariesOptional = usesSdkLibrariesOptional; + onChanged(); + return this; + } + public PackageSetting setUsesStaticLibraries(String[] usesStaticLibraries) { this.usesStaticLibraries = usesStaticLibraries; onChanged(); diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java index 53b84e66840b..31a63e07b66c 100644 --- a/services/core/java/com/android/server/pm/ScanPackageUtils.java +++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java @@ -218,7 +218,8 @@ final class ScanPackageUtils { parsedPackage.getLongVersionCode(), pkgFlags, pkgPrivateFlags, user, true /*allowInstall*/, instantApp, virtualPreload, isStoppedSystemApp, UserManagerService.getInstance(), usesSdkLibraries, - parsedPackage.getUsesSdkLibrariesVersionsMajor(), usesStaticLibraries, + parsedPackage.getUsesSdkLibrariesVersionsMajor(), + parsedPackage.getUsesSdkLibrariesOptional(), usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash()); @@ -240,6 +241,7 @@ final class ScanPackageUtils { PackageInfoUtils.appInfoPrivateFlags(parsedPackage, pkgSetting), UserManagerService.getInstance(), usesSdkLibraries, parsedPackage.getUsesSdkLibrariesVersionsMajor(), + parsedPackage.getUsesSdkLibrariesOptional(), usesStaticLibraries, parsedPackage.getUsesStaticLibrariesVersions(), parsedPackage.getMimeGroups(), newDomainSetId, parsedPackage.getTargetSdkVersion(), parsedPackage.getRestrictUpdateHash()); diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java index 2cbf714792f7..75d88da059b5 100644 --- a/services/core/java/com/android/server/pm/Settings.java +++ b/services/core/java/com/android/server/pm/Settings.java @@ -106,6 +106,7 @@ import com.android.server.LocalServices; import com.android.server.backup.PreferredActivityBackupHelper; import com.android.server.pm.Installer.InstallerException; import com.android.server.pm.parsing.PackageInfoUtils; +import com.android.server.pm.parsing.pkg.PackageImpl; import com.android.server.pm.permission.LegacyPermissionDataProvider; import com.android.server.pm.permission.LegacyPermissionSettings; import com.android.server.pm.permission.LegacyPermissionState; @@ -339,6 +340,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile private static final String ATTR_DISTRACTION_FLAGS = "distraction_flags"; private static final String ATTR_SUSPENDED = "suspended"; private static final String ATTR_SUSPENDING_PACKAGE = "suspending-package"; + + private static final String ATTR_OPTIONAL = "optional"; /** * @deprecated Legacy attribute, kept only for upgrading from P builds. */ @@ -942,6 +945,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile ret.setLongVersionCode(p.getVersionCode()); ret.setUsesSdkLibraries(p.getUsesSdkLibraries()); ret.setUsesSdkLibrariesVersionsMajor(p.getUsesSdkLibrariesVersionsMajor()); + ret.setUsesSdkLibrariesOptional(p.getUsesSdkLibrariesOptional()); ret.setUsesStaticLibraries(p.getUsesStaticLibraries()); ret.setUsesStaticLibrariesVersions(p.getUsesStaticLibrariesVersions()); ret.setMimeGroups(p.getMimeGroups()); @@ -1061,9 +1065,9 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile UserHandle installUser, boolean allowInstall, boolean instantApp, boolean virtualPreload, boolean isStoppedSystemApp, UserManagerService userManager, String[] usesSdkLibraries, long[] usesSdkLibrariesVersions, - String[] usesStaticLibraries, long[] usesStaticLibrariesVersions, - Set<String> mimeGroupNames, @NonNull UUID domainSetId, - int targetSdkVersion, byte[] restrictUpdatedHash) { + boolean[] usesSdkLibrariesOptional, String[] usesStaticLibraries, + long[] usesStaticLibrariesVersions, Set<String> mimeGroupNames, + @NonNull UUID domainSetId, int targetSdkVersion, byte[] restrictUpdatedHash) { final PackageSetting pkgSetting; if (originalPkg != null) { if (PackageManagerService.DEBUG_UPGRADE) Log.v(PackageManagerService.TAG, "Package " @@ -1079,6 +1083,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .setLongVersionCode(versionCode) .setUsesSdkLibraries(usesSdkLibraries) .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions) + .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional) .setUsesStaticLibraries(usesStaticLibraries) .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions) // Update new package state. @@ -1096,6 +1101,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile pkgPrivateFlags, domainSetId) .setUsesSdkLibraries(usesSdkLibraries) .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions) + .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional) .setUsesStaticLibraries(usesStaticLibraries) .setUsesStaticLibrariesVersions(usesStaticLibrariesVersions) .setLegacyNativeLibraryPath(legacyNativeLibraryPath) @@ -1218,6 +1224,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile @Nullable String primaryCpuAbi, @Nullable String secondaryCpuAbi, int pkgFlags, int pkgPrivateFlags, @NonNull UserManagerService userManager, @Nullable String[] usesSdkLibraries, @Nullable long[] usesSdkLibrariesVersions, + @Nullable boolean[] usesSdkLibrariesOptional, @Nullable String[] usesStaticLibraries, @Nullable long[] usesStaticLibrariesVersions, @Nullable Set<String> mimeGroupNames, @NonNull UUID domainSetId, int targetSdkVersion, byte[] restrictUpdatedHash) @@ -1277,12 +1284,17 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile .setRestrictUpdateHash(restrictUpdatedHash); // Update SDK library dependencies if needed. if (usesSdkLibraries != null && usesSdkLibrariesVersions != null - && usesSdkLibraries.length == usesSdkLibrariesVersions.length) { + && usesSdkLibrariesOptional != null + && usesSdkLibraries.length == usesSdkLibrariesVersions.length + && usesSdkLibraries.length == usesSdkLibrariesOptional.length) { pkgSetting.setUsesSdkLibraries(usesSdkLibraries) - .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions); + .setUsesSdkLibrariesVersionsMajor(usesSdkLibrariesVersions) + .setUsesSdkLibrariesOptional(usesSdkLibrariesOptional); } else { pkgSetting.setUsesSdkLibraries(null) - .setUsesSdkLibrariesVersionsMajor(null); + .setUsesSdkLibrariesVersionsMajor(null) + .setUsesSdkLibrariesOptional(null); + } // Update static shared library dependencies if needed. @@ -2537,12 +2549,15 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile throws IOException, XmlPullParserException { String libName = parser.getAttributeValue(null, ATTR_NAME); long libVersion = parser.getAttributeLong(null, ATTR_VERSION, -1); + boolean optional = parser.getAttributeBoolean(null, ATTR_OPTIONAL, true); if (libName != null && libVersion >= 0) { outPs.setUsesSdkLibraries(ArrayUtils.appendElement(String.class, outPs.getUsesSdkLibraries(), libName)); outPs.setUsesSdkLibrariesVersionsMajor(ArrayUtils.appendLong( outPs.getUsesSdkLibrariesVersionsMajor(), libVersion)); + outPs.setUsesSdkLibrariesOptional(PackageImpl.appendBoolean( + outPs.getUsesSdkLibrariesOptional(), optional)); } XmlUtils.skipCurrentTag(parser); @@ -2564,7 +2579,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } void writeUsesSdkLibLPw(TypedXmlSerializer serializer, String[] usesSdkLibraries, - long[] usesSdkLibraryVersions) throws IOException { + long[] usesSdkLibraryVersions, boolean[] usesSdkLibrariesOptional) throws IOException { if (ArrayUtils.isEmpty(usesSdkLibraries) || ArrayUtils.isEmpty(usesSdkLibraryVersions) || usesSdkLibraries.length != usesSdkLibraryVersions.length) { return; @@ -2573,9 +2588,11 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile for (int i = 0; i < libCount; i++) { final String libName = usesSdkLibraries[i]; final long libVersion = usesSdkLibraryVersions[i]; + boolean libOptional = usesSdkLibrariesOptional[i]; serializer.startTag(null, TAG_USES_SDK_LIB); serializer.attribute(null, ATTR_NAME, libName); serializer.attributeLong(null, ATTR_VERSION, libVersion); + serializer.attributeBoolean(null, ATTR_OPTIONAL, libOptional); serializer.endTag(null, TAG_USES_SDK_LIB); } } @@ -3106,7 +3123,8 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(), - pkg.getUsesSdkLibrariesVersionsMajor()); + pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional()); + writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(), pkg.getUsesStaticLibrariesVersions()); @@ -3206,7 +3224,7 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile } writeUsesSdkLibLPw(serializer, pkg.getUsesSdkLibraries(), - pkg.getUsesSdkLibrariesVersionsMajor()); + pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesOptional()); writeUsesStaticLibLPw(serializer, pkg.getUsesStaticLibraries(), pkg.getUsesStaticLibrariesVersions()); @@ -5091,12 +5109,14 @@ public final class Settings implements Watchable, Snappable, ResilientAtomicFile List<String> usesSdkLibraries = pkg.getUsesSdkLibraries(); long[] usesSdkLibrariesVersionsMajor = pkg.getUsesSdkLibrariesVersionsMajor(); + boolean[] usesSdkLibrariesOptional = pkg.getUsesSdkLibrariesOptional(); if (usesSdkLibraries.size() > 0) { pw.print(prefix); pw.println(" usesSdkLibraries:"); for (int i = 0, size = usesSdkLibraries.size(); i < size; ++i) { pw.print(prefix); pw.print(" "); pw.print(usesSdkLibraries.get(i)); pw.print(" version:"); - pw.println(usesSdkLibrariesVersionsMajor[i]); + pw.println(usesSdkLibrariesVersionsMajor[i]); pw.print(" optional:"); + pw.println(usesSdkLibrariesOptional[i]); } } diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java index 9384c13e583b..94495bf462f2 100644 --- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java +++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java @@ -16,7 +16,6 @@ package com.android.server.pm; -import static android.content.pm.Flags.sdkLibIndependence; import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY; import static android.content.pm.PackageManager.INSTALL_FAILED_SHARED_LIBRARY_BAD_CERTIFICATE_DIGEST; @@ -28,6 +27,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; +import android.content.pm.Flags; import android.content.pm.PackageManager; import android.content.pm.SharedLibraryInfo; import android.content.pm.Signature; @@ -82,6 +82,8 @@ import java.util.function.BiConsumer; public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable { private static final boolean DEBUG_SHARED_LIBRARIES = false; + private static final String LIBRARY_TYPE_SDK = "sdk"; + /** * Apps targeting Android S and above need to declare dependencies to the public native * shared libraries that are defined by the device maker using {@code uses-native-library} tag @@ -798,8 +800,9 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable // Remove the shared library overlays from its dependent packages. for (int currentUserId : mPm.mUserManager.getUserIds()) { - final List<VersionedPackage> dependents = snapshot.getPackagesUsingSharedLibrary( - libraryInfo, 0, Process.SYSTEM_UID, currentUserId); + var usingSharedLibraryPair = snapshot.getPackagesUsingSharedLibrary(libraryInfo, 0, + Process.SYSTEM_UID, currentUserId); + final List<VersionedPackage> dependents = usingSharedLibraryPair.first; if (dependents == null) { continue; } @@ -921,42 +924,43 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable // duplicates. ArrayList<SharedLibraryInfo> usesLibraryInfos = null; if (!pkg.getUsesLibraries().isEmpty()) { - usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, + usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null, null, pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null, availablePackages, newLibraries); } if (!pkg.getUsesStaticLibraries().isEmpty()) { usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(), pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(), - pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(), - usesLibraryInfos, availablePackages, newLibraries); + null, pkg.getPackageName(), "static shared", true, + pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } if (!pkg.getUsesOptionalLibraries().isEmpty()) { usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null, - pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(), + null, pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES, pkg.getPackageName(), pkg.getTargetSdkVersion())) { if (!pkg.getUsesNativeLibraries().isEmpty()) { usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null, - null, pkg.getPackageName(), "native shared", true, + null, null, pkg.getPackageName(), "native shared", true, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) { usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(), - null, null, pkg.getPackageName(), "native shared", false, + null, null, null, pkg.getPackageName(), "native shared", false, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } } if (!pkg.getUsesSdkLibraries().isEmpty()) { // Allow installation even if sdk-library dependency doesn't exist - boolean required = !sdkLibIndependence(); + boolean required = !Flags.sdkLibIndependence(); usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(), pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(), - pkg.getPackageName(), "sdk", required, pkg.getTargetSdkVersion(), + pkg.getUsesSdkLibrariesOptional(), + pkg.getPackageName(), LIBRARY_TYPE_SDK, required, pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages, newLibraries); } return usesLibraryInfos; @@ -965,6 +969,7 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos( @NonNull List<String> requestedLibraries, @Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests, + @Nullable boolean[] libsOptional, @NonNull String packageName, @NonNull String libraryType, boolean required, int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries, @NonNull final Map<String, AndroidPackage> availablePackages, @@ -981,7 +986,10 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable libName, libVersion, mSharedLibraries, newLibraries); } if (libraryInfo == null) { - if (required) { + // Only allow app be installed if the app specifies the sdk-library dependency is + // optional + if (required || (LIBRARY_TYPE_SDK.equals(libraryType) && (libsOptional != null + && !libsOptional[i]))) { throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY, "Package " + packageName + " requires unavailable " + libraryType + " library " + libName + "; failing!"); diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java index 71f6c0d507d4..fe8c12c8e232 100644 --- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java +++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java @@ -33,7 +33,6 @@ import android.os.PersistableBundle; import android.os.Process; import android.os.UserHandle; import android.os.UserManager; -import android.provider.DeviceConfig; import android.util.ArrayMap; import android.util.ArraySet; import android.util.IntArray; @@ -504,10 +503,6 @@ public final class SuspendPackageHelper { final String requiredPermissionControllerPackage = getKnownPackageName(snapshot, KnownPackages.PACKAGE_PERMISSION_CONTROLLER, userId); - final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class); - final boolean isSystemExemptFlagEnabled = DeviceConfig.getBoolean( - DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE, - SYSTEM_EXEMPT_FROM_SUSPENSION, /* defaultValue= */ true); for (int i = 0; i < packageNames.length; i++) { canSuspend[i] = false; final String packageName = packageNames[i]; @@ -581,9 +576,7 @@ public final class SuspendPackageHelper { + pkg.getStaticSharedLibraryName()); continue; } - if (isSystemExemptFlagEnabled && appOpsManager.checkOpNoThrow( - AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName) - == AppOpsManager.MODE_ALLOWED) { + if (exemptFromSuspensionByAppOp(uid, packageName)) { Slog.w(TAG, "Cannot suspend package \"" + packageName + "\": has OP_SYSTEM_EXEMPT_FROM_SUSPENSION set"); continue; @@ -601,6 +594,13 @@ public final class SuspendPackageHelper { return canSuspend; } + private boolean exemptFromSuspensionByAppOp(int uid, String packageName) { + final AppOpsManager appOpsManager = mInjector.getSystemService(AppOpsManager.class); + return appOpsManager.checkOpNoThrow( + AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION, uid, packageName) + == AppOpsManager.MODE_ALLOWED; + } + /** * Suspends packages on behalf of an admin. * diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index b53a21c9aa1c..a7b52f4e7a58 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1519,7 +1519,7 @@ public class UserManagerService extends IUserManager.Stub { try { if (enableQuietMode) { - ActivityManager.getService().stopUser(userId, /* force= */ true, null); + stopUserForQuietMode(userId); LocalServices.getService(ActivityManagerInternal.class) .killForegroundAppsForUser(userId); } else { @@ -1547,6 +1547,18 @@ public class UserManagerService extends IUserManager.Stub { } } + private void stopUserForQuietMode(int userId) throws RemoteException { + if (android.os.Flags.allowPrivateProfile() + && android.multiuser.Flags.enableBiometricsToUnlockPrivateSpace()) { + // Allow delayed locking since some profile types want to be able to unlock again via + // biometrics. + ActivityManager.getService() + .stopUserWithDelayedLocking(userId, /* force= */ true, null); + return; + } + ActivityManager.getService().stopUser(userId, /* force= */ true, null); + } + private void logQuietModeEnabled(@UserIdInt int userId, boolean enableQuietMode, @Nullable String callingPackage) { Slogf.i(LOG_TAG, diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java index 4ef8cb780734..7386301bdd6e 100644 --- a/services/core/java/com/android/server/pm/UserTypeFactory.java +++ b/services/core/java/com/android/server/pm/UserTypeFactory.java @@ -160,7 +160,9 @@ public final class UserTypeFactory { UserProperties.SHOW_IN_SHARING_SURFACES_WITH_PARENT) .setMediaSharedWithParent(true) .setCredentialShareableWithParent(true) - .setDeleteAppWithParent(true)); + .setDeleteAppWithParent(true) + .setCrossProfileContentSharingStrategy(UserProperties + .CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)); } /** @@ -309,6 +311,7 @@ public final class UserTypeFactory { .setStartWithParent(true) .setCredentialShareableWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(true) + .setAllowStoppingUserWithDelayedLocking(true) .setMediaSharedWithParent(false) .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE) .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE) @@ -318,7 +321,9 @@ public final class UserTypeFactory { UserProperties.SHOW_IN_SHARING_SURFACES_SEPARATE) .setCrossProfileIntentFilterAccessControl( UserProperties.CROSS_PROFILE_INTENT_FILTER_ACCESS_LEVEL_SYSTEM) - .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT)); + .setInheritDevicePolicy(UserProperties.INHERIT_DEVICE_POLICY_FROM_PARENT) + .setCrossProfileContentSharingStrategy( + UserProperties.CROSS_PROFILE_CONTENT_SHARING_DELEGATE_FROM_PARENT)); } /** diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java index d64201893400..b23dbee5e973 100644 --- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java +++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java @@ -266,17 +266,20 @@ public class PackageInfoUtils { if ((flags & PackageManager.GET_ACTIVITIES) != 0) { final int N = pkg.getActivities().size(); if (N > 0) { + // Allow to match activities of quarantined packages. + long aflags = flags | PackageManager.MATCH_QUARANTINED_COMPONENTS; + int num = 0; final ActivityInfo[] res = new ActivityInfo[N]; for (int i = 0; i < N; i++) { final ParsedActivity a = pkg.getActivities().get(i); if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a, - flags)) { + aflags)) { if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals( a.getName())) { continue; } - res[num++] = generateActivityInfo(pkg, a, flags, state, + res[num++] = generateActivityInfo(pkg, a, aflags, state, applicationInfo, userId, pkgSetting); } } diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java index 85d95eab2958..da58d47edbfe 100644 --- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java +++ b/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java @@ -267,6 +267,8 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Nullable private String[][] usesSdkLibrariesCertDigests; @Nullable + private boolean[] usesSdkLibrariesOptional; + @Nullable @DataClass.ParcelWith(ForInternedString.class) private String sharedUserId; private int sharedUserLabel; @@ -718,16 +720,33 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Override public PackageImpl addUsesSdkLibrary(String libraryName, long versionMajor, - String[] certSha256Digests) { + String[] certSha256Digests, boolean usesSdkLibrariesOptional) { this.usesSdkLibraries = CollectionUtils.add(this.usesSdkLibraries, TextUtils.safeIntern(libraryName)); this.usesSdkLibrariesVersionsMajor = ArrayUtils.appendLong( this.usesSdkLibrariesVersionsMajor, versionMajor, true); this.usesSdkLibrariesCertDigests = ArrayUtils.appendElement(String[].class, this.usesSdkLibrariesCertDigests, certSha256Digests, true); + this.usesSdkLibrariesOptional = appendBoolean(this.usesSdkLibrariesOptional, + usesSdkLibrariesOptional); return this; } + /** + * Adds value to given array if not already present, providing set-like + * behavior. + */ + public static boolean[] appendBoolean(@Nullable boolean[] cur, boolean val) { + if (cur == null) { + return new boolean[] { val }; + } + final int N = cur.length; + boolean[] ret = new boolean[N + 1]; + System.arraycopy(cur, 0, ret, 0, N); + ret[N] = val; + return ret; + } + @Override public PackageImpl addUsesStaticLibrary(String libraryName, long version, String[] certSha256Digests) { @@ -1468,6 +1487,12 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, @Override public long[] getUsesSdkLibrariesVersionsMajor() { return usesSdkLibrariesVersionsMajor; } + @Nullable + @Override + public boolean[] getUsesSdkLibrariesOptional() { + return usesSdkLibrariesOptional; + } + @NonNull @Override public List<String> getUsesStaticLibraries() { @@ -3126,6 +3151,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, dest.writeStringArray(this.usesSdkLibrariesCertDigests[index]); } } + dest.writeBooleanArray(this.usesSdkLibrariesOptional); sForInternedString.parcel(this.sharedUserId, dest, flags); dest.writeInt(this.sharedUserLabel); @@ -3278,6 +3304,7 @@ public class PackageImpl implements ParsedPackage, AndroidPackageInternal, } } } + this.usesSdkLibrariesOptional = in.createBooleanArray(); this.sharedUserId = sForInternedString.unparcel(in); this.sharedUserLabel = in.readInt(); diff --git a/services/core/java/com/android/server/pm/pkg/PackageState.java b/services/core/java/com/android/server/pm/pkg/PackageState.java index 10b59c7230f6..a7ae4ebcb2eb 100644 --- a/services/core/java/com/android/server/pm/pkg/PackageState.java +++ b/services/core/java/com/android/server/pm/pkg/PackageState.java @@ -322,6 +322,14 @@ public interface PackageState { long[] getUsesSdkLibrariesVersionsMajor(); /** + * @see R.styleable#AndroidManifestUsesSdkLibrary_optional + * @hide + */ + @Immutable.Ignore + @NonNull + boolean[] getUsesSdkLibrariesOptional(); + + /** * @see R.styleable#AndroidManifestUsesStaticLibrary * @hide */ diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java index 722350a0d7fb..aa0fb2734382 100644 --- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java +++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java @@ -2598,6 +2598,8 @@ public class ParsingPackageUtils { R.styleable.AndroidManifestUsesSdkLibrary_versionMajor, -1); String certSha256Digest = sa.getNonResourceString(R.styleable .AndroidManifestUsesSdkLibrary_certDigest); + boolean optional = + sa.getBoolean(R.styleable.AndroidManifestUsesSdkLibrary_optional, false); // Since an APK providing a static shared lib can only provide the lib - fail if // malformed @@ -2641,7 +2643,8 @@ public class ParsingPackageUtils { System.arraycopy(additionalCertSha256Digests, 0, certSha256Digests, 1, additionalCertSha256Digests.length); - return input.success(pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests)); + return input.success( + pkg.addUsesSdkLibrary(lname, versionMajor, certSha256Digests, optional)); } finally { sa.recycle(); } diff --git a/services/core/java/com/android/server/power/ThermalManagerService.java b/services/core/java/com/android/server/power/ThermalManagerService.java index 99653ae1cd72..24d7acd772c1 100644 --- a/services/core/java/com/android/server/power/ThermalManagerService.java +++ b/services/core/java/com/android/server/power/ThermalManagerService.java @@ -759,6 +759,36 @@ public class ThermalManagerService extends SystemService { case "NPU": type = Temperature.TYPE_NPU; break; + case "TPU": + type = Temperature.TYPE_TPU; + break; + case "DISPLAY": + type = Temperature.TYPE_DISPLAY; + break; + case "MODEM": + type = Temperature.TYPE_MODEM; + break; + case "SOC": + type = Temperature.TYPE_SOC; + break; + case "WIFI": + type = Temperature.TYPE_WIFI; + break; + case "CAMERA": + type = Temperature.TYPE_CAMERA; + break; + case "FLASHLIGHT": + type = Temperature.TYPE_FLASHLIGHT; + break; + case "SPEAKER": + type = Temperature.TYPE_SPEAKER; + break; + case "AMBIENT": + type = Temperature.TYPE_AMBIENT; + break; + case "POGO": + type = Temperature.TYPE_POGO; + break; default: pw.println("Invalid temperature type: " + typeName); return -1; diff --git a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java index 4442845f83b2..1af127175f80 100644 --- a/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java +++ b/services/core/java/com/android/server/power/stats/CpuPowerStatsCollector.java @@ -78,7 +78,7 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private final SparseArray<UidStats> mUidStats = new SparseArray<>(); private boolean mIsPerUidTimeInStateSupported; private PowerStatsInternal mPowerStatsInternal; - private int[] mCpuEnergyConsumerIds; + private int[] mCpuEnergyConsumerIds = new int[0]; private PowerStats.Descriptor mPowerStatsDescriptor; // Reusable instance private PowerStats mCpuPowerStats; @@ -286,8 +286,6 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { if (mPowerStatsInternal != null) { readCpuEnergyConsumerIds(); - } else { - mCpuEnergyConsumerIds = new int[0]; } int cpuScalingStepCount = mCpuScalingPolicies.getScalingStepCount(); @@ -320,7 +318,6 @@ public class CpuPowerStatsCollector extends PowerStatsCollector { private void readCpuEnergyConsumerIds() { EnergyConsumer[] energyConsumerInfo = mPowerStatsInternal.getEnergyConsumerInfo(); if (energyConsumerInfo == null) { - mCpuEnergyConsumerIds = new int[0]; return; } diff --git a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java index 0386e66aaf3d..b8850afdf3ad 100644 --- a/services/core/java/com/android/server/utils/WatchedSparseSetArray.java +++ b/services/core/java/com/android/server/utils/WatchedSparseSetArray.java @@ -142,6 +142,20 @@ public class WatchedSparseSetArray<T> extends WatchableImpl implements Snappable return (T) mStorage.valueAt(intIndex, valueIndex); } + /** + * Copy from another SparseSetArray. + */ + public void copyFrom(@NonNull SparseSetArray<T> c) { + clear(); + final int end = c.size(); + for (int i = 0; i < end; i++) { + final int key = c.keyAt(i); + final ArraySet<T> set = c.get(key); + mStorage.addAll(key, set); + } + onChanged(); + } + @NonNull @Override public Object snapshot() { diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index e5794a1f7efd..dfb2a5fcf9fa 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -1121,6 +1121,7 @@ class ActivityStarter { callerApp, request.originatingPendingIntent, request.forcedBalByPiSender, + resultRecord, intent, checkedOptions); request.logMessage.append(" (").append(balVerdict).append(")"); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index a04513f407b8..73edb4bed6ca 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -103,6 +103,7 @@ import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_FIRST_ORD import static com.android.server.wm.ActivityInterceptorCallback.SYSTEM_LAST_ORDERED_ID; import static com.android.server.wm.ActivityRecord.State.PAUSING; import static com.android.server.wm.ActivityRecord.State.RESUMED; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS; import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ALL; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_ROOT_TASK; import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_SWITCH; @@ -118,6 +119,7 @@ import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME; import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP; import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS; import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS; +import static com.android.server.wm.BackgroundActivityStartController.BalVerdict; import static com.android.server.wm.LockTaskController.LOCK_TASK_AUTH_DONT_LOCK; import static com.android.server.wm.RecentsAnimationController.REORDER_KEEP_IN_PLACE; import static com.android.server.wm.RecentsAnimationController.REORDER_MOVE_TO_ORIGINAL_POSITION; @@ -2242,7 +2244,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } final BackgroundActivityStartController balController = mTaskSupervisor.getBackgroundActivityLaunchController(); - if (balController.shouldAbortBackgroundActivityStart( + final BalVerdict balVerdict = balController.checkBackgroundActivityStart( callingUid, callingPid, callingPackage, @@ -2252,10 +2254,14 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { null, BackgroundStartPrivileges.NONE, null, - null)) { - if (!isBackgroundActivityStartsEnabled()) { - return; - } + null, + null); + if (balVerdict.blocks() && !isBackgroundActivityStartsEnabled()) { + Slog.w(TAG, "moveTaskToFront blocked: " + balVerdict); + return; + } + if (DEBUG_ACTIVITY_STARTS) { + Slog.d(TAG, "moveTaskToFront allowed: " + balVerdict); } try { final Task task = mRootWindowContainer.anyTaskForId(taskId); diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 761b0a8f0b39..50de0b08f3b0 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -16,6 +16,8 @@ package com.android.server.wm; +import static com.android.server.wm.ActivityTaskManagerDebugConfig.DEBUG_ACTIVITY_STARTS; +import static com.android.server.wm.BackgroundActivityStartController.BalVerdict; import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS; import static com.android.server.wm.RootWindowContainer.MATCH_ATTACHED_TASK_OR_RECENT_TASKS; @@ -31,6 +33,7 @@ import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.UserHandle; +import android.util.Slog; /** * An implementation of IAppTask, that allows an app to manage its own tasks via @@ -123,7 +126,7 @@ class AppTaskImpl extends IAppTask.Stub { } final BackgroundActivityStartController balController = mService.mTaskSupervisor.getBackgroundActivityLaunchController(); - if (balController.shouldAbortBackgroundActivityStart( + BalVerdict balVerdict = balController.checkBackgroundActivityStart( callingUid, callingPid, callingPackage, @@ -133,10 +136,14 @@ class AppTaskImpl extends IAppTask.Stub { null, BackgroundStartPrivileges.NONE, null, - null)) { - if (!mService.isBackgroundActivityStartsEnabled()) { - return; - } + null, + null); + if (balVerdict.blocks() && !mService.isBackgroundActivityStartsEnabled()) { + Slog.w(TAG, "moveTaskToFront blocked: : " + balVerdict); + return; + } + if (DEBUG_ACTIVITY_STARTS) { + Slog.d(TAG, "moveTaskToFront allowed: " + balVerdict); } } mService.mTaskSupervisor.startActivityFromRecents(callingPid, callingUid, mTaskId, diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index 07dac5481b79..92665af1075b 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -205,27 +205,6 @@ public class BackgroundActivityStartController { return activity != null && packageName.equals(activity.getPackageName()); } - /** - * @see #checkBackgroundActivityStart(int, int, String, int, int, WindowProcessController, - * PendingIntentRecord, BackgroundStartPrivileges, Intent, ActivityOptions) - */ - boolean shouldAbortBackgroundActivityStart( - int callingUid, - int callingPid, - final String callingPackage, - int realCallingUid, - int realCallingPid, - WindowProcessController callerApp, - PendingIntentRecord originatingPendingIntent, - BackgroundStartPrivileges forcedBalByPiSender, - Intent intent, - ActivityOptions checkedOptions) { - return checkBackgroundActivityStart(callingUid, callingPid, callingPackage, - realCallingUid, realCallingPid, - callerApp, originatingPendingIntent, - forcedBalByPiSender, intent, checkedOptions).blocks(); - } - private class BalState { private final String mCallingPackage; @@ -255,6 +234,7 @@ public class BackgroundActivityStartController { WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, BackgroundStartPrivileges forcedBalByPiSender, + ActivityRecord resultRecord, Intent intent, ActivityOptions checkedOptions) { this.mCallingPackage = callingPackage; @@ -267,7 +247,9 @@ public class BackgroundActivityStartController { mOriginatingPendingIntent = originatingPendingIntent; mIntent = intent; mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid); - if (originatingPendingIntent == null) { + if (originatingPendingIntent == null // not a PendingIntent + || resultRecord != null // sent for result + ) { // grant BAL privileges unless explicitly opted out mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator = checkedOptions.getPendingIntentCreatorBackgroundActivityStartMode() @@ -535,6 +517,7 @@ public class BackgroundActivityStartController { * @param forcedBalByPiSender If set to allow, the * PendingIntent's sender will try to force allow background activity starts. * This is only possible if the sender of the PendingIntent is a system process. + * @param resultRecord If not null, this indicates that the caller expects a result. * @param intent Intent that should be started. * @param checkedOptions ActivityOptions to allow specific opt-ins/opt outs. * @@ -550,6 +533,7 @@ public class BackgroundActivityStartController { WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, BackgroundStartPrivileges forcedBalByPiSender, + ActivityRecord resultRecord, Intent intent, ActivityOptions checkedOptions) { @@ -560,7 +544,7 @@ public class BackgroundActivityStartController { BalState state = new BalState(callingUid, callingPid, callingPackage, realCallingUid, realCallingPid, callerApp, originatingPendingIntent, - forcedBalByPiSender, intent, checkedOptions); + forcedBalByPiSender, resultRecord, intent, checkedOptions); // In the case of an SDK sandbox calling uid, check if the corresponding app uid has a // visible window. diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java new file mode 100644 index 000000000000..975fdc0ade5d --- /dev/null +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -0,0 +1,345 @@ +/* + * 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.wm; + +import static android.view.WindowManager.TRANSIT_CHANGE; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS; +import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; +import static com.android.server.wm.utils.DisplayInfoOverrides.WM_OVERRIDE_FIELDS; +import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.graphics.Rect; +import android.view.DisplayInfo; +import android.window.DisplayAreaInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.display.BrightnessSynchronizer; +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater; + +import java.util.Arrays; +import java.util.Objects; + +/** + * A DisplayUpdater that could defer and queue display updates coming from DisplayManager to + * WindowManager. It allows to defer pending display updates if WindowManager is currently not + * ready to apply them. + * For example, this might happen if there is a Shell transition running and physical display + * changed. We can't immediately apply the display updates because we want to start a separate + * display change transition. In this case, we will queue all display updates until the current + * transition's collection finishes and then apply them afterwards. + */ +public class DeferredDisplayUpdater implements DisplayUpdater { + + /** + * List of fields that could be deferred before applying to DisplayContent. + * This should be kept in sync with {@link DeferredDisplayUpdater#calculateDisplayInfoDiff} + */ + @VisibleForTesting + static final DisplayInfoFieldsUpdater DEFERRABLE_FIELDS = (out, override) -> { + // Treat unique id and address change as WM-specific display change as we re-query display + // settings and parameters based on it which could cause window changes + out.uniqueId = override.uniqueId; + out.address = override.address; + + // Also apply WM-override fields, since they might produce differences in window hierarchy + WM_OVERRIDE_FIELDS.setFields(out, override); + }; + + private final DisplayContent mDisplayContent; + + @NonNull + private final DisplayInfo mNonOverrideDisplayInfo = new DisplayInfo(); + + /** + * The last known display parameters from DisplayManager, some WM-specific fields in this object + * might not be applied to the DisplayContent yet + */ + @Nullable + private DisplayInfo mLastDisplayInfo; + + /** + * The last DisplayInfo that was applied to DisplayContent, only WM-specific parameters must be + * used from this object. This object is used to store old values of DisplayInfo while these + * fields are pending to be applied to DisplayContent. + */ + @Nullable + private DisplayInfo mLastWmDisplayInfo; + + @NonNull + private final DisplayInfo mOutputDisplayInfo = new DisplayInfo(); + + public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) { + mDisplayContent = displayContent; + mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); + } + + /** + * Reads the latest display parameters from the display manager and returns them in a callback. + * If there are pending display updates, it will wait for them to finish first and only then it + * will call the callback with the latest display parameters. + * + * @param finishCallback is called when all pending display updates are finished + */ + @Override + public void updateDisplayInfo(@NonNull Runnable finishCallback) { + // Get the latest display parameters from the DisplayManager + final DisplayInfo displayInfo = getCurrentDisplayInfo(); + + final int displayInfoDiff = calculateDisplayInfoDiff(mLastDisplayInfo, displayInfo); + final boolean physicalDisplayUpdated = isPhysicalDisplayUpdated(mLastDisplayInfo, + displayInfo); + + mLastDisplayInfo = displayInfo; + + // Apply whole display info immediately as is if either: + // * it is the first display update + // * shell transitions are disabled or temporary unavailable + if (displayInfoDiff == DIFF_EVERYTHING + || !mDisplayContent.mTransitionController.isShellTransitionsEnabled()) { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: applying DisplayInfo immediately"); + + mLastWmDisplayInfo = displayInfo; + applyLatestDisplayInfo(); + finishCallback.run(); + return; + } + + // If there are non WM-specific display info changes, apply only these fields immediately + if ((displayInfoDiff & DIFF_NOT_WM_DEFERRABLE) > 0) { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: partially applying DisplayInfo immediately"); + applyLatestDisplayInfo(); + } + + // If there are WM-specific display info changes, apply them through a Shell transition + if ((displayInfoDiff & DIFF_WM_DEFERRABLE) > 0) { + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: deferring DisplayInfo update"); + + requestDisplayChangeTransition(physicalDisplayUpdated, () -> { + // Apply deferrable fields to DisplayContent only when the transition + // starts collecting, non-deferrable fields are ignored in mLastWmDisplayInfo + mLastWmDisplayInfo = displayInfo; + applyLatestDisplayInfo(); + finishCallback.run(); + }); + } else { + // There are no WM-specific updates, so we can immediately notify that all display + // info changes are applied + finishCallback.run(); + } + } + + /** + * Requests a display change Shell transition + * + * @param physicalDisplayUpdated if true also starts remote display change + * @param onStartCollect called when the Shell transition starts collecting + */ + private void requestDisplayChangeTransition(boolean physicalDisplayUpdated, + @NonNull Runnable onStartCollect) { + + final Transition transition = new Transition(TRANSIT_CHANGE, /* flags= */ 0, + mDisplayContent.mTransitionController, + mDisplayContent.mTransitionController.mSyncEngine); + + mDisplayContent.mAtmService.startPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); + + mDisplayContent.mTransitionController.startCollectOrQueue(transition, deferred -> { + final Rect startBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth, + mDisplayContent.mInitialDisplayHeight); + final int fromRotation = mDisplayContent.getRotation(); + + onStartCollect.run(); + + ProtoLog.d(WM_DEBUG_WINDOW_TRANSITIONS, + "DeferredDisplayUpdater: applied DisplayInfo after deferring"); + + if (physicalDisplayUpdated) { + onDisplayUpdated(transition, fromRotation, startBounds); + } else { + transition.setAllReady(); + } + }); + } + + /** + * Applies current DisplayInfo to DisplayContent, DisplayContent is merged from two parts: + * - non-deferrable fields are set from the most recent values received from DisplayManager + * (uses {@link mLastDisplayInfo} field) + * - deferrable fields are set from the latest values that we could apply to WM + * (uses {@link mLastWmDisplayInfo} field) + */ + private void applyLatestDisplayInfo() { + copyDisplayInfoFields(mOutputDisplayInfo, /* base= */ mLastDisplayInfo, + /* override= */ mLastWmDisplayInfo, /* fields= */ DEFERRABLE_FIELDS); + mDisplayContent.onDisplayInfoUpdated(mOutputDisplayInfo); + } + + @NonNull + private DisplayInfo getCurrentDisplayInfo() { + mDisplayContent.mWmService.mDisplayManagerInternal.getNonOverrideDisplayInfo( + mDisplayContent.mDisplayId, mNonOverrideDisplayInfo); + return new DisplayInfo(mNonOverrideDisplayInfo); + } + + /** + * Called when physical display is updated, this could happen e.g. on foldable + * devices when the physical underlying display is replaced. This method should be called + * when the new display info is already applied to the WM hierarchy. + * + * @param fromRotation rotation before the display change + * @param startBounds display bounds before the display change + */ + private void onDisplayUpdated(@NonNull Transition transition, int fromRotation, + @NonNull Rect startBounds) { + final Rect endBounds = new Rect(0, 0, mDisplayContent.mInitialDisplayWidth, + mDisplayContent.mInitialDisplayHeight); + final int toRotation = mDisplayContent.getRotation(); + + final TransitionRequestInfo.DisplayChange displayChange = + new TransitionRequestInfo.DisplayChange(mDisplayContent.getDisplayId()); + displayChange.setStartAbsBounds(startBounds); + displayChange.setEndAbsBounds(endBounds); + displayChange.setStartRotation(fromRotation); + displayChange.setEndRotation(toRotation); + displayChange.setPhysicalDisplayChanged(true); + + mDisplayContent.mTransitionController.requestStartTransition(transition, + /* startTask= */ null, /* remoteTransition= */ null, displayChange); + + final DisplayAreaInfo newDisplayAreaInfo = mDisplayContent.getDisplayAreaInfo(); + + final boolean startedRemoteChange = mDisplayContent.mRemoteDisplayChangeController + .performRemoteDisplayChange(fromRotation, toRotation, newDisplayAreaInfo, + transaction -> finishDisplayUpdate(transaction, transition)); + + if (!startedRemoteChange) { + finishDisplayUpdate(/* wct= */ null, transition); + } + } + + private void finishDisplayUpdate(@Nullable WindowContainerTransaction wct, + @NonNull Transition transition) { + if (wct != null) { + mDisplayContent.mAtmService.mWindowOrganizerController.applyTransaction( + wct); + } + transition.setAllReady(); + } + + private boolean isPhysicalDisplayUpdated(@Nullable DisplayInfo first, + @Nullable DisplayInfo second) { + if (first == null || second == null) return true; + return !Objects.equals(first.uniqueId, second.uniqueId); + } + + /** + * Diff result: fields are the same + */ + static final int DIFF_NONE = 0; + + /** + * Diff result: fields that could be deferred in WM are different + */ + static final int DIFF_WM_DEFERRABLE = 1 << 0; + + /** + * Diff result: fields that could not be deferred in WM are different + */ + static final int DIFF_NOT_WM_DEFERRABLE = 1 << 1; + + /** + * Diff result: everything is different + */ + static final int DIFF_EVERYTHING = 0XFFFFFFFF; + + @VisibleForTesting + static int calculateDisplayInfoDiff(@Nullable DisplayInfo first, @Nullable DisplayInfo second) { + int diff = DIFF_NONE; + + if (Objects.equals(first, second)) return diff; + if (first == null || second == null) return DIFF_EVERYTHING; + + if (first.layerStack != second.layerStack + || first.flags != second.flags + || first.type != second.type + || first.displayId != second.displayId + || first.displayGroupId != second.displayGroupId + || !Objects.equals(first.deviceProductInfo, second.deviceProductInfo) + || first.modeId != second.modeId + || first.renderFrameRate != second.renderFrameRate + || first.defaultModeId != second.defaultModeId + || first.userPreferredModeId != second.userPreferredModeId + || !Arrays.equals(first.supportedModes, second.supportedModes) + || first.colorMode != second.colorMode + || !Arrays.equals(first.supportedColorModes, second.supportedColorModes) + || !Objects.equals(first.hdrCapabilities, second.hdrCapabilities) + || !Arrays.equals(first.userDisabledHdrTypes, second.userDisabledHdrTypes) + || first.minimalPostProcessingSupported != second.minimalPostProcessingSupported + || first.appVsyncOffsetNanos != second.appVsyncOffsetNanos + || first.presentationDeadlineNanos != second.presentationDeadlineNanos + || first.state != second.state + || first.committedState != second.committedState + || first.ownerUid != second.ownerUid + || !Objects.equals(first.ownerPackageName, second.ownerPackageName) + || first.removeMode != second.removeMode + || first.getRefreshRate() != second.getRefreshRate() + || first.brightnessMinimum != second.brightnessMinimum + || first.brightnessMaximum != second.brightnessMaximum + || first.brightnessDefault != second.brightnessDefault + || first.installOrientation != second.installOrientation + || !Objects.equals(first.layoutLimitedRefreshRate, second.layoutLimitedRefreshRate) + || !BrightnessSynchronizer.floatEquals(first.hdrSdrRatio, second.hdrSdrRatio) + || !first.thermalRefreshRateThrottling.contentEquals( + second.thermalRefreshRateThrottling) + || !Objects.equals(first.thermalBrightnessThrottlingDataId, + second.thermalBrightnessThrottlingDataId)) { + diff |= DIFF_NOT_WM_DEFERRABLE; + } + + if (first.appWidth != second.appWidth + || first.appHeight != second.appHeight + || first.smallestNominalAppWidth != second.smallestNominalAppWidth + || first.smallestNominalAppHeight != second.smallestNominalAppHeight + || first.largestNominalAppWidth != second.largestNominalAppWidth + || first.largestNominalAppHeight != second.largestNominalAppHeight + || first.logicalWidth != second.logicalWidth + || first.logicalHeight != second.logicalHeight + || first.physicalXDpi != second.physicalXDpi + || first.physicalYDpi != second.physicalYDpi + || first.rotation != second.rotation + || !Objects.equals(first.displayCutout, second.displayCutout) + || first.logicalDensityDpi != second.logicalDensityDpi + || !Objects.equals(first.roundedCorners, second.roundedCorners) + || !Objects.equals(first.displayShape, second.displayShape) + || !Objects.equals(first.uniqueId, second.uniqueId) + || !Objects.equals(first.address, second.address) + ) { + diff |= DIFF_WM_DEFERRABLE; + } + + return diff; + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a840973a941e..4cc1c0d4df9e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -276,6 +276,7 @@ import java.util.Objects; import java.util.Set; import java.util.function.Consumer; import java.util.function.Predicate; +import static com.android.window.flags.Flags.deferDisplayUpdates; /** * Utility class for keeping track of the WindowStates and other pertinent contents of a @@ -1158,7 +1159,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWallpaperController.resetLargestDisplay(display); display.getDisplayInfo(mDisplayInfo); display.getMetrics(mDisplayMetrics); - mDisplayUpdater = new ImmediateDisplayUpdater(this); + if (deferDisplayUpdates()) { + mDisplayUpdater = new DeferredDisplayUpdater(this); + } else { + mDisplayUpdater = new ImmediateDisplayUpdater(this); + } mSystemGestureExclusionLimit = mWmService.mConstants.mSystemGestureExclusionLimitDp * mDisplayMetrics.densityDpi / DENSITY_DEFAULT; isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY; diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index b862d7c28b52..460a68f48ff6 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -39,6 +39,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCRE import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_IMMERSIVE_CONFIRMATION_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP; @@ -272,6 +273,8 @@ public class DisplayPolicy { private @InsetsType int mForciblyShownTypes; + private boolean mImeInsetsConsumed; + private boolean mIsImmersiveMode; // The windows we were told about in focusChanged. @@ -1420,6 +1423,7 @@ public class DisplayPolicy { mShowingDream = false; mIsFreeformWindowOverlappingWithNavBar = false; mForciblyShownTypes = 0; + mImeInsetsConsumed = false; } /** @@ -1481,6 +1485,17 @@ public class DisplayPolicy { mForciblyShownTypes |= win.mAttrs.forciblyShownTypes; } + if (win.mImeInsetsConsumed != mImeInsetsConsumed) { + win.mImeInsetsConsumed = mImeInsetsConsumed; + final WindowState imeWin = mDisplayContent.mInputMethodWindow; + if (win.isReadyToDispatchInsetsState() && imeWin != null && imeWin.isVisible()) { + win.notifyInsetsChanged(); + } + } + if ((attrs.privateFlags & PRIVATE_FLAG_CONSUME_IME_INSETS) != 0 && win.isVisible()) { + mImeInsetsConsumed = true; + } + if (!affectsSystemUi) { return; } @@ -2828,6 +2843,7 @@ public class DisplayPolicy { } } pw.print(prefix); pw.print("mTopIsFullscreen="); pw.println(mTopIsFullscreen); + pw.print(prefix); pw.print("mImeInsetsConsumed="); pw.println(mImeInsetsConsumed); pw.print(prefix); pw.print("mForceShowNavigationBarEnabled="); pw.print(mForceShowNavigationBarEnabled); pw.print(" mAllowLockscreenWhenOn="); pw.println(mAllowLockscreenWhenOn); diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index c089d107d07d..781567990235 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -391,13 +391,26 @@ class InsetsPolicy { if (originalImeSource != null) { final boolean imeVisibility = w.isRequestedVisible(Type.ime()); - final InsetsState state = copyState ? new InsetsState(originalState) + final InsetsState state = copyState + ? new InsetsState(originalState) : originalState; final InsetsSource imeSource = new InsetsSource(originalImeSource); imeSource.setVisible(imeVisibility); state.addSource(imeSource); return state; } + } else if (w.mImeInsetsConsumed) { + // Set the IME source (if there is one) to be invisible if it has been consumed. + final InsetsSource originalImeSource = originalState.peekSource(ID_IME); + if (originalImeSource != null && originalImeSource.isVisible()) { + final InsetsState state = copyState + ? new InsetsState(originalState) + : originalState; + final InsetsSource imeSource = new InsetsSource(originalImeSource); + imeSource.setVisible(false); + state.addSource(imeSource); + return state; + } } return originalState; } diff --git a/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java new file mode 100644 index 000000000000..1688a1a91114 --- /dev/null +++ b/services/core/java/com/android/server/wm/TrustedPresentationListenerController.java @@ -0,0 +1,448 @@ +/* + * 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.server.wm; + +import static android.graphics.Matrix.MSCALE_X; +import static android.graphics.Matrix.MSCALE_Y; +import static android.graphics.Matrix.MSKEW_X; +import static android.graphics.Matrix.MSKEW_Y; + +import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_TPL; + +import android.graphics.Matrix; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Region; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.IntArray; +import android.util.Pair; +import android.util.Size; +import android.view.InputWindowHandle; +import android.window.ITrustedPresentationListener; +import android.window.TrustedPresentationThresholds; +import android.window.WindowInfosListener; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.server.wm.utils.RegionUtils; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Optional; + +/** + * Class to handle TrustedPresentationListener registrations in a thread safe manner. This class + * also takes care of cleaning up listeners when the remote process dies. + */ +public class TrustedPresentationListenerController { + + // Should only be accessed by the posting to the handler + private class Listeners { + private final class ListenerDeathRecipient implements IBinder.DeathRecipient { + IBinder mListenerBinder; + int mInstances; + + ListenerDeathRecipient(IBinder listenerBinder) { + mListenerBinder = listenerBinder; + mInstances = 0; + try { + mListenerBinder.linkToDeath(this, 0); + } catch (RemoteException ignore) { + } + } + + void addInstance() { + mInstances++; + } + + // return true if there are no instances alive + boolean removeInstance() { + mInstances--; + if (mInstances > 0) { + return false; + } + mListenerBinder.unlinkToDeath(this, 0); + return true; + } + + public void binderDied() { + mHandler.post(() -> { + mUniqueListeners.remove(mListenerBinder); + removeListeners(mListenerBinder, Optional.empty()); + }); + } + } + + // tracks binder deaths for cleanup + ArrayMap<IBinder, ListenerDeathRecipient> mUniqueListeners = new ArrayMap<>(); + ArrayMap<IBinder /*window*/, ArrayList<TrustedPresentationInfo>> mWindowToListeners = + new ArrayMap<>(); + + void register(IBinder window, ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + var listenersForWindow = mWindowToListeners.computeIfAbsent(window, + iBinder -> new ArrayList<>()); + listenersForWindow.add(new TrustedPresentationInfo(thresholds, id, listener)); + + // register death listener + var listenerBinder = listener.asBinder(); + var deathRecipient = mUniqueListeners.computeIfAbsent(listenerBinder, + ListenerDeathRecipient::new); + deathRecipient.addInstance(); + } + + void unregister(ITrustedPresentationListener trustedPresentationListener, int id) { + var listenerBinder = trustedPresentationListener.asBinder(); + var deathRecipient = mUniqueListeners.get(listenerBinder); + if (deathRecipient == null) { + ProtoLog.e(WM_DEBUG_TPL, "unregister failed, couldn't find" + + " deathRecipient for %s with id=%d", trustedPresentationListener, id); + return; + } + + if (deathRecipient.removeInstance()) { + mUniqueListeners.remove(listenerBinder); + } + removeListeners(listenerBinder, Optional.of(id)); + } + + boolean isEmpty() { + return mWindowToListeners.isEmpty(); + } + + ArrayList<TrustedPresentationInfo> get(IBinder windowToken) { + return mWindowToListeners.get(windowToken); + } + + private void removeListeners(IBinder listenerBinder, Optional<Integer> id) { + for (int i = mWindowToListeners.size() - 1; i >= 0; i--) { + var listeners = mWindowToListeners.valueAt(i); + for (int j = listeners.size() - 1; j >= 0; j--) { + var listener = listeners.get(j); + if (listener.mListener.asBinder() == listenerBinder && (id.isEmpty() + || listener.mId == id.get())) { + listeners.remove(j); + } + } + if (listeners.isEmpty()) { + mWindowToListeners.removeAt(i); + } + } + } + } + + private final Object mHandlerThreadLock = new Object(); + private HandlerThread mHandlerThread; + private Handler mHandler; + + private WindowInfosListener mWindowInfosListener; + + Listeners mRegisteredListeners = new Listeners(); + + private InputWindowHandle[] mLastWindowHandles; + + private final Object mIgnoredWindowTokensLock = new Object(); + + private final ArraySet<IBinder> mIgnoredWindowTokens = new ArraySet<>(); + + private void startHandlerThreadIfNeeded() { + synchronized (mHandlerThreadLock) { + if (mHandler == null) { + mHandlerThread = new HandlerThread("WindowInfosListenerForTpl"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } + } + } + + void addIgnoredWindowTokens(IBinder token) { + synchronized (mIgnoredWindowTokensLock) { + mIgnoredWindowTokens.add(token); + } + } + + void removeIgnoredWindowTokens(IBinder token) { + synchronized (mIgnoredWindowTokensLock) { + mIgnoredWindowTokens.remove(token); + } + } + + void registerListener(IBinder window, ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + startHandlerThreadIfNeeded(); + mHandler.post(() -> { + ProtoLog.d(WM_DEBUG_TPL, "Registering listener=%s with id=%d for window=%s with %s", + listener, id, window, thresholds); + + mRegisteredListeners.register(window, listener, thresholds, id); + registerWindowInfosListener(); + // Update the initial state for the new registered listener + computeTpl(mLastWindowHandles); + }); + } + + void unregisterListener(ITrustedPresentationListener listener, int id) { + startHandlerThreadIfNeeded(); + mHandler.post(() -> { + ProtoLog.d(WM_DEBUG_TPL, "Unregistering listener=%s with id=%d", + listener, id); + + mRegisteredListeners.unregister(listener, id); + if (mRegisteredListeners.isEmpty()) { + unregisterWindowInfosListener(); + } + }); + } + + void dump(PrintWriter pw) { + final String innerPrefix = " "; + pw.println("TrustedPresentationListenerController:"); + pw.println(innerPrefix + "Active unique listeners (" + + mRegisteredListeners.mUniqueListeners.size() + "):"); + for (int i = 0; i < mRegisteredListeners.mWindowToListeners.size(); i++) { + pw.println( + innerPrefix + " window=" + mRegisteredListeners.mWindowToListeners.keyAt(i)); + final var listeners = mRegisteredListeners.mWindowToListeners.valueAt(i); + for (int j = 0; j < listeners.size(); j++) { + final var listener = listeners.get(j); + pw.println(innerPrefix + innerPrefix + " listener=" + listener.mListener.asBinder() + + " id=" + listener.mId + + " thresholds=" + listener.mThresholds); + } + } + } + + private void registerWindowInfosListener() { + if (mWindowInfosListener != null) { + return; + } + + mWindowInfosListener = new WindowInfosListener() { + @Override + public void onWindowInfosChanged(InputWindowHandle[] windowHandles, + DisplayInfo[] displayInfos) { + mHandler.post(() -> computeTpl(windowHandles)); + } + }; + mLastWindowHandles = mWindowInfosListener.register().first; + } + + private void unregisterWindowInfosListener() { + if (mWindowInfosListener == null) { + return; + } + + mWindowInfosListener.unregister(); + mWindowInfosListener = null; + mLastWindowHandles = null; + } + + private void computeTpl(InputWindowHandle[] windowHandles) { + mLastWindowHandles = windowHandles; + if (mLastWindowHandles == null || mLastWindowHandles.length == 0 + || mRegisteredListeners.isEmpty()) { + return; + } + + Rect tmpRect = new Rect(); + Matrix tmpInverseMatrix = new Matrix(); + float[] tmpMatrix = new float[9]; + Region coveredRegionsAbove = new Region(); + long currTimeMs = System.currentTimeMillis(); + ProtoLog.v(WM_DEBUG_TPL, "Checking %d windows", mLastWindowHandles.length); + + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates = + new ArrayMap<>(); + ArraySet<IBinder> ignoredWindowTokens; + synchronized (mIgnoredWindowTokensLock) { + ignoredWindowTokens = new ArraySet<>(mIgnoredWindowTokens); + } + for (var windowHandle : mLastWindowHandles) { + if (ignoredWindowTokens.contains(windowHandle.getWindowToken())) { + ProtoLog.v(WM_DEBUG_TPL, "Skipping %s", windowHandle.name); + continue; + } + tmpRect.set(windowHandle.frame); + var listeners = mRegisteredListeners.get(windowHandle.getWindowToken()); + if (listeners != null) { + Region region = new Region(); + region.op(tmpRect, coveredRegionsAbove, Region.Op.DIFFERENCE); + windowHandle.transform.invert(tmpInverseMatrix); + tmpInverseMatrix.getValues(tmpMatrix); + float scaleX = (float) Math.sqrt(tmpMatrix[MSCALE_X] * tmpMatrix[MSCALE_X] + + tmpMatrix[MSKEW_X] * tmpMatrix[MSKEW_X]); + float scaleY = (float) Math.sqrt(tmpMatrix[MSCALE_Y] * tmpMatrix[MSCALE_Y] + + tmpMatrix[MSKEW_Y] * tmpMatrix[MSKEW_Y]); + + float fractionRendered = computeFractionRendered(region, new RectF(tmpRect), + windowHandle.contentSize, + scaleX, scaleY); + + checkIfInThreshold(listeners, listenerUpdates, fractionRendered, windowHandle.alpha, + currTimeMs); + } + + coveredRegionsAbove.op(tmpRect, Region.Op.UNION); + ProtoLog.v(WM_DEBUG_TPL, "coveredRegionsAbove updated with %s frame:%s region:%s", + windowHandle.name, tmpRect.toShortString(), coveredRegionsAbove); + } + + for (int i = 0; i < listenerUpdates.size(); i++) { + var updates = listenerUpdates.valueAt(i); + var listener = listenerUpdates.keyAt(i); + try { + listener.onTrustedPresentationChanged(updates.first.toArray(), + updates.second.toArray()); + } catch (RemoteException ignore) { + } + } + } + + private void addListenerUpdate( + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates, + ITrustedPresentationListener listener, int id, boolean presentationState) { + var updates = listenerUpdates.get(listener); + if (updates == null) { + updates = new Pair<>(new IntArray(), new IntArray()); + listenerUpdates.put(listener, updates); + } + if (presentationState) { + updates.first.add(id); + } else { + updates.second.add(id); + } + } + + + private void checkIfInThreshold( + ArrayList<TrustedPresentationInfo> listeners, + ArrayMap<ITrustedPresentationListener, Pair<IntArray, IntArray>> listenerUpdates, + float fractionRendered, float alpha, long currTimeMs) { + ProtoLog.v(WM_DEBUG_TPL, "checkIfInThreshold fractionRendered=%f alpha=%f currTimeMs=%d", + fractionRendered, alpha, currTimeMs); + for (int i = 0; i < listeners.size(); i++) { + var trustedPresentationInfo = listeners.get(i); + var listener = trustedPresentationInfo.mListener; + boolean lastState = trustedPresentationInfo.mLastComputedTrustedPresentationState; + boolean newState = + (alpha >= trustedPresentationInfo.mThresholds.minAlpha) && (fractionRendered + >= trustedPresentationInfo.mThresholds.minFractionRendered); + trustedPresentationInfo.mLastComputedTrustedPresentationState = newState; + + ProtoLog.v(WM_DEBUG_TPL, + "lastState=%s newState=%s alpha=%f minAlpha=%f fractionRendered=%f " + + "minFractionRendered=%f", + lastState, newState, alpha, trustedPresentationInfo.mThresholds.minAlpha, + fractionRendered, trustedPresentationInfo.mThresholds.minFractionRendered); + + if (lastState && !newState) { + // We were in the trusted presentation state, but now we left it, + // emit the callback if needed + if (trustedPresentationInfo.mLastReportedTrustedPresentationState) { + trustedPresentationInfo.mLastReportedTrustedPresentationState = false; + addListenerUpdate(listenerUpdates, listener, + trustedPresentationInfo.mId, /*presentationState*/ false); + ProtoLog.d(WM_DEBUG_TPL, "Adding untrusted state listener=%s with id=%d", + listener, trustedPresentationInfo.mId); + } + // Reset the timer + trustedPresentationInfo.mEnteredTrustedPresentationStateTime = -1; + } else if (!lastState && newState) { + // We were not in the trusted presentation state, but we entered it, begin the timer + // and make sure this gets called at least once more! + trustedPresentationInfo.mEnteredTrustedPresentationStateTime = currTimeMs; + mHandler.postDelayed(() -> { + computeTpl(mLastWindowHandles); + }, (long) (trustedPresentationInfo.mThresholds.stabilityRequirementMs * 1.5)); + } + + // Has the timer elapsed, but we are still in the state? Emit a callback if needed + if (!trustedPresentationInfo.mLastReportedTrustedPresentationState && newState && ( + currTimeMs - trustedPresentationInfo.mEnteredTrustedPresentationStateTime + > trustedPresentationInfo.mThresholds.stabilityRequirementMs)) { + trustedPresentationInfo.mLastReportedTrustedPresentationState = true; + addListenerUpdate(listenerUpdates, listener, + trustedPresentationInfo.mId, /*presentationState*/ true); + ProtoLog.d(WM_DEBUG_TPL, "Adding trusted state listener=%s with id=%d", + listener, trustedPresentationInfo.mId); + } + } + } + + private float computeFractionRendered(Region visibleRegion, RectF screenBounds, + Size contentSize, + float sx, float sy) { + ProtoLog.v(WM_DEBUG_TPL, + "computeFractionRendered: visibleRegion=%s screenBounds=%s contentSize=%s " + + "scale=%f,%f", + visibleRegion, screenBounds, contentSize, sx, sy); + + if (contentSize.getWidth() == 0 || contentSize.getHeight() == 0) { + return -1; + } + if (screenBounds.width() == 0 || screenBounds.height() == 0) { + return -1; + } + + float fractionRendered = Math.min(sx * sy, 1.0f); + ProtoLog.v(WM_DEBUG_TPL, "fractionRendered scale=%f", fractionRendered); + + float boundsOverSourceW = screenBounds.width() / (float) contentSize.getWidth(); + float boundsOverSourceH = screenBounds.height() / (float) contentSize.getHeight(); + fractionRendered *= boundsOverSourceW * boundsOverSourceH; + ProtoLog.v(WM_DEBUG_TPL, "fractionRendered boundsOverSource=%f", fractionRendered); + // Compute the size of all the rects since they may be disconnected. + float[] visibleSize = new float[1]; + RegionUtils.forEachRect(visibleRegion, rect -> { + float size = rect.width() * rect.height(); + visibleSize[0] += size; + }); + + fractionRendered *= visibleSize[0] / (screenBounds.width() * screenBounds.height()); + return fractionRendered; + } + + private static class TrustedPresentationInfo { + boolean mLastComputedTrustedPresentationState = false; + boolean mLastReportedTrustedPresentationState = false; + long mEnteredTrustedPresentationStateTime = -1; + final TrustedPresentationThresholds mThresholds; + + final ITrustedPresentationListener mListener; + final int mId; + + private TrustedPresentationInfo(TrustedPresentationThresholds thresholds, int id, + ITrustedPresentationListener listener) { + mThresholds = thresholds; + mId = id; + mListener = listener; + checkValid(thresholds); + } + + private void checkValid(TrustedPresentationThresholds thresholds) { + if (thresholds.minAlpha <= 0 || thresholds.minFractionRendered <= 0 + || thresholds.stabilityRequirementMs < 1) { + throw new IllegalArgumentException( + "TrustedPresentationThresholds values are invalid"); + } + } + } +} diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 72632dce2507..2b8e541685cc 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -303,9 +303,11 @@ import android.window.AddToSurfaceSyncGroupResult; import android.window.ClientWindowFrames; import android.window.ISurfaceSyncGroupCompletedListener; import android.window.ITaskFpsCallback; +import android.window.ITrustedPresentationListener; import android.window.ScreenCapture; import android.window.SystemPerformanceHinter; import android.window.TaskSnapshot; +import android.window.TrustedPresentationThresholds; import android.window.WindowContainerToken; import android.window.WindowContextInfo; @@ -764,6 +766,9 @@ public class WindowManagerService extends IWindowManager.Stub private final SurfaceSyncGroupController mSurfaceSyncGroupController = new SurfaceSyncGroupController(); + final TrustedPresentationListenerController mTrustedPresentationListenerController = + new TrustedPresentationListenerController(); + @VisibleForTesting final class SettingsObserver extends ContentObserver { private final Uri mDisplayInversionEnabledUri = @@ -7171,6 +7176,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(separator); } mSystemPerformanceHinter.dump(pw, ""); + mTrustedPresentationListenerController.dump(pw); } } @@ -9771,4 +9777,17 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } } + + @Override + public void registerTrustedPresentationListener(IBinder window, + ITrustedPresentationListener listener, + TrustedPresentationThresholds thresholds, int id) { + mTrustedPresentationListenerController.registerListener(window, listener, thresholds, id); + } + + @Override + public void unregisterTrustedPresentationListener(ITrustedPresentationListener listener, + int id) { + mTrustedPresentationListenerController.unregisterListener(listener, id); + } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index b890a9e9bd04..9c4ad5cf3217 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -668,6 +668,12 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean mSeamlesslyRotated = false; /** + * Whether the IME insets have been consumed. If {@code true}, this window won't be able to + * receive visible IME insets; {@code false}, otherwise. + */ + boolean mImeInsetsConsumed = false; + + /** * The insets state of sources provided by windows above the current window. */ final InsetsState mAboveInsetsState = new InsetsState(); @@ -1189,6 +1195,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP ProtoLog.v(WM_DEBUG_ADD_REMOVE, "Adding %s to %s", this, parentWindow); parentWindow.addChild(this, sWindowSubLayerComparator); } + + if (token.mRoundedCornerOverlay) { + mWmService.mTrustedPresentationListenerController.addIgnoredWindowTokens( + getWindowToken()); + } } @Override @@ -1487,7 +1498,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (insetsChanged) { mWindowFrames.setInsetsChanged(false); - mWmService.mWindowsInsetsChanged--; + if (mWmService.mWindowsInsetsChanged > 0) { + mWmService.mWindowsInsetsChanged--; + } if (mWmService.mWindowsInsetsChanged == 0) { mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); } @@ -2393,6 +2406,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } mWmService.postWindowRemoveCleanupLocked(this); + + mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens( + getWindowToken()); } @Override @@ -3796,13 +3812,15 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP */ void notifyInsetsChanged() { ProtoLog.d(WM_DEBUG_WINDOW_INSETS, "notifyInsetsChanged for %s ", this); - mWindowFrames.setInsetsChanged(true); + if (!mWindowFrames.hasInsetsChanged()) { + mWindowFrames.setInsetsChanged(true); - // If the new InsetsState won't be dispatched before releasing WM lock, the following - // message will be executed. - mWmService.mWindowsInsetsChanged++; - mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); - mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED); + // If the new InsetsState won't be dispatched before releasing WM lock, the following + // message will be executed. + mWmService.mWindowsInsetsChanged++; + mWmService.mH.removeMessages(WindowManagerService.H.INSETS_CHANGED); + mWmService.mH.sendEmptyMessage(WindowManagerService.H.INSETS_CHANGED); + } final WindowContainer p = getParent(); if (p != null) { @@ -4192,6 +4210,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } else { pw.print("null"); } + pw.println(); if (mXOffset != 0 || mYOffset != 0) { pw.println(prefix + "mXOffset=" + mXOffset + " mYOffset=" + mYOffset); @@ -4225,6 +4244,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP if (computeDragResizing()) { pw.println(prefix + "computeDragResizing=" + computeDragResizing()); } + if (mImeInsetsConsumed) { + pw.println(prefix + "mImeInsetsConsumed=true"); + } pw.println(prefix + "isOnScreen=" + isOnScreen()); pw.println(prefix + "isVisible=" + isVisible()); pw.println(prefix + "keepClearAreas: restricted=" + mKeepClearAreas diff --git a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java index 8c8f6a6cb386..193a0c8be60b 100644 --- a/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java +++ b/services/core/java/com/android/server/wm/utils/DisplayInfoOverrides.java @@ -30,7 +30,7 @@ public class DisplayInfoOverrides { * Set of DisplayInfo fields that are overridden in DisplayManager using values from * WindowManager */ - public static final DisplayInfoFields WM_OVERRIDE_FIELDS = (out, source) -> { + public static final DisplayInfoFieldsUpdater WM_OVERRIDE_FIELDS = (out, source) -> { out.appWidth = source.appWidth; out.appHeight = source.appHeight; out.smallestNominalAppWidth = source.smallestNominalAppWidth; @@ -55,7 +55,7 @@ public class DisplayInfoOverrides { public static void copyDisplayInfoFields(@NonNull DisplayInfo out, @NonNull DisplayInfo base, @Nullable DisplayInfo override, - @NonNull DisplayInfoFields fields) { + @NonNull DisplayInfoFieldsUpdater fields) { out.copyFrom(base); if (override != null) { @@ -66,7 +66,7 @@ public class DisplayInfoOverrides { /** * Callback interface that allows to specify a subset of fields of DisplayInfo object */ - public interface DisplayInfoFields { + public interface DisplayInfoFieldsUpdater { /** * Copies a subset of fields from {@param source} to {@param out} * diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp index 24ee16389fd2..b19f3d813985 100644 --- a/services/core/jni/Android.bp +++ b/services/core/jni/Android.bp @@ -187,7 +187,7 @@ cc_defaults { "android.hardware.power.stats@1.0", "android.hardware.power.stats-V1-ndk", "android.hardware.thermal@1.0", - "android.hardware.thermal-V1-ndk", + "android.hardware.thermal-V2-ndk", "android.hardware.tv.input@1.0", "android.hardware.tv.input-V2-ndk", "android.hardware.vibrator-V2-cpp", diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java index a0dc2b68415c..f07e820bf8b3 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java @@ -817,12 +817,14 @@ public class PackageManagerSettingsTests { ps1.setUsesSdkLibraries(new String[] { "com.example.sdk.one" }); ps1.setUsesSdkLibrariesVersionsMajor(new long[] { 12 }); + ps1.setUsesSdkLibrariesOptional(new boolean[] {true}); ps1.setFlags(ps1.getFlags() | ApplicationInfo.FLAG_SYSTEM); settingsUnderTest.mPackages.put(PACKAGE_NAME_1, ps1); assertThat(settingsUnderTest.disableSystemPackageLPw(PACKAGE_NAME_1, false), is(true)); ps2.setUsesSdkLibraries(new String[] { "com.example.sdk.two" }); ps2.setUsesSdkLibrariesVersionsMajor(new long[] { 34 }); + ps2.setUsesSdkLibrariesOptional(new boolean[] {false}); settingsUnderTest.mPackages.put(PACKAGE_NAME_2, ps2); settingsUnderTest.writeLPr(computer, /*sync=*/true); @@ -838,19 +840,30 @@ public class PackageManagerSettingsTests { Truth.assertThat(readPs1).isNotNull(); Truth.assertThat(readPs1.getUsesSdkLibraries()).isNotNull(); Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).isNotNull(); + Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).isNotNull(); Truth.assertThat(readPs2).isNotNull(); Truth.assertThat(readPs2.getUsesSdkLibraries()).isNotNull(); Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).isNotNull(); + Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).isNotNull(); List<Long> ps1VersionsAsList = new ArrayList<>(); for (long version : ps1.getUsesSdkLibrariesVersionsMajor()) { ps1VersionsAsList.add(version); } + List<Boolean> ps1RequireAsList = new ArrayList<>(); + for (boolean optional : ps1.getUsesSdkLibrariesOptional()) { + ps1RequireAsList.add(optional); + } + List<Long> ps2VersionsAsList = new ArrayList<>(); for (long version : ps2.getUsesSdkLibrariesVersionsMajor()) { ps2VersionsAsList.add(version); } + List<Boolean> ps2RequireAsList = new ArrayList<>(); + for (boolean optional : ps2.getUsesSdkLibrariesOptional()) { + ps2RequireAsList.add(optional); + } Truth.assertThat(readPs1.getUsesSdkLibraries()).asList() .containsExactlyElementsIn(ps1.getUsesSdkLibraries()).inOrder(); @@ -858,11 +871,17 @@ public class PackageManagerSettingsTests { Truth.assertThat(readPs1.getUsesSdkLibrariesVersionsMajor()).asList() .containsExactlyElementsIn(ps1VersionsAsList).inOrder(); + Truth.assertThat(readPs1.getUsesSdkLibrariesOptional()).asList() + .containsExactlyElementsIn(ps1RequireAsList).inOrder(); + Truth.assertThat(readPs2.getUsesSdkLibraries()).asList() .containsExactlyElementsIn(ps2.getUsesSdkLibraries()).inOrder(); Truth.assertThat(readPs2.getUsesSdkLibrariesVersionsMajor()).asList() .containsExactlyElementsIn(ps2VersionsAsList).inOrder(); + + Truth.assertThat(readPs2.getUsesSdkLibrariesOptional()).asList() + .containsExactlyElementsIn(ps2RequireAsList).inOrder(); } @Test @@ -1047,6 +1066,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1087,6 +1107,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1129,6 +1150,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1167,6 +1189,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1214,6 +1237,7 @@ public class PackageManagerSettingsTests { UserManagerService.getInstance(), null /*usesSdkLibraries*/, null /*usesSdkLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, null /*mimeGroups*/, @@ -1263,6 +1287,7 @@ public class PackageManagerSettingsTests { null /*usesSdkLibrariesVersions*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*mimeGroups*/, UUID.randomUUID(), 34 /*targetSdkVersion*/, @@ -1311,6 +1336,7 @@ public class PackageManagerSettingsTests { null /*usesSdkLibrariesVersions*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*mimeGroups*/, UUID.randomUUID(), 34 /*targetSdkVersion*/, @@ -1356,6 +1382,7 @@ public class PackageManagerSettingsTests { null /*usesSdkLibrariesVersions*/, null /*usesStaticLibraries*/, null /*usesStaticLibrariesVersions*/, + null /*usesSdkLibrariesOptional*/, null /*mimeGroups*/, UUID.randomUUID(), 34 /*targetSdkVersion*/, diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java index ea88ec25b26e..a62cd4fc5fc2 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java @@ -1062,7 +1062,7 @@ public class PackageParserTest { .addProtectedBroadcast("foo8") .setSdkLibraryName("sdk12") .setSdkLibVersionMajor(42) - .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"}) + .addUsesSdkLibrary("sdk23", 200, new String[]{"digest2"}, true) .setStaticSharedLibraryName("foo23") .setStaticSharedLibraryVersion(100) .addUsesStaticLibrary("foo23", 100, new String[]{"digest"}) diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java index decb44c2cd9b..6202908cdfbf 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java @@ -599,8 +599,8 @@ public class ScanTests { .setVolumeUuid(UUID_ONE.toString()) .addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"}) .addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"}) - .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"}) - .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"}) + .addUsesSdkLibrary("some.sdk.library", 123L, new String[]{"testCert3"}, false) + .addUsesSdkLibrary("some.other.sdk.library", 789L, new String[]{"testCert4"}, true) .hideAsParsed()) .setNativeLibraryRootDir("/data/tmp/randompath/base.apk:/lib") .setVersionCodeMajor(1) @@ -628,6 +628,7 @@ public class ScanTests { assertThat(pkgSetting.getUsesSdkLibraries(), arrayContaining("some.sdk.library", "some.other.sdk.library")); assertThat(pkgSetting.getUsesSdkLibrariesVersionsMajor(), is(new long[]{123L, 789L})); + assertThat(pkgSetting.getUsesSdkLibrariesOptional(), is(new boolean[]{false, true})); assertThat(pkgSetting.getPkg(), is(scanResult.mRequest.mParsedPackage)); assertThat(pkgSetting.getPath(), is(new File(createCodePath(packageName)))); assertThat(pkgSetting.getVersionCode(), diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt index 170faf61858b..09b66c11d200 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt @@ -127,6 +127,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag "getUsesSdkLibraries", "getUsesSdkLibrariesVersionsMajor", "getUsesSdkLibrariesCertDigests", + "getUsesSdkLibrariesOptional", // Tested through addUsesStaticLibrary "addUsesStaticLibrary", "getUsesStaticLibraries", @@ -608,7 +609,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag .setSplitHasCode(1, false) .setSplitClassLoaderName(0, "testSplitClassLoaderNameZero") .setSplitClassLoaderName(1, "testSplitClassLoaderNameOne") - .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1")) + .addUsesSdkLibrary("testSdk", 2L, arrayOf("testCertDigest1"), true) .addUsesStaticLibrary("testStatic", 3L, arrayOf("testCertDigest2")) override fun finalizeObject(parcelable: Parcelable) { @@ -661,6 +662,7 @@ class AndroidPackageTest : ParcelableComponentTest(AndroidPackage::class, Packag expect.that(after.usesSdkLibrariesCertDigests!!.size).isEqualTo(1) expect.that(after.usesSdkLibrariesCertDigests!![0]).asList() .containsExactly("testCertDigest1") + expect.that(after.usesSdkLibrariesOptional).asList().containsExactly(true) expect.that(after.usesStaticLibraries).containsExactly("testStatic") expect.that(after.usesStaticLibrariesVersions).asList().containsExactly(3L) 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 4fb9472021c5..650c473533ed 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 @@ -30,15 +30,16 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_FALLBACK_FLEXIBILITY_DEADLINE_MS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.DEFAULT_UNSEEN_CONSTRAINT_GRACE_PERIOD_MS; +import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_APPLIED_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_DEADLINE_PROXIMITY_LIMIT; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FALLBACK_FLEXIBILITY_DEADLINE; -import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_FLEXIBILITY_ENABLED; import static com.android.server.job.controllers.FlexibilityController.FcConfig.KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS; -import static com.android.server.job.controllers.FlexibilityController.NUM_FLEXIBLE_CONSTRAINTS; +import static com.android.server.job.controllers.FlexibilityController.FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.FlexibilityController.SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_BATTERY_NOT_LOW; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CHARGING; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONNECTIVITY; +import static com.android.server.job.controllers.JobStatus.CONSTRAINT_CONTENT_TRIGGER; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_FLEXIBLE; import static com.android.server.job.controllers.JobStatus.CONSTRAINT_IDLE; import static com.android.server.job.controllers.JobStatus.MIN_WINDOW_FOR_FLEXIBILITY_MS; @@ -66,6 +67,7 @@ import android.os.Looper; import android.provider.DeviceConfig; import android.util.ArraySet; +import com.android.server.AppSchedulingModuleThread; import com.android.server.LocalServices; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; @@ -129,6 +131,7 @@ public class FlexibilityControllerTest { when(mContext.getPackageManager()).thenReturn(mPackageManager); when(mPackageManager.hasSystemFeature( PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(false); + when(mPackageManager.hasSystemFeature(PackageManager.FEATURE_EMBEDDED)).thenReturn(false); // Used in FlexibilityController.FcConstants. doAnswer((Answer<Void>) invocationOnMock -> null) .when(() -> DeviceConfig.addOnPropertiesChangedListener( @@ -161,7 +164,8 @@ public class FlexibilityControllerTest { setDeviceConfigString(KEY_PERCENTS_TO_DROP_NUM_FLEXIBLE_CONSTRAINTS, "50,60,70,80"); setDeviceConfigLong(KEY_DEADLINE_PROXIMITY_LIMIT, 0L); - setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true); + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS); + waitForQuietModuleThread(); } @After @@ -171,26 +175,22 @@ public class FlexibilityControllerTest { } } - private void setDeviceConfigBoolean(String key, boolean val) { - mDeviceConfigPropertiesBuilder.setBoolean(key, val); - synchronized (mFlexibilityController.mLock) { - mFlexibilityController.prepareForUpdatedConstantsLocked(); - mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key); - mFlexibilityController.onConstantsUpdatedLocked(); - } + private void setDeviceConfigInt(String key, int val) { + mDeviceConfigPropertiesBuilder.setInt(key, val); + updateDeviceConfig(key); } private void setDeviceConfigLong(String key, Long val) { mDeviceConfigPropertiesBuilder.setLong(key, val); - synchronized (mFlexibilityController.mLock) { - mFlexibilityController.prepareForUpdatedConstantsLocked(); - mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key); - mFlexibilityController.onConstantsUpdatedLocked(); - } + updateDeviceConfig(key); } private void setDeviceConfigString(String key, String val) { mDeviceConfigPropertiesBuilder.setString(key, val); + updateDeviceConfig(key); + } + + private void updateDeviceConfig(String key) { synchronized (mFlexibilityController.mLock) { mFlexibilityController.prepareForUpdatedConstantsLocked(); mFcConfig.processConstantLocked(mDeviceConfigPropertiesBuilder.build(), key); @@ -198,6 +198,11 @@ public class FlexibilityControllerTest { } } + private void waitForQuietModuleThread() { + assertTrue("Failed to wait for quiet module thread", + AppSchedulingModuleThread.getHandler().runWithScissors(() -> {}, 10_000L)); + } + private static JobInfo.Builder createJob(int id) { return new JobInfo.Builder(id, new ComponentName("foo", "bar")); } @@ -207,6 +212,10 @@ public class FlexibilityControllerTest { JobStatus js = JobStatus.createFromJobInfo( jobInfo, 1000, SOURCE_PACKAGE, SOURCE_USER_ID, "FCTest", testTag); js.enqueueTime = FROZEN_TIME; + if (js.hasFlexibilityConstraint()) { + js.setNumAppliedFlexibleConstraints(Integer.bitCount( + mFlexibilityController.getRelevantAppliedConstraintsLocked(js))); + } return js; } @@ -215,18 +224,120 @@ public class FlexibilityControllerTest { */ @Test public void testDefaultVariableValues() { - assertEquals(NUM_FLEXIBLE_CONSTRAINTS, + assertEquals(Integer.bitCount(FLEXIBLE_CONSTRAINTS), mFlexibilityController.mFcConfig.DEFAULT_PERCENT_TO_DROP_FLEXIBLE_CONSTRAINTS.length ); } @Test - public void testOnConstantsUpdated_DefaultFlexibility() { + public void testAppliedConstraints() { + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS); + + // Add connectivity to require 4 constraints + JobStatus connJs = createJobStatus("testAppliedConstraints", + createJob(0).setRequiredNetworkType(NETWORK_TYPE_ANY)); + JobStatus nonConnJs = createJobStatus("testAppliedConstraints", + createJob(1).setRequiredNetworkType(NETWORK_TYPE_NONE)); + + mFlexibilityController.maybeStartTrackingJobLocked(connJs, null); + mFlexibilityController.maybeStartTrackingJobLocked(nonConnJs, null); + + assertEquals(4, connJs.getNumAppliedFlexibleConstraints()); + assertEquals(3, nonConnJs.getNumAppliedFlexibleConstraints()); + + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_BATTERY_NOT_LOW, true, + JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CHARGING, false, + JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_IDLE, false, + JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS); + mFlexibilityController.setConstraintSatisfied( + CONSTRAINT_CONNECTIVITY, true, + JobSchedulerService.sElapsedRealtimeClock.millis() - HOUR_IN_MILLIS); + connJs.setTransportAffinitiesSatisfied(true); + + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, + CONSTRAINT_BATTERY_NOT_LOW | CONSTRAINT_CONNECTIVITY); + waitForQuietModuleThread(); + + // Only battery-not-low (which is satisfied) applies to the non-connectivity job, so it + // should be able to run. + assertEquals(2, connJs.getNumAppliedFlexibleConstraints()); + assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints()); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_BATTERY_NOT_LOW); + waitForQuietModuleThread(); + + assertEquals(1, connJs.getNumAppliedFlexibleConstraints()); + assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints()); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CONNECTIVITY); + waitForQuietModuleThread(); + + // No constraints apply to the non-connectivity job, so it should be able to run. + assertEquals(1, connJs.getNumAppliedFlexibleConstraints()); + assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints()); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CHARGING); + waitForQuietModuleThread(); + + assertEquals(1, connJs.getNumAppliedFlexibleConstraints()); + assertEquals(1, nonConnJs.getNumAppliedFlexibleConstraints()); + synchronized (mFlexibilityController.mLock) { + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertFalse(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, 0); + waitForQuietModuleThread(); + + // No constraints apply, so they should be able to run. + assertEquals(0, connJs.getNumAppliedFlexibleConstraints()); + assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints()); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + + // Invalid constraint to apply. + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, CONSTRAINT_CONTENT_TRIGGER); + waitForQuietModuleThread(); + + // No constraints apply, so they should be able to run. + assertEquals(0, connJs.getNumAppliedFlexibleConstraints()); + assertEquals(0, nonConnJs.getNumAppliedFlexibleConstraints()); + synchronized (mFlexibilityController.mLock) { + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(connJs)); + assertTrue(mFlexibilityController.hasEnoughSatisfiedConstraintsLocked(nonConnJs)); + } + } + + @Test + public void testOnConstantsUpdated_AppliedConstraints() { JobStatus js = createJobStatus("testDefaultFlexibilityConfig", createJob(0)); - assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js)); - setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, false); + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, 0); assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js)); - setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true); + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS); assertFalse(mFlexibilityController.isFlexibilitySatisfiedLocked(js)); } @@ -261,10 +372,10 @@ public class FlexibilityControllerTest { new int[] {10, 20, 30, 40}); assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(1); assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 2, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(2); assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 3, mFlexibilityController.getNextConstraintDropTimeElapsedLocked(js)); } @@ -303,12 +414,12 @@ public class FlexibilityControllerTest { .getNextConstraintDropTimeElapsedLocked(js); assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5, nextTimeToDropNumConstraints); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6, nextTimeToDropNumConstraints); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(FROZEN_TIME + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7, @@ -321,11 +432,11 @@ public class FlexibilityControllerTest { nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(130400100, nextTimeToDropNumConstraints); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(156320100L, nextTimeToDropNumConstraints); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(182240100L, nextTimeToDropNumConstraints); @@ -337,11 +448,11 @@ public class FlexibilityControllerTest { nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(129600100, nextTimeToDropNumConstraints); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(155520100L, nextTimeToDropNumConstraints); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(181440100L, nextTimeToDropNumConstraints); @@ -357,12 +468,12 @@ public class FlexibilityControllerTest { .getNextConstraintDropTimeElapsedLocked(js); assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 5, nextTimeToDropNumConstraints); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(1); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 6, nextTimeToDropNumConstraints); - js.adjustNumRequiredFlexibleConstraints(-1); + js.setNumDroppedFlexibleConstraints(2); nextTimeToDropNumConstraints = mFlexibilityController .getNextConstraintDropTimeElapsedLocked(js); assertEquals(windowStart + MIN_WINDOW_FOR_FLEXIBILITY_MS / 10 * 7, @@ -580,10 +691,10 @@ public class FlexibilityControllerTest { } @Test - public void testWontStopJobFromRunning() { - JobStatus js = createJobStatus("testWontStopJobFromRunning", createJob(101)); + public void testWontStopAlreadyRunningJob() { + JobStatus js = createJobStatus("testWontStopAlreadyRunningJob", createJob(101)); // Stop satisfied constraints from causing a false positive. - js.adjustNumRequiredFlexibleConstraints(100); + js.setNumAppliedFlexibleConstraints(100); synchronized (mFlexibilityController.mLock) { when(mJobSchedulerService.isCurrentlyRunningLocked(js)).thenReturn(true); assertTrue(mFlexibilityController.isFlexibilitySatisfiedLocked(js)); @@ -593,10 +704,9 @@ public class FlexibilityControllerTest { @Test public void testFlexibilityTracker() { FlexibilityController.FlexibilityTracker flexTracker = - mFlexibilityController.new - FlexibilityTracker(NUM_FLEXIBLE_CONSTRAINTS); + mFlexibilityController.new FlexibilityTracker(4); // Plus one for jobs with 0 required constraint. - assertEquals(NUM_FLEXIBLE_CONSTRAINTS + 1, flexTracker.size()); + assertEquals(4 + 1, flexTracker.size()); JobStatus[] jobs = new JobStatus[4]; JobInfo.Builder jb; for (int i = 0; i < jobs.length; i++) { @@ -622,21 +732,21 @@ public class FlexibilityControllerTest { assertEquals(3, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); - flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); + flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 1); 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(0, trackedJobs.get(4).size()); - flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); + flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 2); 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(0, trackedJobs.get(4).size()); - flexTracker.adjustJobsRequiredConstraints(jobs[0], -1, FROZEN_TIME); + flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 3); assertEquals(2, trackedJobs.get(0).size()); assertEquals(0, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); @@ -650,14 +760,14 @@ public class FlexibilityControllerTest { assertEquals(1, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); - flexTracker.resetJobNumDroppedConstraints(jobs[0], FROZEN_TIME); + flexTracker.calculateNumDroppedConstraints(jobs[0], FROZEN_TIME); assertEquals(1, trackedJobs.get(0).size()); assertEquals(0, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); assertEquals(2, trackedJobs.get(3).size()); assertEquals(0, trackedJobs.get(4).size()); - flexTracker.adjustJobsRequiredConstraints(jobs[0], -2, FROZEN_TIME); + flexTracker.setNumDroppedFlexibleConstraints(jobs[0], 2); assertEquals(1, trackedJobs.get(0).size()); assertEquals(1, trackedJobs.get(1).size()); assertEquals(0, trackedJobs.get(2).size()); @@ -669,7 +779,7 @@ public class FlexibilityControllerTest { JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - flexTracker.resetJobNumDroppedConstraints(jobs[0], nowElapsed); + flexTracker.calculateNumDroppedConstraints(jobs[0], nowElapsed); assertEquals(1, trackedJobs.get(0).size()); assertEquals(0, trackedJobs.get(1).size()); assertEquals(1, trackedJobs.get(2).size()); @@ -779,9 +889,9 @@ public class FlexibilityControllerTest { mFlexibilityController.setConstraintSatisfied( SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS, false, FROZEN_TIME); // Require only a single constraint - jsAny.adjustNumRequiredFlexibleConstraints(-3); - jsCell.adjustNumRequiredFlexibleConstraints(-2); - jsWifi.adjustNumRequiredFlexibleConstraints(-2); + jsAny.setNumAppliedFlexibleConstraints(1); + jsCell.setNumAppliedFlexibleConstraints(1); + jsWifi.setNumAppliedFlexibleConstraints(1); synchronized (mFlexibilityController.mLock) { jsAny.setTransportAffinitiesSatisfied(false); jsCell.setTransportAffinitiesSatisfied(false); @@ -1008,9 +1118,9 @@ public class FlexibilityControllerTest { } @Test - public void testResetJobNumDroppedConstraints() { + public void testCalculateNumDroppedConstraints() { JobInfo.Builder jb = createJob(22); - JobStatus js = createJobStatus("testResetJobNumDroppedConstraints", jb); + JobStatus js = createJobStatus("testCalculateNumDroppedConstraints", jb); long nowElapsed = FROZEN_TIME; mFlexibilityController.mFlexibilityTracker.add(js); @@ -1025,14 +1135,14 @@ public class FlexibilityControllerTest { Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); mFlexibilityController.mFlexibilityTracker - .adjustJobsRequiredConstraints(js, -1, nowElapsed); + .setNumDroppedFlexibleConstraints(js, 1); assertEquals(2, js.getNumRequiredFlexibleConstraints()); assertEquals(1, js.getNumDroppedFlexibleConstraints()); assertEquals(1, mFlexibilityController .mFlexibilityTracker.getJobsByNumRequiredConstraints(2).size()); - mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); + mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); assertEquals(2, js.getNumRequiredFlexibleConstraints()); assertEquals(1, js.getNumDroppedFlexibleConstraints()); @@ -1043,7 +1153,7 @@ public class FlexibilityControllerTest { JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); + mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); assertEquals(3, js.getNumRequiredFlexibleConstraints()); assertEquals(0, js.getNumDroppedFlexibleConstraints()); @@ -1054,7 +1164,7 @@ public class FlexibilityControllerTest { JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); + mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); assertEquals(0, js.getNumRequiredFlexibleConstraints()); assertEquals(3, js.getNumDroppedFlexibleConstraints()); @@ -1063,7 +1173,7 @@ public class FlexibilityControllerTest { JobSchedulerService.sElapsedRealtimeClock = Clock.fixed(Instant.ofEpochMilli(nowElapsed), ZoneOffset.UTC); - mFlexibilityController.mFlexibilityTracker.resetJobNumDroppedConstraints(js, nowElapsed); + mFlexibilityController.mFlexibilityTracker.calculateNumDroppedConstraints(js, nowElapsed); assertEquals(1, js.getNumRequiredFlexibleConstraints()); assertEquals(2, js.getNumDroppedFlexibleConstraints()); @@ -1139,20 +1249,28 @@ public class FlexibilityControllerTest { } @Test - public void testDeviceDisabledFlexibility_Auto() { - when(mPackageManager.hasSystemFeature( - PackageManager.FEATURE_AUTOMOTIVE)).thenReturn(true); + public void testUnsupportedDevice_Auto() { + runTestUnsupportedDevice(PackageManager.FEATURE_AUTOMOTIVE); + } + + @Test + public void testUnsupportedDevice_Embedded() { + runTestUnsupportedDevice(PackageManager.FEATURE_EMBEDDED); + } + + private void runTestUnsupportedDevice(String feature) { + when(mPackageManager.hasSystemFeature(feature)).thenReturn(true); mFlexibilityController = new FlexibilityController(mJobSchedulerService, mPrefetchController); - assertFalse(mFlexibilityController.mFlexibilityEnabled); + assertFalse(mFlexibilityController.isEnabled()); - JobStatus js = createJobStatus("testIsAuto", createJob(0)); + JobStatus js = createJobStatus("testUnsupportedDevice", createJob(0)); mFlexibilityController.maybeStartTrackingJobLocked(js, null); assertTrue(js.isConstraintSatisfied(CONSTRAINT_FLEXIBLE)); - setDeviceConfigBoolean(KEY_FLEXIBILITY_ENABLED, true); - assertFalse(mFlexibilityController.mFlexibilityEnabled); + setDeviceConfigInt(KEY_APPLIED_CONSTRAINTS, FLEXIBLE_CONSTRAINTS); + assertFalse(mFlexibilityController.isEnabled()); ArrayList<ArraySet<JobStatus>> jobs = mFlexibilityController.mFlexibilityTracker.getArrayList(); 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 8397b87706d6..293391f43828 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 @@ -232,6 +232,19 @@ public class JobStatusTest { } @Test + public void testFlexibleConstraintCounts() { + JobStatus js = createJobStatus(new JobInfo.Builder(101, new ComponentName("foo", "bar")) + .setUserInitiated(false) + .build()); + + js.setNumAppliedFlexibleConstraints(3); + js.setNumDroppedFlexibleConstraints(2); + assertEquals(3, js.getNumAppliedFlexibleConstraints()); + assertEquals(2, js.getNumDroppedFlexibleConstraints()); + assertEquals(1, js.getNumRequiredFlexibleConstraints()); + } + + @Test public void testIsUserVisibleJob() { JobInfo jobInfo = new JobInfo.Builder(101, new ComponentName("foo", "bar")) .setUserInitiated(false) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt index cf81f0a77702..e131a98b52d0 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt @@ -16,12 +16,14 @@ package com.android.server.pm +import android.app.AppOpsManager import android.content.Intent import android.content.pm.PackageManager import android.os.Binder import com.android.server.testutils.any import com.android.server.testutils.eq import com.android.server.testutils.nullable +import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -31,6 +33,7 @@ import org.mockito.Mockito.clearInvocations import org.mockito.Mockito.never import org.mockito.Mockito.verify + @RunWith(JUnit4::class) class DistractingPackageHelperTest : PackageHelperTestBase() { @@ -40,6 +43,9 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { super.setup() distractingPackageHelper = DistractingPackageHelper( pms, broadcastHelper, suspendPackageHelper) + whenever(rule.mocks().appOpsManager.checkOpNoThrow( + eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any())) + .thenReturn(AppOpsManager.MODE_DEFAULT) } @Test diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt index 46806f334a27..28bd98781ced 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt @@ -15,6 +15,7 @@ */ package com.android.server.pm +import android.app.AppOpsManager import android.app.PropertyInvalidatedCache import android.content.Context import android.content.Intent @@ -151,7 +152,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { .mockStatic(EventLog::class.java) .mockStatic(LocalServices::class.java) .mockStatic(LocalManagerRegistry::class.java) - .mockStatic(DeviceConfig::class.java) + .mockStatic(DeviceConfig::class.java, Mockito.CALLS_REAL_METHODS) .mockStatic(HexEncoding::class.java) .apply(withSession) session = apply.startMocking() @@ -192,6 +193,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { val userManagerService: UserManagerService = mock() val componentResolver: ComponentResolver = mock() val permissionManagerInternal: PermissionManagerServiceInternal = mock() + val appOpsManager: AppOpsManager = mock() val incrementalManager: IncrementalManager = mock() val platformCompat: PlatformCompat = mock() val settings: Settings = mock() @@ -304,6 +306,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) { whenever(mocks.injector.defaultAppProvider) { mocks.defaultAppProvider } whenever(mocks.injector.backgroundHandler) { mocks.backgroundHandler } whenever(mocks.injector.updateOwnershipHelper) { mocks.updateOwnershipHelper } + whenever(mocks.injector.getSystemService(AppOpsManager::class.java)) { mocks.appOpsManager } wheneverStatic { SystemConfig.getInstance() }.thenReturn(mocks.systemConfig) whenever(mocks.systemConfig.availableFeatures).thenReturn(DEFAULT_AVAILABLE_FEATURES_MAP) whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt index 7b381ce443e1..147303363200 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt @@ -16,12 +16,14 @@ package com.android.server.pm +import android.app.AppOpsManager import android.content.Intent import android.content.pm.SuspendDialogInfo import android.os.Binder import android.os.PersistableBundle import com.android.server.testutils.any import com.android.server.testutils.eq +import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -32,6 +34,12 @@ import org.mockito.Mockito.verify @RunWith(JUnit4::class) class SuspendPackageHelperTest : PackageHelperTestBase() { + override fun setup() { + super.setup() + whenever(rule.mocks().appOpsManager.checkOpNoThrow( + eq(AppOpsManager.OP_SYSTEM_EXEMPT_FROM_SUSPENSION), any(), any())) + .thenReturn(AppOpsManager.MODE_DEFAULT) + } @Test fun setPackagesSuspended() { diff --git a/services/tests/servicestests/res/xml/usertypes_test_profile.xml b/services/tests/servicestests/res/xml/usertypes_test_profile.xml index e89199dc9278..6537d47106a9 100644 --- a/services/tests/servicestests/res/xml/usertypes_test_profile.xml +++ b/services/tests/servicestests/res/xml/usertypes_test_profile.xml @@ -40,11 +40,13 @@ mediaSharedWithParent='true' credentialShareableWithParent='false' authAlwaysRequiredToDisableQuietMode='true' + allowStoppingUserWithDelayedLocking='true' showInSettings='23' hideInSettingsInQuietMode='true' inheritDevicePolicy='450' deleteAppWithParent='false' alwaysVisible='true' + crossProfileContentSharingStrategy='0' /> </profile-type> <profile-type name='custom.test.1' max-allowed-per-parent='14' /> diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java index 91140276cde0..a9967f63b3b3 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandlerTest.java @@ -717,6 +717,45 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testSecondFingerSwipe_twoPointerDownAndActivatedState_shouldInPanningState() { + goFromStateIdleTo(STATE_ACTIVATED); + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1)); + //The minimum movement to transit to panningState. + final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); + pointer2.offset(sWipeMinDistance + 1, 0); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1)); + fastForward(ViewConfiguration.getTapTimeout()); + assertIn(STATE_PANNING); + + returnToNormalFrom(STATE_PANNING); + } + + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) + public void testTowFingerSwipe_twoPointerDownAndShortcutTriggeredState_shouldInPanningState() { + goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); + PointF pointer1 = DEFAULT_POINT; + PointF pointer2 = new PointF(DEFAULT_X * 1.5f, DEFAULT_Y); + + send(downEvent()); + send(pointerEvent(ACTION_POINTER_DOWN, new PointF[] {pointer1, pointer2}, 1)); + //The minimum movement to transit to panningState. + final float sWipeMinDistance = ViewConfiguration.get(mContext).getScaledTouchSlop(); + pointer2.offset(sWipeMinDistance + 1, 0); + send(pointerEvent(ACTION_MOVE, new PointF[] {pointer1, pointer2}, 1)); + fastForward(ViewConfiguration.getTapTimeout()); + assertIn(STATE_PANNING); + + returnToNormalFrom(STATE_PANNING); + } + + @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) public void testSecondFingerSwipe_twoPointerDownAndActivatedState_panningState() { goFromStateIdleTo(STATE_ACTIVATED); PointF pointer1 = DEFAULT_POINT; @@ -734,6 +773,7 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) public void testSecondFingerSwipe_twoPointerDownAndShortcutTriggeredState_panningState() { goFromStateIdleTo(STATE_SHORTCUT_TRIGGERED); PointF pointer1 = DEFAULT_POINT; diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java index d26d67107001..77b1455a2ecc 100644 --- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java @@ -92,6 +92,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.os.storage.IStorageManager; import android.platform.test.annotations.Presubmit; +import android.platform.test.flag.junit.SetFlagsRule; import android.util.Log; import android.view.Display; @@ -104,11 +105,14 @@ import com.android.server.am.UserState.KeyEvictedCallback; import com.android.server.pm.UserJourneyLogger; import com.android.server.pm.UserManagerInternal; import com.android.server.pm.UserManagerService; +import com.android.server.pm.UserTypeDetails; +import com.android.server.pm.UserTypeFactory; import com.android.server.wm.ActivityTaskManagerInternal; import com.android.server.wm.WindowManagerService; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -175,6 +179,9 @@ public class UserControllerTest { USER_START_MSG, REPORT_LOCKED_BOOT_COMPLETE_MSG); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Before public void setUp() throws Exception { runWithDexmakerShareClassLoader(() -> { @@ -789,28 +796,99 @@ public class UserControllerTest { } @Test - public void testStartProfile() throws Exception { - setUpAndStartProfileInBackground(TEST_USER_ID1); + public void testStartManagedProfile() throws Exception { + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); startBackgroundUserAssertions(); verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY); } @Test - public void testStartProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception { + public void testStartManagedProfile_whenUsersOnSecondaryDisplaysIsEnabled() throws Exception { mockIsUsersOnSecondaryDisplaysEnabled(true); - setUpAndStartProfileInBackground(TEST_USER_ID1); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); startBackgroundUserAssertions(); verifyUserAssignedToDisplay(TEST_USER_ID1, Display.DEFAULT_DISPLAY); } @Test - public void testStopProfile() throws Exception { - setUpAndStartProfileInBackground(TEST_USER_ID1); + public void testStopManagedProfile() throws Exception { + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); + assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true); + verifyUserUnassignedFromDisplay(TEST_USER_ID1); + } + + @Test + public void testStopPrivateProfile() throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* expectLocking= */ true); verifyUserUnassignedFromDisplay(TEST_USER_ID1); + + mSetFlagsRule.disableFlags( + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertProfileLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* expectLocking= */ true); + verifyUserUnassignedFromDisplay(TEST_USER_ID2); + } + + @Test + public void testStopPrivateProfileWithDelayedLocking() throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ false); + } + + @Test + public void testStopPrivateProfileWithDelayedLocking_flagDisabled() throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.disableFlags( + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); + + mSetFlagsRule.disableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE); + mSetFlagsRule.enableFlags( + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_PRIVATE); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID2, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); + } + + @Test + public void testStopPrivateProfileWithDelayedLocking_maxRunningUsersBreached() + throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 1, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_PRIVATE); + setUpAndStartProfileInBackground(TEST_USER_ID2, UserManager.USER_TYPE_PROFILE_MANAGED); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); + } + + @Test + public void testStopManagedProfileWithDelayedLocking() throws Exception { + mUserController.setInitialConfig(/* mUserSwitchUiEnabled */ true, + /* maxRunningUsers= */ 3, /* delayUserDataLocking= */ false); + mSetFlagsRule.enableFlags(android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE, + android.multiuser.Flags.FLAG_ENABLE_BIOMETRICS_TO_UNLOCK_PRIVATE_SPACE); + setUpAndStartProfileInBackground(TEST_USER_ID1, UserManager.USER_TYPE_PROFILE_MANAGED); + assertUserLockedOrUnlockedAfterStopping(TEST_USER_ID1, /* delayedLocking= */ true, + /* keyEvictedCallback */ null, /* expectLocking= */ true); } /** Tests handleIncomingUser() for a variety of permissions and situations. */ @@ -1001,8 +1079,8 @@ public class UserControllerTest { mUserStates.put(userId, mUserController.getStartedUserState(userId)); } - private void setUpAndStartProfileInBackground(int userId) throws Exception { - setUpUser(userId, UserInfo.FLAG_PROFILE, false, UserManager.USER_TYPE_PROFILE_MANAGED); + private void setUpAndStartProfileInBackground(int userId, String userType) throws Exception { + setUpUser(userId, UserInfo.FLAG_PROFILE, false, userType); assertThat(mUserController.startProfile(userId, /* evenWhenDisabled=*/ false, /* unlockListener= */ null)).isTrue(); @@ -1070,6 +1148,11 @@ public class UserControllerTest { userInfo.preCreated = preCreated; when(mInjector.mUserManagerMock.getUserInfo(eq(userId))).thenReturn(userInfo); when(mInjector.mUserManagerMock.isPreCreated(userId)).thenReturn(preCreated); + + UserTypeDetails userTypeDetails = UserTypeFactory.getUserTypes().get(userType); + assertThat(userTypeDetails).isNotNull(); + when(mInjector.mUserManagerInternalMock.getUserProperties(eq(userId))) + .thenReturn(userTypeDetails.getDefaultUserPropertiesReference()); } private static List<String> getActions(List<Intent> intents) { diff --git a/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java new file mode 100644 index 000000000000..472a82c02937 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/audio/MusicFxHelperTest.java @@ -0,0 +1,642 @@ +/* + * 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.server.audio; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyObject; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.content.Intent; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.media.audiofx.AudioEffect; +import android.os.Message; +import android.util.Log; + +import androidx.test.filters.MediumTest; +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +@MediumTest +@RunWith(AndroidJUnit4.class) +public class MusicFxHelperTest { + private static final String TAG = "MusicFxHelperTest"; + + @Mock private AudioService mMockAudioService; + @Mock private Context mMockContext; + @Mock private PackageManager mMockPackageManager; + + private ResolveInfo mResolveInfo1 = new ResolveInfo(); + private ResolveInfo mResolveInfo2 = new ResolveInfo(); + private final String mTestPkg1 = "testPkg1", mTestPkg2 = "testPkg2", mTestPkg3 = "testPkg3"; + private final String mMusicFxPkgName = "com.android.musicfx"; + private final int mTestUid1 = 1, mTestUid2 = 2, mTestUid3 = 3, mMusicFxUid = 78; + private final int mTestSession1 = 11, mTestSession2 = 22, mTestSession3 = 33; + + private List<ResolveInfo> mEmptyList = new ArrayList<>(); + private List<ResolveInfo> mSingleList = new ArrayList<>(); + private List<ResolveInfo> mDoubleList = new ArrayList<>(); + + // the class being unit-tested here + @InjectMocks private MusicFxHelper mMusicFxHelper; + + @Before + @SuppressWarnings("DirectInvocationOnMock") + public void setUp() throws Exception { + mMockAudioService = mock(AudioService.class); + mMusicFxHelper = mMockAudioService.getMusicFxHelper(); + MockitoAnnotations.initMocks(this); + + mResolveInfo1.activityInfo = new ActivityInfo(); + mResolveInfo1.activityInfo.packageName = mTestPkg1; + mResolveInfo2.activityInfo = new ActivityInfo(); + mResolveInfo2.activityInfo.packageName = mTestPkg2; + + mSingleList.add(mResolveInfo1); + mDoubleList.add(mResolveInfo1); + mDoubleList.add(mResolveInfo2); + + Assert.assertNotNull(mMusicFxHelper); + } + + private Intent newIntent(String action, String packageName, int sessionId) { + Intent intent = new Intent(action); + if (packageName != null) { + intent.putExtra(AudioEffect.EXTRA_PACKAGE_NAME, packageName); + } + intent.putExtra(AudioEffect.EXTRA_AUDIO_SESSION, sessionId); + return intent; + } + + /** + * Helper function to send ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION intent with verification. + * + * @throws NameNotFoundException if no such package is available to the caller. + */ + private void openSessionWithResList( + List<ResolveInfo> list, int bind, int broadcast, String packageName, int audioSession, + int uid) { + doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); + doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt()); + if (list != null && list.size() != 0) { + try { + doReturn(uid).when(mMockPackageManager) + .getPackageUidAsUser(eq(packageName), anyObject(), anyInt()); + doReturn(mMusicFxUid).when(mMockPackageManager) + .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "NameNotFoundException: " + e); + } + } + + Intent intent = newIntent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION, + packageName, audioSession); + mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); + verify(mMockContext, times(bind)) + .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); + verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject()); + } + + /** + * Helper function to send ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION intent with verification. + * + * @throws NameNotFoundException if no such package is available to the caller. + */ + private void closeSessionWithResList( + List<ResolveInfo> list, int unBind, int broadcast, String packageName, + int audioSession, int uid) { + doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); + doReturn(list).when(mMockPackageManager).queryBroadcastReceivers(anyObject(), anyInt()); + if (list != null && list.size() != 0) { + try { + doReturn(uid).when(mMockPackageManager) + .getPackageUidAsUser(eq(packageName), anyObject(), anyInt()); + doReturn(mMusicFxUid).when(mMockPackageManager) + .getPackageUidAsUser(eq(mMusicFxPkgName), anyObject(), anyInt()); + } catch (PackageManager.NameNotFoundException e) { + Log.e(TAG, "NameNotFoundException: " + e); + } + } + + Intent intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, + packageName, audioSession); + mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); + verify(mMockContext, times(unBind)).unbindService(anyObject()); + verify(mMockContext, times(broadcast)).sendBroadcastAsUser(anyObject(), anyObject()); + } + + /** + * Helper function to send MSG_EFFECT_CLIENT_GONE message with verification. + */ + private void sendMessage(int msgId, int uid, int unBinds, int broadcasts) { + mMusicFxHelper.handleMessage(Message.obtain(null, msgId, uid /* arg1 */, 0 /* arg2 */)); + verify(mMockContext, times(broadcasts)).sendBroadcastAsUser(anyObject(), anyObject()); + verify(mMockContext, times(unBinds)).unbindService(anyObject()); + } + + /** + * Send invalid message to MusicFxHelper. + */ + @Test + public void testInvalidMessage() { + Log.i(TAG, "running testInvalidMessage"); + + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE - 1, 0, 0, 0); + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE + 1, 0, 0, 0); + } + + /** + * Send client gone message to MusicFxHelper when no client exist. + */ + @Test + public void testGoneMessageWhenNoClient() { + Log.i(TAG, "running testGoneMessageWhenNoClient"); + + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, 0, 0, 0); + } + + /** + * Send ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION intent to MusicFxHelper when no session exist. + */ + @Test + public void testCloseBroadcastIntent() { + Log.i(TAG, "running testCloseBroadcastIntent"); + + closeSessionWithResList(null, 0, 0, null, mTestSession1, mTestUid1); + } + + /** + * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION intent when target application package was set. + * When the target application package was set for an intent, it means this intent is limited + * to a specific target application, as a result MusicFxHelper will not handle this intent. + */ + @Test + public void testBroadcastIntentWithPackage() { + Log.i(TAG, "running testBroadcastIntentWithPackage"); + + Intent intent = newIntent(AudioEffect.ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION, null, 1); + intent.setPackage(mTestPkg1); + mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); + verify(mMockContext, times(0)) + .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); + verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject()); + + intent = newIntent(AudioEffect.ACTION_CLOSE_AUDIO_EFFECT_CONTROL_SESSION, null, 1); + intent.setPackage(mTestPkg2); + mMusicFxHelper.handleAudioEffectBroadcast(mMockContext, intent); + verify(mMockContext, times(0)) + .bindServiceAsUser(anyObject(), anyObject(), anyInt(), anyObject()); + verify(mMockContext, times(0)).sendBroadcastAsUser(anyObject(), anyObject()); + } + + /** + * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with no broadcast receiver. + */ + @Test + public void testBroadcastIntentWithNoPackageAndNoBroadcastReceiver() { + Log.i(TAG, "running testBroadcastIntentWithNoPackageAndNoBroadcastReceiver"); + + openSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1); + closeSessionWithResList(mEmptyList, 0, 0, null, mTestSession1, mTestUid1); + } + + /** + * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with one broadcast receiver. + */ + @Test + public void testBroadcastIntentWithNoPackageAndOneBroadcastReceiver() { + Log.i(TAG, "running testBroadcastIntentWithNoPackageAndOneBroadcastReceiver"); + + int broadcasts = 1, bind = 1, unbind = 1; + openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid1); + + // repeat with different session ID + broadcasts = broadcasts + 1; + bind = bind + 1; + unbind = unbind + 1; + openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession2, mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession2, mTestUid1); + + // repeat with different UID + broadcasts = broadcasts + 1; + bind = bind + 1; + unbind = unbind + 1; + openSessionWithResList(mSingleList, bind, broadcasts, null, mTestSession1, mTestUid2); + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, null, mTestSession1, mTestUid2); + } + + /** + * OPEN/CLOSE AUDIO_EFFECT_CONTROL_SESSION with two broadcast receivers. + */ + @Test + public void testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers() { + Log.i(TAG, "running testBroadcastIntentWithNoPackageAndTwoBroadcastReceivers"); + + openSessionWithResList(mDoubleList, 1, 1, null, mTestSession1, mTestUid1); + closeSessionWithResList(mDoubleList, 1, 2, null, mTestSession1, mTestUid1); + } + + /** + * Open/close session UID not matching. + * No broadcast for mismatching sessionID/UID/packageName. + */ + @Test + public void testBroadcastBadIntents() { + Log.i(TAG, "running testBroadcastBadIntents"); + + int broadcasts = 1; + openSessionWithResList(mSingleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + // mismatch UID + closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg1, mTestSession1, mTestUid2); + // mismatch AudioSession + closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // mismatch packageName + closeSessionWithResList(mSingleList, 0, broadcasts, mTestPkg2, mTestSession1, mTestUid1); + + // cleanup with correct UID and session ID + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + } + + /** + * Open/close sessions with one UID, some with correct intents some with illegal intents. + * No broadcast for mismatching sessionID/UID/packageName. + */ + @Test + public void testBroadcastGoodAndBadIntents() { + Log.i(TAG, "running testBroadcastGoodAndBadIntents"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + // mismatch packageName, session ID and UID + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid2); + // mismatch session ID and mismatch UID + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid2); + // mismatch packageName and mismatch UID + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession1, + mTestUid2); + // mismatch packageName and sessionID + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid1); + // inconsistency package name for same UID + openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid1); + // open session2 with good intent + broadcasts = broadcasts + 1; + openSessionWithResList(mSingleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + + // cleanup with correct UID and session ID + broadcasts = broadcasts + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mSingleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + } + + /** + * Send ACTION_OPEN_AUDIO_EFFECT_CONTROL_SESSION when there is no listener. + */ + @Test + public void testBroadcastOpenSessionWithValidPackageNameAndNoListener() { + Log.i(TAG, "running testBroadcastOpenSessionWithValidPackageNameAndNoListener"); + + // null listener list should not trigger any action + openSessionWithResList(null, 0, 0, mTestPkg1, mTestSession1, mTestUid1); + // empty listener list should not trigger any action + openSessionWithResList(mEmptyList, 0, 0, mTestPkg1, mTestSession1, mTestUid1); + } + + /** + * One MusicFx client, open session and close. + */ + @Test + public void testOpenCloseAudioSession() { + Log.i(TAG, "running testOpenCloseAudioSession"); + + int broadcasts = 1; + openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + } + + /** + * One MusicFx client, open session and close, then gone. + */ + @Test + public void testOpenCloseAudioSessionAndGone() { + Log.i(TAG, "running testOpenCloseAudioSessionAndGone"); + + int broadcasts = 1; + openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, 0, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + + broadcasts = broadcasts + 1; // 1 open session left + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, 1, broadcasts); + } + + /** + * One MusicFx client, open session, then UID gone without close. + */ + @Test + public void testOpenOneSessionAndGo() { + Log.i(TAG, "running testOpenOneSessionAndGo"); + + int broadcasts = 1; + openSessionWithResList(mDoubleList, 1, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + + broadcasts = broadcasts + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, 1, broadcasts); + } + + /** + * Two MusicFx clients open and close sessions. + */ + @Test + public void testOpenTwoSessionsAndClose() { + Log.i(TAG, "running testOpenTwoSessionsAndClose"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid2); + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + + broadcasts = broadcasts + 1; + bind = bind + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid2); + } + + /** + * Two MusicFx clients open sessions, then both UID gone without close. + */ + @Test + public void testOpenTwoSessionsAndGo() { + Log.i(TAG, "running testOpenTwoSessionsAndGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + + broadcasts = broadcasts + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, unbind, broadcasts); + + broadcasts = broadcasts + 1; + unbind = unbind + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + } + + /** + * Two MusicFx clients open sessions, one close but not gone, the other one gone without close. + */ + @Test + public void testTwoSessionsOpenOneCloseOneGo() { + Log.i(TAG, "running testTwoSessionsOpneAndOneCloseOneGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + + broadcasts = broadcasts + 1; + unbind = unbind + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + } + + /** + * One MusicFx client, open multiple audio sessions, and close all sessions. + */ + @Test + public void testTwoSessionsInSameUidOpenClose() { + Log.i(TAG, "running testTwoSessionsOpneAndOneCloseOneGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + } + + /** + * Three MusicFx clients, each with multiple audio sessions, and close all sessions. + */ + @Test + public void testThreeSessionsInThreeUidOpenClose() { + Log.i(TAG, "running testThreeSessionsInThreeUidOpenClose"); + + int broadcasts = 1, bind = 1, unbind = 0; + //client1 + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // client2 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + // client3 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg3, mTestSession3, + mTestUid3); + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession2, + mTestUid2); + // all sessions of client1 closed + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + // all sessions of client3 closed + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg3, mTestSession1, + mTestUid3); + // all sessions of client2 closed + broadcasts = broadcasts + 1; + // now expect unbind to happen + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg2, mTestSession3, + mTestUid2); + } + + /** + * Two MusicFx clients, with multiple audio sessions, one close all sessions, and other gone. + */ + @Test + public void testTwoUidOneCloseOneGo() { + Log.i(TAG, "running testTwoUidOneCloseOneGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + //client1 + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // client2 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession1, mTestUid2); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + // client2 gone + broadcasts = broadcasts + 2; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + // client 1 close all sessions + broadcasts = broadcasts + 1; + unbind = unbind + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + } + + /** + * Three MusicFx clients, with multiple audio sessions, all UID gone. + */ + @Test + public void testThreeUidAllGo() { + Log.i(TAG, "running testThreeUidAllGo"); + + int broadcasts = 1, bind = 1, unbind = 0; + //client1 + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // client2 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2); + // client3 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3); + + // client2 gone + broadcasts = broadcasts + 2; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + // client3 gone + broadcasts = broadcasts + 2; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts); + // client 1 gone + broadcasts = broadcasts + 2; + // now expect unbindService to happen + unbind = unbind + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid1, unbind, broadcasts); + } + + /** + * Three MusicFx clients, multiple audio sessions, open and UID gone in difference sequence. + */ + @Test + public void testThreeUidDiffSequence() { + Log.i(TAG, "running testThreeUidDiffSequence"); + + int broadcasts = 1, bind = 1, unbind = 0; + //client1 + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession1, mTestUid1); + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg1, mTestSession2, mTestUid1); + // client2 + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession2, mTestUid2); + // client1 close one session + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession1, + mTestUid1); + // client2 open another session + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg2, mTestSession3, mTestUid2); + // client3 open one session + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession3, mTestUid3); + // client2 gone + broadcasts = broadcasts + 2; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid2, unbind, broadcasts); + // client3 open another session + broadcasts = broadcasts + 1; + openSessionWithResList(mDoubleList, bind, broadcasts, mTestPkg3, mTestSession1, mTestUid3); + // client1 close another session, and gone + broadcasts = broadcasts + 1; + closeSessionWithResList(mDoubleList, unbind, broadcasts, mTestPkg1, mTestSession2, + mTestUid1); + // last UID client3 gone, unbind + broadcasts = broadcasts + 2; + unbind = unbind + 1; + sendMessage(MusicFxHelper.MSG_EFFECT_CLIENT_GONE, mTestUid3, unbind, broadcasts); + } +} diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java index 33559107dfbb..18e6f0a2cc57 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/GenericWindowPolicyControllerTest.java @@ -151,6 +151,19 @@ public class GenericWindowPolicyControllerTest { } @Test + public void userNotAllowlisted_launchIsBlocked() { + GenericWindowPolicyController gwpc = createGwpcWithNoAllowedUsers(); + gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false); + + ActivityInfo activityInfo = getActivityInfo( + NONBLOCKED_APP_PACKAGE_NAME, + NONBLOCKED_APP_PACKAGE_NAME, + /* displayOnRemoteDevices */ true, + /* targetDisplayCategory */ null); + assertActivityIsBlocked(gwpc, activityInfo); + } + + @Test public void openNonBlockedAppOnVirtualDisplay_isNotBlocked() { GenericWindowPolicyController gwpc = createGwpc(); gwpc.setDisplayId(DISPLAY_ID, /* isMirrorDisplay= */ false); @@ -702,6 +715,26 @@ public class GenericWindowPolicyControllerTest { /* customHomeComponent= */ null); } + private GenericWindowPolicyController createGwpcWithNoAllowedUsers() { + return new GenericWindowPolicyController( + 0, + 0, + /* allowedUsers= */ new ArraySet<>(), + /* activityLaunchAllowedByDefault= */ true, + /* activityPolicyExemptions= */ new ArraySet<>(), + /* crossTaskNavigationAllowedByDefault= */ true, + /* crossTaskNavigationExemptions= */ new ArraySet<>(), + /* permissionDialogComponent= */ null, + /* activityListener= */ mActivityListener, + /* pipBlockedCallback= */ mPipBlockedCallback, + /* activityBlockedCallback= */ mActivityBlockedCallback, + /* secureWindowCallback= */ mSecureWindowCallback, + /* intentListenerCallback= */ mIntentListenerCallback, + /* displayCategories= */ new ArraySet<>(), + /* showTasksInHostDeviceRecents= */ true, + /* customHomeComponent= */ null); + } + private GenericWindowPolicyController createGwpcWithCustomHomeComponent( ComponentName homeComponent) { return new GenericWindowPolicyController( diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java index d70a4fd555ec..d7ed7c2d6469 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java @@ -69,8 +69,10 @@ public class UserManagerServiceUserPropertiesTest { .setMediaSharedWithParent(false) .setCredentialShareableWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(false) + .setAllowStoppingUserWithDelayedLocking(false) .setDeleteAppWithParent(false) .setAlwaysVisible(false) + .setCrossProfileContentSharingStrategy(0) .build(); final UserProperties actualProps = new UserProperties(defaultProps); actualProps.setShowInLauncher(14); @@ -84,8 +86,10 @@ public class UserManagerServiceUserPropertiesTest { actualProps.setMediaSharedWithParent(true); actualProps.setCredentialShareableWithParent(false); actualProps.setAuthAlwaysRequiredToDisableQuietMode(true); + actualProps.setAllowStoppingUserWithDelayedLocking(true); actualProps.setDeleteAppWithParent(true); actualProps.setAlwaysVisible(true); + actualProps.setCrossProfileContentSharingStrategy(1); // Write the properties to xml. final ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -128,6 +132,7 @@ public class UserManagerServiceUserPropertiesTest { .setMediaSharedWithParent(true) .setDeleteAppWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(false) + .setAllowStoppingUserWithDelayedLocking(false) .setAlwaysVisible(true) .build(); final UserProperties orig = new UserProperties(defaultProps); @@ -137,6 +142,7 @@ public class UserManagerServiceUserPropertiesTest { orig.setInheritDevicePolicy(9456); orig.setDeleteAppWithParent(false); orig.setAuthAlwaysRequiredToDisableQuietMode(true); + orig.setAllowStoppingUserWithDelayedLocking(true); orig.setAlwaysVisible(false); // Test every permission level. (Currently, it's linear so it's easy.) @@ -182,6 +188,8 @@ public class UserManagerServiceUserPropertiesTest { assertEqualGetterOrThrows(orig::getDeleteAppWithParent, copy::getDeleteAppWithParent, exposeAll); assertEqualGetterOrThrows(orig::getAlwaysVisible, copy::getAlwaysVisible, exposeAll); + assertEqualGetterOrThrows(orig::getAllowStoppingUserWithDelayedLocking, + copy::getAllowStoppingUserWithDelayedLocking, exposeAll); // Items requiring hasManagePermission - put them here using hasManagePermission. assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings, @@ -199,6 +207,8 @@ public class UserManagerServiceUserPropertiesTest { copy::isMediaSharedWithParent, true); assertEqualGetterOrThrows(orig::isCredentialShareableWithParent, copy::isCredentialShareableWithParent, true); + assertEqualGetterOrThrows(orig::getCrossProfileContentSharingStrategy, + copy::getCrossProfileContentSharingStrategy, true); } /** @@ -254,7 +264,11 @@ public class UserManagerServiceUserPropertiesTest { .isEqualTo(actual.isCredentialShareableWithParent()); assertThat(expected.isAuthAlwaysRequiredToDisableQuietMode()) .isEqualTo(actual.isAuthAlwaysRequiredToDisableQuietMode()); + assertThat(expected.getAllowStoppingUserWithDelayedLocking()) + .isEqualTo(actual.getAllowStoppingUserWithDelayedLocking()); assertThat(expected.getDeleteAppWithParent()).isEqualTo(actual.getDeleteAppWithParent()); assertThat(expected.getAlwaysVisible()).isEqualTo(actual.getAlwaysVisible()); + assertThat(expected.getCrossProfileContentSharingStrategy()) + .isEqualTo(actual.getCrossProfileContentSharingStrategy()); } } diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java index 77f693917574..70837061b0bb 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserTypeTest.java @@ -29,7 +29,6 @@ import static com.android.server.pm.UserTypeDetails.UNLIMITED_NUMBER_OF_USERS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; @@ -91,12 +90,14 @@ public class UserManagerServiceUserTypeTest { .setMediaSharedWithParent(true) .setCredentialShareableWithParent(false) .setAuthAlwaysRequiredToDisableQuietMode(true) + .setAllowStoppingUserWithDelayedLocking(true) .setShowInSettings(900) .setShowInSharingSurfaces(20) .setShowInQuietMode(30) .setInheritDevicePolicy(340) .setDeleteAppWithParent(true) - .setAlwaysVisible(true); + .setAlwaysVisible(true) + .setCrossProfileContentSharingStrategy(1); final UserTypeDetails type = new UserTypeDetails.Builder() .setName("a.name") @@ -167,6 +168,8 @@ public class UserManagerServiceUserTypeTest { assertFalse(type.getDefaultUserPropertiesReference().isCredentialShareableWithParent()); assertTrue(type.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); + assertTrue(type.getDefaultUserPropertiesReference() + .getAllowStoppingUserWithDelayedLocking()); assertEquals(900, type.getDefaultUserPropertiesReference().getShowInSettings()); assertEquals(20, type.getDefaultUserPropertiesReference().getShowInSharingSurfaces()); assertEquals(30, @@ -175,6 +178,8 @@ public class UserManagerServiceUserTypeTest { .getInheritDevicePolicy()); assertTrue(type.getDefaultUserPropertiesReference().getDeleteAppWithParent()); assertTrue(type.getDefaultUserPropertiesReference().getAlwaysVisible()); + assertEquals(1, type.getDefaultUserPropertiesReference() + .getCrossProfileContentSharingStrategy()); assertEquals(23, type.getBadgeLabel(0)); assertEquals(24, type.getBadgeLabel(1)); @@ -231,6 +236,8 @@ public class UserManagerServiceUserTypeTest { assertEquals(UserProperties.SHOW_IN_LAUNCHER_SEPARATE, props.getShowInSharingSurfaces()); assertEquals(UserProperties.SHOW_IN_QUIET_MODE_PAUSED, props.getShowInQuietMode()); + assertEquals(UserProperties.CROSS_PROFILE_CONTENT_SHARING_NO_DELEGATION, + props.getCrossProfileContentSharingStrategy()); assertFalse(type.hasBadge()); } @@ -318,12 +325,14 @@ public class UserManagerServiceUserTypeTest { .setMediaSharedWithParent(false) .setCredentialShareableWithParent(true) .setAuthAlwaysRequiredToDisableQuietMode(false) + .setAllowStoppingUserWithDelayedLocking(false) .setShowInSettings(20) .setInheritDevicePolicy(21) .setShowInSharingSurfaces(22) .setShowInQuietMode(24) .setDeleteAppWithParent(true) - .setAlwaysVisible(false); + .setAlwaysVisible(false) + .setCrossProfileContentSharingStrategy(1); final ArrayMap<String, UserTypeDetails.Builder> builders = new ArrayMap<>(); builders.put(userTypeAosp1, new UserTypeDetails.Builder() @@ -362,6 +371,8 @@ public class UserManagerServiceUserTypeTest { .isCredentialShareableWithParent()); assertFalse(aospType.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); + assertFalse(aospType.getDefaultUserPropertiesReference() + .getAllowStoppingUserWithDelayedLocking()); assertEquals(20, aospType.getDefaultUserPropertiesReference().getShowInSettings()); assertEquals(21, aospType.getDefaultUserPropertiesReference() .getInheritDevicePolicy()); @@ -370,6 +381,8 @@ public class UserManagerServiceUserTypeTest { aospType.getDefaultUserPropertiesReference().getShowInQuietMode()); assertTrue(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent()); assertFalse(aospType.getDefaultUserPropertiesReference().getAlwaysVisible()); + assertEquals(1, aospType.getDefaultUserPropertiesReference() + .getCrossProfileContentSharingStrategy()); // userTypeAosp2 should be modified. aospType = builders.get(userTypeAosp2).createUserTypeDetails(); @@ -413,6 +426,8 @@ public class UserManagerServiceUserTypeTest { .isCredentialShareableWithParent()); assertTrue(aospType.getDefaultUserPropertiesReference() .isAuthAlwaysRequiredToDisableQuietMode()); + assertTrue(aospType.getDefaultUserPropertiesReference() + .getAllowStoppingUserWithDelayedLocking()); assertEquals(23, aospType.getDefaultUserPropertiesReference().getShowInSettings()); assertEquals(22, aospType.getDefaultUserPropertiesReference().getShowInSharingSurfaces()); @@ -422,6 +437,8 @@ public class UserManagerServiceUserTypeTest { .getInheritDevicePolicy()); assertFalse(aospType.getDefaultUserPropertiesReference().getDeleteAppWithParent()); assertTrue(aospType.getDefaultUserPropertiesReference().getAlwaysVisible()); + assertEquals(0, aospType.getDefaultUserPropertiesReference() + .getCrossProfileContentSharingStrategy()); // userTypeOem1 should be created. UserTypeDetails.Builder customType = builders.get(userTypeOem1); diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java index 8933c6c56797..a743fff5d2ea 100644 --- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java @@ -23,7 +23,6 @@ import static org.junit.Assert.fail; import static org.junit.Assume.assumeTrue; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; import android.annotation.UserIdInt; import android.app.ActivityManager; @@ -219,6 +218,8 @@ public final class UserManagerTest { .isEqualTo(cloneUserProperties.isMediaSharedWithParent()); assertThat(typeProps.isCredentialShareableWithParent()) .isEqualTo(cloneUserProperties.isCredentialShareableWithParent()); + assertThat(typeProps.getCrossProfileContentSharingStrategy()) + .isEqualTo(cloneUserProperties.getCrossProfileContentSharingStrategy()); assertThrows(SecurityException.class, cloneUserProperties::getDeleteAppWithParent); assertThrows(SecurityException.class, cloneUserProperties::getAlwaysVisible); compareDrawables(mUserManager.getUserBadge(), @@ -338,9 +339,15 @@ public final class UserManagerTest { assertThat(typeProps.isAuthAlwaysRequiredToDisableQuietMode()) .isEqualTo(privateProfileUserProperties .isAuthAlwaysRequiredToDisableQuietMode()); + assertThat(typeProps.getCrossProfileContentSharingStrategy()) + .isEqualTo(privateProfileUserProperties.getCrossProfileContentSharingStrategy()); assertThrows(SecurityException.class, privateProfileUserProperties::getDeleteAppWithParent); + assertThrows(SecurityException.class, + privateProfileUserProperties::getAllowStoppingUserWithDelayedLocking); + compareDrawables(mUserManager.getUserBadge(), Resources.getSystem().getDrawable(userTypeDetails.getBadgePlain())); + // Verify private profile parent assertThat(mUserManager.getProfileParent(mainUserId)).isNull(); UserInfo parentProfileInfo = mUserManager.getProfileParent(userInfo.id); diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml index c3074bb0fee8..ef197918deff 100644 --- a/services/tests/wmtests/AndroidManifest.xml +++ b/services/tests/wmtests/AndroidManifest.xml @@ -99,11 +99,6 @@ android:theme="@style/WhiteBackgroundTheme" android:exported="true"/> - <activity android:name="com.android.server.wm.TrustedPresentationCallbackTest$TestActivity" - android:exported="true" - android:showWhenLocked="true" - android:turnScreenOn="true" /> - <activity android:name="android.app.Activity" android:exported="true" android:showWhenLocked="true" diff --git a/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java new file mode 100644 index 000000000000..44b69f18eb04 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DeferredDisplayUpdaterDiffTest.java @@ -0,0 +1,267 @@ +/* + * 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.wm; + +import static android.hardware.display.DeviceProductInfo.CONNECTION_TO_SINK_UNKNOWN; +import static android.view.RoundedCorner.POSITION_TOP_LEFT; +import static android.view.RoundedCorners.NO_ROUNDED_CORNERS; + +import static com.android.server.wm.DeferredDisplayUpdater.DEFERRABLE_FIELDS; +import static com.android.server.wm.DeferredDisplayUpdater.DIFF_NOT_WM_DEFERRABLE; +import static com.android.server.wm.DeferredDisplayUpdater.DIFF_WM_DEFERRABLE; +import static com.android.server.wm.DeferredDisplayUpdater.calculateDisplayInfoDiff; +import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFields; + +import static com.google.common.truth.Truth.assertWithMessage; + +import android.annotation.NonNull; +import android.graphics.Insets; +import android.graphics.Rect; +import android.hardware.display.DeviceProductInfo; +import android.platform.test.annotations.Presubmit; +import android.util.SparseArray; +import android.view.Display; +import android.view.DisplayAddress; +import android.view.DisplayCutout; +import android.view.DisplayInfo; +import android.view.DisplayShape; +import android.view.RoundedCorner; +import android.view.RoundedCorners; +import android.view.SurfaceControl.RefreshRateRange; + +import androidx.test.filters.SmallTest; + +import org.junit.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.function.Consumer; + +/** + * Build/Install/Run: + * atest WmTests:DeferredDisplayUpdaterDiffTest + */ +@SmallTest +@Presubmit +public class DeferredDisplayUpdaterDiffTest { + + private static final Set<String> IGNORED_FIELDS = new HashSet<>(Arrays.asList( + "name" // human-readable name is ignored in equals() checks + )); + + private static final DisplayInfo EMPTY = new DisplayInfo(); + + @Test + public void testCalculateDisplayInfoDiff_allDifferent_returnsChanges() { + final DisplayInfo first = new DisplayInfo(); + final DisplayInfo second = new DisplayInfo(); + makeAllFieldsDifferent(first, second); + + int diff = calculateDisplayInfoDiff(first, second); + + assertWithMessage("Expected to receive a non-zero difference when " + + "there are changes in all fields of DisplayInfo\n" + + "Make sure that you have updated calculateDisplayInfoDiff function after " + + "changing DisplayInfo fields").that(diff).isGreaterThan(0); + } + + @Test + public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsChanges() { + generateWithSingleDifferentField((first, second, field) -> { + int diff = calculateDisplayInfoDiff(first, second); + + assertWithMessage("Expected to receive a non-zero difference when " + + "there are changes in " + field + "\n" + + "Make sure that you have updated calculateDisplayInfoDiff function after " + + "changing DisplayInfo fields").that(diff).isGreaterThan(0); + }); + } + + @Test + public void testCalculateDisplayInfoDiff_forEveryDifferentField_returnsMatchingChange() { + generateWithSingleDifferentField((first, second, field) -> { + boolean hasDeferrableFieldChange = hasDeferrableFieldChange(first, second); + int expectedDiff = + hasDeferrableFieldChange ? DIFF_WM_DEFERRABLE : DIFF_NOT_WM_DEFERRABLE; + + int diff = calculateDisplayInfoDiff(first, second); + + assertWithMessage("Expected to have diff = " + expectedDiff + + ", for field = " + field + "\n" + + "Make sure that you have updated calculateDisplayInfoDiff function after " + + "changing DisplayInfo fields").that( + diff).isEqualTo(expectedDiff); + }); + } + + /** + * Sets each field of the objects to different values using reflection + */ + private static void makeAllFieldsDifferent(@NonNull DisplayInfo first, + @NonNull DisplayInfo second) { + forEachDisplayInfoField(field -> { + try { + setDifferentFieldValues(first, second, field); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + }); + } + + private static boolean hasDeferrableFieldChange(@NonNull DisplayInfo first, + @NonNull DisplayInfo second) { + final DisplayInfo firstDeferrableFieldsOnly = new DisplayInfo(); + final DisplayInfo secondDeferrableFieldsOnly = new DisplayInfo(); + + copyDisplayInfoFields(/* out= */ firstDeferrableFieldsOnly, /* base= */ + EMPTY, /* override= */ first, DEFERRABLE_FIELDS); + copyDisplayInfoFields(/* out= */ secondDeferrableFieldsOnly, /* base= */ + EMPTY, /* override= */ second, DEFERRABLE_FIELDS); + + return !firstDeferrableFieldsOnly.equals(secondDeferrableFieldsOnly); + } + + /** + * Creates pairs of DisplayInfos where only one field is different, the callback is called for + * each field + */ + private static void generateWithSingleDifferentField(DisplayInfoConsumer consumer) { + forEachDisplayInfoField(field -> { + final DisplayInfo first = new DisplayInfo(); + final DisplayInfo second = new DisplayInfo(); + + try { + setDifferentFieldValues(first, second, field); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + + consumer.consume(first, second, field); + }); + } + + private static void setDifferentFieldValues(@NonNull DisplayInfo first, + @NonNull DisplayInfo second, + @NonNull Field field) throws IllegalAccessException { + final Class<?> type = field.getType(); + if (type.equals(int.class)) { + field.setInt(first, 1); + field.setInt(second, 2); + } else if (type.equals(double.class)) { + field.setDouble(first, 1.0); + field.setDouble(second, 2.0); + } else if (type.equals(short.class)) { + field.setShort(first, (short) 1); + field.setShort(second, (short) 2); + } else if (type.equals(long.class)) { + field.setLong(first, 1L); + field.setLong(second, 2L); + } else if (type.equals(char.class)) { + field.setChar(first, 'a'); + field.setChar(second, 'b'); + } else if (type.equals(byte.class)) { + field.setByte(first, (byte) 1); + field.setByte(second, (byte) 2); + } else if (type.equals(float.class)) { + field.setFloat(first, 1.0f); + field.setFloat(second, 2.0f); + } else if (type == boolean.class) { + field.setBoolean(first, true); + field.setBoolean(second, false); + } else if (type.equals(String.class)) { + field.set(first, "one"); + field.set(second, "two"); + } else if (type.equals(DisplayAddress.class)) { + field.set(first, DisplayAddress.fromPhysicalDisplayId(0)); + field.set(second, DisplayAddress.fromPhysicalDisplayId(1)); + } else if (type.equals(DeviceProductInfo.class)) { + field.set(first, new DeviceProductInfo("name", "pnp_id", "product_id1", 2023, + CONNECTION_TO_SINK_UNKNOWN)); + field.set(second, new DeviceProductInfo("name", "pnp_id", "product_id2", 2023, + CONNECTION_TO_SINK_UNKNOWN)); + } else if (type.equals(DisplayCutout.class)) { + field.set(first, + new DisplayCutout(Insets.NONE, new Rect(0, 0, 100, 100), null, null, + null)); + field.set(second, + new DisplayCutout(Insets.NONE, new Rect(0, 0, 200, 200), null, null, + null)); + } else if (type.equals(RoundedCorners.class)) { + field.set(first, NO_ROUNDED_CORNERS); + + final RoundedCorners other = new RoundedCorners(NO_ROUNDED_CORNERS); + other.setRoundedCorner(POSITION_TOP_LEFT, + new RoundedCorner(POSITION_TOP_LEFT, 1, 2, 3)); + field.set(second, other); + } else if (type.equals(DisplayShape.class)) { + field.set(first, DisplayShape.createDefaultDisplayShape(100, 200, false)); + field.set(second, DisplayShape.createDefaultDisplayShape(50, 100, false)); + } else if (type.equals(RefreshRateRange.class)) { + field.set(first, new RefreshRateRange(0, 100)); + field.set(second, new RefreshRateRange(20, 80)); + } else if (type.equals(Display.HdrCapabilities.class)) { + field.set(first, new Display.HdrCapabilities(new int[]{0}, 100, 50, 25)); + field.set(second, new Display.HdrCapabilities(new int[]{1}, 100, 50, 25)); + } else if (type.equals(SparseArray.class) + && ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0].equals( + RefreshRateRange.class)) { + final SparseArray<RefreshRateRange> array1 = new SparseArray<>(); + array1.set(0, new RefreshRateRange(0, 100)); + final SparseArray<RefreshRateRange> array2 = new SparseArray<>(); + array2.set(0, new RefreshRateRange(20, 80)); + field.set(first, array1); + field.set(second, array2); + } else if (type.isArray() && type.getComponentType().equals(int.class)) { + field.set(first, new int[]{0}); + field.set(second, new int[]{1}); + } else if (type.isArray() && type.getComponentType().equals(Display.Mode.class)) { + field.set(first, new Display.Mode[]{new Display.Mode(100, 200, 300)}); + field.set(second, new Display.Mode[]{new Display.Mode(10, 20, 30)}); + } else { + throw new IllegalArgumentException("Field " + field + + " is not supported by this test, please add implementation of setting " + + "different values for this field"); + } + } + + private interface DisplayInfoConsumer { + void consume(DisplayInfo first, DisplayInfo second, Field field); + } + + /** + * Iterates over every non-static field of DisplayInfo class except IGNORED_FIELDS + */ + private static void forEachDisplayInfoField(Consumer<Field> consumer) { + for (Field field : DisplayInfo.class.getDeclaredFields()) { + field.setAccessible(true); + + if (Modifier.isStatic(field.getModifiers())) { + continue; + } + + if (IGNORED_FIELDS.contains(field.getName())) { + continue; + } + + consumer.accept(field); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java new file mode 100644 index 000000000000..dfa595c23e44 --- /dev/null +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java @@ -0,0 +1,237 @@ +/* + * 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.wm; + +import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.when; + +import android.platform.test.annotations.Presubmit; +import android.view.DisplayInfo; + +import androidx.test.filters.SmallTest; + +import com.android.server.wm.TransitionController.OnStartCollect; +import com.android.window.flags.Flags; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; + +/** + * Tests for the {@link DisplayContent} class when FLAG_DEFER_DISPLAY_UPDATES is enabled. + * + * Build/Install/Run: + * atest WmTests:DisplayContentDeferredUpdateTests + */ +@SmallTest +@Presubmit +@RunWith(WindowTestRunner.class) +public class DisplayContentDeferredUpdateTests extends WindowTestsBase { + + @Override + protected void onBeforeSystemServicesCreated() { + // Set other flags to their default values + mSetFlagsRule.initAllFlagsToReleaseConfigDefault(); + + mSetFlagsRule.enableFlags(Flags.FLAG_DEFER_DISPLAY_UPDATES); + } + + @Before + public void before() { + mockTransitionsController(/* enabled= */ true); + mockRemoteDisplayChangeController(); + } + + @Test + public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() { + performInitialDisplayUpdate(); + + givenDisplayInfo(/* uniqueId= */ "old"); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + + // Emulate that collection has started + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + verify(onUpdated).run(); + clearInvocations(mDisplayContent.mTransitionController, onUpdated); + + givenDisplayInfo(/* uniqueId= */ "new"); + mDisplayContent.requestDisplayUpdate(onUpdated); + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + verify(onUpdated).run(); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new"); + } + + @Test + public void testUpdate_nonDeferrableUpdateAndTransitionDeferred_nonDeferrableFieldUpdated() { + performInitialDisplayUpdate(); + + // Update only color mode (non-deferrable field) and keep the same unique id + givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + + verify(onUpdated).run(); + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123); + } + + @Test + public void testUpdate_nonDeferrableUpdateTwiceAndTransitionDeferred_fieldHasLatestValue() { + performInitialDisplayUpdate(); + + // Update only color mode (non-deferrable field) and keep the same unique id + givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 123); + mDisplayContent.requestDisplayUpdate(mock(Runnable.class)); + + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123); + assertThat(mDisplayContent.getDisplayInfo().uniqueId) + .isEqualTo("initial_unique_id"); + + // Update unique id (deferrable field), keep the same color mode, + // this update should be deferred + givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 123); + mDisplayContent.requestDisplayUpdate(mock(Runnable.class)); + + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(123); + assertThat(mDisplayContent.getDisplayInfo().uniqueId) + .isEqualTo("initial_unique_id"); + + // Update color mode again and keep the same unique id, color mode update + // should not be deferred, unique id update is still deferred as transition + // has not started collecting yet + givenDisplayInfo(/* uniqueId= */ "new_unique_id", /* colorMode= */ 456); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456); + assertThat(mDisplayContent.getDisplayInfo().uniqueId) + .isEqualTo("initial_unique_id"); + + // Mark transition as started collected, so pending changes are applied + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + + // Verify that all fields have the latest values + verify(onUpdated).run(); + assertThat(mDisplayContent.getDisplayInfo().colorMode).isEqualTo(456); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new_unique_id"); + } + + @Test + public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() { + performInitialDisplayUpdate(); + givenDisplayInfo(/* uniqueId= */ "old"); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + verify(onUpdated).run(); + clearInvocations(mDisplayContent.mTransitionController, onUpdated); + + givenDisplayInfo(/* uniqueId= */ "new"); + mDisplayContent.requestDisplayUpdate(onUpdated); + + captureStartTransitionCollection(); // do not continue by not starting the collection + verify(onUpdated, never()).run(); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("old"); + } + + @Test + public void testTwoDisplayUpdates_transitionStarted_displayUpdated() { + performInitialDisplayUpdate(); + givenDisplayInfo(/* uniqueId= */ "old"); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + captureStartTransitionCollection().getValue() + .onCollectStarted(/* deferred= */ true); + verify(onUpdated).run(); + clearInvocations(mDisplayContent.mTransitionController, onUpdated); + + // Perform two display updates while WM is 'busy' + givenDisplayInfo(/* uniqueId= */ "new1"); + Runnable onUpdated1 = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated1); + givenDisplayInfo(/* uniqueId= */ "new2"); + Runnable onUpdated2 = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated2); + + // Continue with the first update + captureStartTransitionCollection().getAllValues().get(0) + .onCollectStarted(/* deferred= */ true); + verify(onUpdated1).run(); + verify(onUpdated2, never()).run(); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new1"); + + // Continue with the second update + captureStartTransitionCollection().getAllValues().get(1) + .onCollectStarted(/* deferred= */ true); + verify(onUpdated2).run(); + assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new2"); + } + + private void mockTransitionsController(boolean enabled) { + spyOn(mDisplayContent.mTransitionController); + when(mDisplayContent.mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled); + doReturn(true).when(mDisplayContent.mTransitionController).startCollectOrQueue(any(), + any()); + } + + private void mockRemoteDisplayChangeController() { + spyOn(mDisplayContent.mRemoteDisplayChangeController); + doReturn(true).when(mDisplayContent.mRemoteDisplayChangeController) + .performRemoteDisplayChange(anyInt(), anyInt(), any(), any()); + } + + private ArgumentCaptor<OnStartCollect> captureStartTransitionCollection() { + ArgumentCaptor<OnStartCollect> callbackCaptor = + ArgumentCaptor.forClass(OnStartCollect.class); + verify(mDisplayContent.mTransitionController, atLeast(1)).startCollectOrQueue(any(), + callbackCaptor.capture()); + return callbackCaptor; + } + + private void givenDisplayInfo(String uniqueId) { + givenDisplayInfo(uniqueId, /* colorMode= */ 0); + } + + private void givenDisplayInfo(String uniqueId, int colorMode) { + spyOn(mDisplayContent.mDisplay); + doAnswer(invocation -> { + DisplayInfo info = invocation.getArgument(0); + info.uniqueId = uniqueId; + info.colorMode = colorMode; + return null; + }).when(mDisplayContent.mDisplay).getDisplayInfo(any()); + } + + private void performInitialDisplayUpdate() { + givenDisplayInfo(/* uniqueId= */ "initial_unique_id", /* colorMode= */ 0); + Runnable onUpdated = mock(Runnable.class); + mDisplayContent.requestDisplayUpdate(onUpdated); + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java index ffa1ed926766..38a66a9d5486 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -19,10 +19,13 @@ package com.android.server.wm; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; +import static android.view.InsetsSource.ID_IME; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; +import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_CONSUME_IME_INSETS; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; @@ -432,6 +435,56 @@ public class InsetsPolicyTest extends WindowTestsBase { } + @SetupWindows(addWindows = W_INPUT_METHOD) + @Test + public void testConsumeImeInsets() { + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + final InsetsSource imeSource = new InsetsSource(ID_IME, ime()); + imeSource.setVisible(true); + mImeWindow.mHasSurface = true; + + final WindowState win1 = addWindow(TYPE_APPLICATION, "win1"); + final WindowState win2 = addWindow(TYPE_APPLICATION, "win2"); + + win1.mAboveInsetsState.addSource(imeSource); + win1.mHasSurface = true; + win2.mAboveInsetsState.addSource(imeSource); + win2.mHasSurface = true; + + assertTrue(mImeWindow.isVisible()); + assertTrue(win1.isVisible()); + assertTrue(win2.isVisible()); + + // Make sure both windows have visible IME insets. + assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + + win2.mAttrs.privateFlags |= PRIVATE_FLAG_CONSUME_IME_INSETS; + + displayPolicy.beginPostLayoutPolicyLw(); + displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null); + displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null); + displayPolicy.finishPostLayoutPolicyLw(); + + // Make sure win2 doesn't have visible IME insets, but win1 still does. + assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertFalse(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertTrue(win1.getWindowFrames().hasInsetsChanged()); + + win2.mAttrs.privateFlags &= ~PRIVATE_FLAG_CONSUME_IME_INSETS; + win2.getWindowFrames().setInsetsChanged(false); + + displayPolicy.beginPostLayoutPolicyLw(); + displayPolicy.applyPostLayoutPolicyLw(win2, win2.mAttrs, null, null); + displayPolicy.applyPostLayoutPolicyLw(win1, win1.mAttrs, null, null); + displayPolicy.finishPostLayoutPolicyLw(); + + // Make sure both windows have visible IME insets. + assertTrue(win1.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertTrue(win2.getInsetsState().isSourceOrDefaultVisible(ID_IME, ime())); + assertTrue(win1.getWindowFrames().hasInsetsChanged()); + } + private WindowState addNavigationBar() { final Binder owner = new Binder(); final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar"); diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java b/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java deleted file mode 100644 index c5dd447b5b0c..000000000000 --- a/services/tests/wmtests/src/com/android/server/wm/TrustedPresentationCallbackTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * 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.wm; - -import static android.server.wm.ActivityManagerTestBase.createFullscreenActivityScenarioRule; -import static android.server.wm.BuildUtils.HW_TIMEOUT_MULTIPLIER; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; - -import android.app.Activity; -import android.platform.test.annotations.Presubmit; -import android.server.wm.CtsWindowInfoUtils; -import android.view.SurfaceControl; -import android.view.SurfaceControl.TrustedPresentationThresholds; - -import androidx.annotation.GuardedBy; -import androidx.test.ext.junit.rules.ActivityScenarioRule; - -import com.android.server.wm.utils.CommonUtils; - -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TestName; - -import java.util.function.Consumer; - -/** - * TODO (b/287076178): Move these tests to - * {@link android.view.surfacecontrol.cts.TrustedPresentationCallbackTest} when API is made public - */ -@Presubmit -public class TrustedPresentationCallbackTest { - private static final String TAG = "TrustedPresentationCallbackTest"; - private static final int STABILITY_REQUIREMENT_MS = 500; - private static final long WAIT_TIME_MS = HW_TIMEOUT_MULTIPLIER * 2000L; - - private static final float FRACTION_VISIBLE = 0.1f; - - private final Object mResultsLock = new Object(); - @GuardedBy("mResultsLock") - private boolean mResult; - @GuardedBy("mResultsLock") - private boolean mReceivedResults; - - @Rule - public TestName mName = new TestName(); - - @Rule - public ActivityScenarioRule<TestActivity> mActivityRule = createFullscreenActivityScenarioRule( - TestActivity.class); - - private TestActivity mActivity; - - @Before - public void setup() { - mActivityRule.getScenario().onActivity(activity -> mActivity = activity); - } - - @After - public void tearDown() { - CommonUtils.waitUntilActivityRemoved(mActivity); - } - - @Test - public void testAddTrustedPresentationListenerOnWindow() throws InterruptedException { - TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds( - 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds, - Runnable::run, inTrustedPresentationState -> { - synchronized (mResultsLock) { - mResult = inTrustedPresentationState; - mReceivedResults = true; - mResultsLock.notify(); - } - }); - t.apply(); - synchronized (mResultsLock) { - assertResults(); - } - } - - @Test - public void testRemoveTrustedPresentationListenerOnWindow() throws InterruptedException { - TrustedPresentationThresholds thresholds = new TrustedPresentationThresholds( - 1 /* minAlpha */, FRACTION_VISIBLE, STABILITY_REQUIREMENT_MS); - Consumer<Boolean> trustedPresentationCallback = inTrustedPresentationState -> { - synchronized (mResultsLock) { - mResult = inTrustedPresentationState; - mReceivedResults = true; - mResultsLock.notify(); - } - }; - SurfaceControl.Transaction t = new SurfaceControl.Transaction(); - mActivity.getWindow().getRootSurfaceControl().addTrustedPresentationCallback(t, thresholds, - Runnable::run, trustedPresentationCallback); - t.apply(); - - synchronized (mResultsLock) { - if (!mReceivedResults) { - mResultsLock.wait(WAIT_TIME_MS); - } - assertResults(); - // reset the state - mReceivedResults = false; - } - - mActivity.getWindow().getRootSurfaceControl().removeTrustedPresentationCallback(t, - trustedPresentationCallback); - t.apply(); - - synchronized (mResultsLock) { - if (!mReceivedResults) { - mResultsLock.wait(WAIT_TIME_MS); - } - // Ensure we waited the full time and never received a notify on the result from the - // callback. - assertFalse("Should never have received a callback", mReceivedResults); - // results shouldn't have changed. - assertTrue(mResult); - } - } - - @GuardedBy("mResultsLock") - private void assertResults() throws InterruptedException { - mResultsLock.wait(WAIT_TIME_MS); - - if (!mReceivedResults) { - CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, "test " + mName.getMethodName()); - } - // Make sure we received the results and not just timed out - assertTrue("Timed out waiting for results", mReceivedResults); - assertTrue(mResult); - } - - public static class TestActivity extends Activity { - } -} diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 1e686873c1b0..6a77b983f963 100644 --- a/telephony/java/android/telephony/CarrierConfigManager.java +++ b/telephony/java/android/telephony/CarrierConfigManager.java @@ -9593,6 +9593,84 @@ public class CarrierConfigManager { "satellite_connection_hysteresis_sec_int"; /** + * This threshold is used when connected to a non-terrestrial LTE network. + * A list of 4 NTN LTE RSRP thresholds above which a signal level is considered POOR, + * MODERATE, GOOD, or EXCELLENT, to be used in SignalStrength reporting. + * + * Note that the min and max thresholds are fixed at -140 and -44, as explained in + * TS 136.133 9.1.4 - RSRP Measurement Report Mapping. + * <p> + * See SignalStrength#MAX_LTE_RSRP and SignalStrength#MIN_LTE_RSRP. Any signal level outside + * these boundaries is considered invalid. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY = + "ntn_lte_rsrp_thresholds_int_array"; + + /** + * This threshold is used when connected to a non-terrestrial LTE network. + * A list of 4 customized NTN LTE Reference Signal Received Quality (RSRQ) thresholds. + * + * Reference: TS 136.133 v12.6.0 section 9.1.7 - RSRQ Measurement Report Mapping. + * + * 4 threshold integers must be within the boundaries [-34 dB, 3 dB], and the levels are: + * "NONE: [-34, threshold1)" + * "POOR: [threshold1, threshold2)" + * "MODERATE: [threshold2, threshold3)" + * "GOOD: [threshold3, threshold4)" + * "EXCELLENT: [threshold4, 3]" + * + * This key is considered invalid if the format is violated. If the key is invalid or + * not configured, a default value set will apply. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY = + "ntn_lte_rsrq_thresholds_int_array"; + + /** + * This threshold is used when connected to a non-terrestrial LTE network. + * A list of 4 customized NTN LTE Reference Signal Signal to Noise Ratio (RSSNR) thresholds. + * + * 4 threshold integers must be within the boundaries [-20 dB, 30 dB], and the levels are: + * "NONE: [-20, threshold1)" + * "POOR: [threshold1, threshold2)" + * "MODERATE: [threshold2, threshold3)" + * "GOOD: [threshold3, threshold4)" + * "EXCELLENT: [threshold4, 30]" + * + * This key is considered invalid if the format is violated. If the key is invalid or + * not configured, a default value set will apply. + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY = + "ntn_lte_rssnr_thresholds_int_array"; + + /** + * This threshold is used when connected to a non-terrestrial LTE network. + * Bit-field integer to determine whether to use Reference Signal Received Power (RSRP), + * Reference Signal Received Quality (RSRQ), or/and Reference Signal Signal to Noise Ratio + * (RSSNR) for the number of NTN LTE signal bars and signal criteria reporting enabling. + * + * <p> If a measure is not set, signal criteria reporting from modem will not be triggered and + * not be used for calculating signal level. If multiple measures are set bit, the parameter + * whose value is smallest is used to indicate the signal level. + * <UL> + * <LI>RSRP = 1 << 0</LI> + * <LI>RSRQ = 1 << 1</LI> + * <LI>RSSNR = 1 << 2</LI> + * </UL> + * <p> The value of this key must be bitwise OR of CellSignalStrengthLte#USE_RSRP, + * CellSignalStrengthLte#USE_RSRQ, CellSignalStrengthLte#USE_RSSNR. + * + * <p> For example, if both RSRP and RSRQ are used, the value of key is 3 (1 << 0 | 1 << 1). + * If the key is invalid or not configured, a default value (RSRP = 1 << 0) will apply. + * + */ + @FlaggedApi(Flags.FLAG_CARRIER_ENABLED_SATELLITE_FLAG) + public static final String KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT = + "parameters_used_for_ntn_lte_signal_bar_int"; + + /** * Indicating whether DUN APN should be disabled when the device is roaming. In that case, * the default APN (i.e. internet) will be used for tethering. * @@ -10628,6 +10706,32 @@ public class CarrierConfigManager { PersistableBundle.EMPTY); sDefaults.putBoolean(KEY_SATELLITE_ATTACH_SUPPORTED_BOOL, false); sDefaults.putInt(KEY_SATELLITE_CONNECTION_HYSTERESIS_SEC_INT, 300); + sDefaults.putIntArray(KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY, + // Boundaries: [-140 dBm, -44 dBm] + new int[]{ + -128, /* SIGNAL_STRENGTH_POOR */ + -118, /* SIGNAL_STRENGTH_MODERATE */ + -108, /* SIGNAL_STRENGTH_GOOD */ + -98 /* SIGNAL_STRENGTH_GREAT */ + }); + sDefaults.putIntArray(KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY, + // Boundaries: [-34 dB, 3 dB] + new int[]{ + -20, /* SIGNAL_STRENGTH_POOR */ + -17, /* SIGNAL_STRENGTH_MODERATE */ + -14, /* SIGNAL_STRENGTH_GOOD */ + -11 /* SIGNAL_STRENGTH_GREAT */ + }); + sDefaults.putIntArray(KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY, + // Boundaries: [-20 dBm, 30 dBm] + new int[] { + -3, /* SIGNAL_STRENGTH_POOR */ + 1, /* SIGNAL_STRENGTH_MODERATE */ + 5, /* SIGNAL_STRENGTH_GOOD */ + 13 /* SIGNAL_STRENGTH_GREAT */ + }); + sDefaults.putInt(KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT, + CellSignalStrengthLte.USE_RSRP); sDefaults.putBoolean(KEY_DISABLE_DUN_APN_WHILE_ROAMING_WITH_PRESET_APN_BOOL, false); sDefaults.putString(KEY_DEFAULT_PREFERRED_APN_NAME_STRING, ""); sDefaults.putBoolean(KEY_SUPPORTS_CALL_COMPOSER_BOOL, false); diff --git a/telephony/java/android/telephony/CellSignalStrengthLte.java b/telephony/java/android/telephony/CellSignalStrengthLte.java index 5e902613a654..f5282639ae6c 100644 --- a/telephony/java/android/telephony/CellSignalStrengthLte.java +++ b/telephony/java/android/telephony/CellSignalStrengthLte.java @@ -263,29 +263,35 @@ public final class CellSignalStrengthLte extends CellSignalStrength implements P rssnrThresholds = sRssnrThresholds; rsrpOnly = false; } else { - mParametersUseForLevel = cc.getInt( - CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT); - if (DBG) { - Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel); + if (ss != null && ss.isUsingNonTerrestrialNetwork()) { + if (DBG) log("updateLevel: from NTN_LTE"); + mParametersUseForLevel = cc.getInt( + CarrierConfigManager.KEY_PARAMETERS_USED_FOR_NTN_LTE_SIGNAL_BAR_INT); + rsrpThresholds = cc.getIntArray( + CarrierConfigManager.KEY_NTN_LTE_RSRP_THRESHOLDS_INT_ARRAY); + rsrqThresholds = cc.getIntArray( + CarrierConfigManager.KEY_NTN_LTE_RSRQ_THRESHOLDS_INT_ARRAY); + rssnrThresholds = cc.getIntArray( + CarrierConfigManager.KEY_NTN_LTE_RSSNR_THRESHOLDS_INT_ARRAY); + } else { + mParametersUseForLevel = cc.getInt( + CarrierConfigManager.KEY_PARAMETERS_USED_FOR_LTE_SIGNAL_BAR_INT); + rsrpThresholds = cc.getIntArray( + CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY); + rsrqThresholds = cc.getIntArray( + CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY); + rssnrThresholds = cc.getIntArray( + CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY); } - rsrpThresholds = cc.getIntArray( - CarrierConfigManager.KEY_LTE_RSRP_THRESHOLDS_INT_ARRAY); if (rsrpThresholds == null) rsrpThresholds = sRsrpThresholds; + if (rsrqThresholds == null) rsrqThresholds = sRsrqThresholds; + if (rssnrThresholds == null) rssnrThresholds = sRssnrThresholds; if (DBG) { + Rlog.i(LOG_TAG, "Using signal strength level: " + mParametersUseForLevel); Rlog.i(LOG_TAG, "Applying LTE RSRP Thresholds: " + Arrays.toString(rsrpThresholds)); - } - rsrqThresholds = cc.getIntArray( - CarrierConfigManager.KEY_LTE_RSRQ_THRESHOLDS_INT_ARRAY); - if (rsrqThresholds == null) rsrqThresholds = sRsrqThresholds; - if (DBG) { Rlog.i(LOG_TAG, "Applying LTE RSRQ Thresholds: " + Arrays.toString(rsrqThresholds)); - } - rssnrThresholds = cc.getIntArray( - CarrierConfigManager.KEY_LTE_RSSNR_THRESHOLDS_INT_ARRAY); - if (rssnrThresholds == null) rssnrThresholds = sRssnrThresholds; - if (DBG) { Rlog.i(LOG_TAG, "Applying LTE RSSNR Thresholds: " + Arrays.toString(rssnrThresholds)); } |