diff options
533 files changed, 14317 insertions, 6330 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", @@ -37,3 +37,5 @@ per-file SQLITE_OWNERS = file:/SQLITE_OWNERS per-file *ravenwood* = file:ravenwood/OWNERS per-file *Ravenwood* = file:ravenwood/OWNERS + +per-file PERFORMANCE_OWNERS = file:/PERFORMANCE_OWNERS diff --git a/PERFORMANCE_OWNERS b/PERFORMANCE_OWNERS index 9452ea35f3e4..48a020130445 100644 --- a/PERFORMANCE_OWNERS +++ b/PERFORMANCE_OWNERS @@ -3,3 +3,6 @@ edgararriaga@google.com dualli@google.com carmenjackson@google.com philipcuadra@google.com +shayba@google.com +jdduke@google.com +shombert@google.com diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java index 73a80b1ac4dd..03891bbec56a 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java @@ -98,7 +98,10 @@ public final class BackgroundJobsController extends StateController { switch (action) { case Intent.ACTION_PACKAGE_RESTARTED: { synchronized (mLock) { - mPackageStoppedState.add(pkgUid, pkgName, Boolean.TRUE); + // ACTION_PACKAGE_RESTARTED doesn't always mean the app is placed and kept + // in the stopped state, so don't put TRUE in the cache. Remove any existing + // entry and rely on an explicit call to PackageManager's isStopped() API. + mPackageStoppedState.delete(pkgUid, pkgName); updateJobRestrictionsForUidLocked(pkgUid, false); } } @@ -311,9 +314,15 @@ public final class BackgroundJobsController extends StateController { if (mPackageStoppedState.contains(uid, packageName)) { return mPackageStoppedState.get(uid, packageName); } - final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid); - mPackageStoppedState.add(uid, packageName, isStopped); - return isStopped; + + try { + final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid); + mPackageStoppedState.add(uid, packageName, isStopped); + return isStopped; + } catch (IllegalArgumentException e) { + Slog.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName); + return false; + } } @GuardedBy("mLock") 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/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java index 1c29982dbd48..8ddbf691359f 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java +++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java @@ -36,6 +36,7 @@ import android.annotation.UserIdInt; import android.app.ActivityManager; import android.app.AlarmManager; import android.app.UidObserver; +import android.app.job.JobInfo; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.app.usage.UsageStatsManagerInternal.UsageEventListener; @@ -772,18 +773,23 @@ public final class QuotaController extends StateController { if (!jobStatus.shouldTreatAsExpeditedJob()) { // If quota is currently "free", then the job can run for the full amount of time, // regardless of bucket (hence using charging instead of isQuotaFreeLocked()). - if (mService.isBatteryCharging() - // The top and foreground cases here were added because apps in those states - // aren't really restricted and the work could be something the user is - // waiting for. Now that user-initiated jobs are a defined concept, we may - // not need these exemptions as much. However, UIJs are currently limited - // (as of UDC) to data transfer work. There may be other work that could - // rely on this exception. Once we add more UIJ types, we can re-evaluate - // the need for these exceptions. - // TODO: re-evaluate the need for these exceptions - || mTopAppCache.get(jobStatus.getSourceUid()) + if (mService.isBatteryCharging()) { + return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; + } + // The top and foreground cases here were added because apps in those states + // aren't really restricted and the work could be something the user is + // waiting for. Now that user-initiated jobs are a defined concept, we may + // not need these exemptions as much. However, UIJs are currently limited + // (as of UDC) to data transfer work. There may be other work that could + // rely on this exception. Once we add more UIJ types, we can re-evaluate + // the need for these exceptions. + // TODO: re-evaluate the need for these exceptions + final boolean isInPrivilegedState = mTopAppCache.get(jobStatus.getSourceUid()) || isTopStartedJobLocked(jobStatus) - || isUidInForeground(jobStatus.getSourceUid())) { + || isUidInForeground(jobStatus.getSourceUid()); + final boolean isJobImportant = jobStatus.getEffectivePriority() >= JobInfo.PRIORITY_HIGH + || (jobStatus.getFlags() & JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0; + if (isInPrivilegedState && isJobImportant) { return mConstants.RUNTIME_FREE_QUOTA_MAX_LIMIT_MS; } return getTimeUntilQuotaConsumedLocked( @@ -2549,7 +2555,25 @@ public final class QuotaController extends StateController { */ @Override public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) { - mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event).sendToTarget(); + // Skip posting a message to the handler for events we don't care about. + switch (event.getEventType()) { + case UsageEvents.Event.ACTIVITY_RESUMED: + case UsageEvents.Event.ACTIVITY_PAUSED: + case UsageEvents.Event.ACTIVITY_STOPPED: + case UsageEvents.Event.ACTIVITY_DESTROYED: + case UsageEvents.Event.USER_INTERACTION: + case UsageEvents.Event.CHOOSER_ACTION: + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + case UsageEvents.Event.NOTIFICATION_SEEN: + mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event) + .sendToTarget(); + break; + default: + if (DEBUG) { + Slog.d(TAG, "Dropping event " + event.getEventType()); + } + break; + } } } diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java index b8397d2cd1b4..357e139617ef 100644 --- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java +++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java @@ -316,8 +316,25 @@ public class InternalResourceService extends SystemService { */ @Override public void onUsageEvent(int userId, @NonNull UsageEvents.Event event) { - mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event) - .sendToTarget(); + // Skip posting a message to the handler for events we don't care about. + switch (event.getEventType()) { + case UsageEvents.Event.ACTIVITY_RESUMED: + case UsageEvents.Event.ACTIVITY_PAUSED: + case UsageEvents.Event.ACTIVITY_STOPPED: + case UsageEvents.Event.ACTIVITY_DESTROYED: + case UsageEvents.Event.USER_INTERACTION: + case UsageEvents.Event.CHOOSER_ACTION: + case UsageEvents.Event.NOTIFICATION_INTERRUPTION: + case UsageEvents.Event.NOTIFICATION_SEEN: + mHandler.obtainMessage(MSG_PROCESS_USAGE_EVENT, userId, 0, event) + .sendToTarget(); + break; + default: + if (DEBUG) { + Slog.d(TAG, "Dropping event " + event.getEventType()); + } + break; + } } }; 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 7731face84f5..d490c3f4d17c 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 @@ -4467,7 +4468,7 @@ package android.app { method public void onProvideKeyboardShortcuts(java.util.List<android.view.KeyboardShortcutGroup>, android.view.Menu, int); method public android.net.Uri onProvideReferrer(); method public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[]); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public void onRequestPermissionsResult(int, @NonNull String[], @NonNull int[], int); method @CallSuper protected void onRestart(); method protected void onRestoreInstanceState(@NonNull android.os.Bundle); method public void onRestoreInstanceState(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle); @@ -4509,14 +4510,14 @@ package android.app { method public android.view.DragAndDropPermissions requestDragAndDropPermissions(android.view.DragEvent); method public void requestFullscreenMode(int, @Nullable android.os.OutcomeReceiver<java.lang.Void,java.lang.Throwable>); method public final void requestPermissions(@NonNull String[], int); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public final void requestPermissions(@NonNull String[], int, int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public final void requestPermissions(@NonNull String[], int, int); method public final void requestShowKeyboardShortcuts(); method @Deprecated public boolean requestVisibleBehind(boolean); method public final boolean requestWindowFeature(int); method @NonNull public final <T extends android.view.View> T requireViewById(@IdRes int); method public final void runOnUiThread(Runnable); method public void setActionBar(@Nullable android.widget.Toolbar); - method public void setAllowCrossUidActivitySwitchFromBelow(boolean); + method @FlaggedApi("android.security.asm_restrictions_enabled") public void setAllowCrossUidActivitySwitchFromBelow(boolean); method public void setContentTransitionManager(android.transition.TransitionManager); method public void setContentView(@LayoutRes int); method public void setContentView(android.view.View); @@ -4557,7 +4558,7 @@ package android.app { method public void setVrModeEnabled(boolean, @NonNull android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException; method public boolean shouldDockBigOverlays(); method public boolean shouldShowRequestPermissionRationale(@NonNull String); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public boolean shouldShowRequestPermissionRationale(@NonNull String, int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public boolean shouldShowRequestPermissionRationale(@NonNull String, int); method public boolean shouldUpRecreateTask(android.content.Intent); method public boolean showAssist(android.os.Bundle); method @Deprecated public final void showDialog(int); @@ -9706,9 +9707,9 @@ package android.companion { method @RequiresPermission(android.Manifest.permission.DELIVER_COMPANION_MESSAGES) public final void detachSystemDataTransport(int) throws android.companion.DeviceNotAssociatedException; method @Nullable public final android.os.IBinder onBind(@NonNull android.content.Intent); method @Deprecated @MainThread public void onDeviceAppeared(@NonNull String); - method @Deprecated @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo); + method @MainThread public void onDeviceAppeared(@NonNull android.companion.AssociationInfo); method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull String); - method @Deprecated @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo); + method @MainThread public void onDeviceDisappeared(@NonNull android.companion.AssociationInfo); method @FlaggedApi("android.companion.device_presence") @MainThread public void onDeviceEvent(@NonNull android.companion.AssociationInfo, int); field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_APPEARED = 0; // 0x0 field @FlaggedApi("android.companion.device_presence") public static final int DEVICE_EVENT_BLE_DISAPPEARED = 1; // 0x1 @@ -9838,7 +9839,7 @@ package android.content { method public int describeContents(); method public void enforceCallingUid(); method @Nullable public String getAttributionTag(); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public int getDeviceId(); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public int getDeviceId(); method @Nullable public android.content.AttributionSource getNext(); method @Nullable public String getPackageName(); method public int getPid(); @@ -9854,7 +9855,7 @@ package android.content { ctor public AttributionSource.Builder(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource build(); method @NonNull public android.content.AttributionSource.Builder setAttributionTag(@Nullable String); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @NonNull public android.content.AttributionSource.Builder setDeviceId(int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @NonNull public android.content.AttributionSource.Builder setDeviceId(int); method @NonNull public android.content.AttributionSource.Builder setNext(@Nullable android.content.AttributionSource); method @FlaggedApi("android.permission.flags.set_next_attribution_source") @NonNull public android.content.AttributionSource.Builder setNextAttributionSource(@NonNull android.content.AttributionSource); method @NonNull public android.content.AttributionSource.Builder setPackageName(@Nullable String); @@ -18787,6 +18788,7 @@ package android.hardware.camera2 { method @NonNull public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableCaptureRequestKeys(); method @NonNull public java.util.List<android.hardware.camera2.CaptureResult.Key<?>> getAvailableCaptureResultKeys(); method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailablePhysicalCameraRequestKeys(); + method @FlaggedApi("com.android.internal.camera.flags.feature_combination_query") @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys(); method public java.util.List<android.hardware.camera2.CaptureRequest.Key<?>> getAvailableSessionKeys(); method @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeys(); method @NonNull public java.util.List<android.hardware.camera2.CameraCharacteristics.Key<?>> getKeysNeedingPermission(); @@ -18811,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; @@ -18939,6 +18942,7 @@ package android.hardware.camera2 { method @Deprecated public abstract void createReprocessableCaptureSessionByConfigurations(@NonNull android.hardware.camera2.params.InputConfiguration, @NonNull java.util.List<android.hardware.camera2.params.OutputConfiguration>, @NonNull android.hardware.camera2.CameraCaptureSession.StateCallback, @Nullable android.os.Handler) throws android.hardware.camera2.CameraAccessException; method public int getCameraAudioRestriction() throws android.hardware.camera2.CameraAccessException; method @NonNull public abstract String getId(); + method @FlaggedApi("com.android.internal.camera.flags.feature_combination_query") @NonNull public android.hardware.camera2.CameraCharacteristics getSessionCharacteristics(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; method @Deprecated public boolean isSessionConfigurationSupported(@NonNull android.hardware.camera2.params.SessionConfiguration) throws android.hardware.camera2.CameraAccessException; method public void setCameraAudioRestriction(int) throws android.hardware.camera2.CameraAccessException; field public static final int AUDIO_RESTRICTION_NONE = 0; // 0x0 @@ -19098,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 @@ -19163,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 @@ -19483,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; @@ -36903,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"; @@ -39298,7 +39307,7 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityStart(); method @NonNull public String getKeystoreAlias(); method public int getMaxUsageCount(); - method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); + method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); @@ -39306,7 +39315,7 @@ package android.security.keystore { method public boolean isDevicePropertiesAttestationIncluded(); method @NonNull public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); - method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified(); + method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isStrongBoxBacked(); method public boolean isUnlockedDeviceRequired(); @@ -39338,7 +39347,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMaxUsageCount(int); - method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...); + method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setMgf1Digests(@NonNull java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyGenParameterSpec.Builder setUnlockedDeviceRequired(boolean); @@ -39443,14 +39452,14 @@ package android.security.keystore { method @Nullable public java.util.Date getKeyValidityForOriginationEnd(); method @Nullable public java.util.Date getKeyValidityStart(); method public int getMaxUsageCount(); - method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); + method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public java.util.Set<java.lang.String> getMgf1Digests(); method public int getPurposes(); method @NonNull public String[] getSignaturePaddings(); method public int getUserAuthenticationType(); method public int getUserAuthenticationValidityDurationSeconds(); method public boolean isDigestsSpecified(); method public boolean isInvalidatedByBiometricEnrollment(); - method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public boolean isMgf1DigestsSpecified(); + method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public boolean isMgf1DigestsSpecified(); method public boolean isRandomizedEncryptionRequired(); method public boolean isUnlockedDeviceRequired(); method public boolean isUserAuthenticationRequired(); @@ -39472,7 +39481,7 @@ package android.security.keystore { method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityForOriginationEnd(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setKeyValidityStart(java.util.Date); method @NonNull public android.security.keystore.KeyProtection.Builder setMaxUsageCount(int); - method @FlaggedApi("MGF1_DIGEST_SETTER") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); + method @FlaggedApi("android.security.mgf1_digest_setter") @NonNull public android.security.keystore.KeyProtection.Builder setMgf1Digests(@Nullable java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setRandomizedEncryptionRequired(boolean); method @NonNull public android.security.keystore.KeyProtection.Builder setSignaturePaddings(java.lang.String...); method @NonNull public android.security.keystore.KeyProtection.Builder setUnlockedDeviceRequired(boolean); @@ -43358,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"; @@ -43372,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"; @@ -53868,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"; @@ -60851,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 f5b9b171b9a0..9a65388c2741 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -418,6 +418,7 @@ package android { field public static final int allowClearUserDataOnFailedRestore = 16844288; // 0x1010600 field public static final int gameSessionService = 16844373; // 0x1010655 field public static final int hotwordDetectionService = 16844326; // 0x1010626 + field @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public static final int isVirtualDeviceOnly; field public static final int isVrOnly = 16844152; // 0x1010578 field public static final int minExtensionVersion = 16844305; // 0x1010611 field public static final int playHomeTransitionSound = 16844358; // 0x1010646 @@ -3231,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); } @@ -3247,6 +3249,7 @@ package android.companion.virtual { method @Deprecated public int getDefaultNavigationPolicy(); method public int getDevicePolicy(int); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @Nullable public android.content.ComponentName getHomeComponent(); + method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @Nullable public android.content.ComponentName getInputMethodComponent(); method public int getLockState(); method @Nullable public String getName(); method @NonNull public java.util.Set<android.os.UserHandle> getUsersWithMatchingAccounts(); @@ -3280,6 +3283,7 @@ package android.companion.virtual { method @Deprecated @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setBlockedCrossTaskNavigations(@NonNull java.util.Set<android.content.ComponentName>); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setDevicePolicy(int, int); method @FlaggedApi("android.companion.virtual.flags.vdm_custom_home") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setHomeComponent(@Nullable android.content.ComponentName); + method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setInputMethodComponent(@Nullable android.content.ComponentName); method @NonNull @RequiresPermission(value=android.Manifest.permission.ADD_ALWAYS_UNLOCKED_DISPLAY, conditional=true) public android.companion.virtual.VirtualDeviceParams.Builder setLockState(int); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setName(@NonNull String); method @NonNull public android.companion.virtual.VirtualDeviceParams.Builder setUsersWithMatchingAccounts(@NonNull java.util.Set<android.os.UserHandle>); @@ -4005,7 +4009,7 @@ package android.content.pm { field public static final int DELETE_FAILED_OWNER_BLOCKED = -4; // 0xfffffffc field public static final int DELETE_KEEP_DATA = 1; // 0x1 field public static final int DELETE_SUCCEEDED = 1; // 0x1 - field @FlaggedApi("android.permission.flags.device_aware_permission_apis") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID"; + field @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID"; field public static final String EXTRA_REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_LEGACY_ACCESS_PERMISSION_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_NAMES = "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES"; field public static final String EXTRA_REQUEST_PERMISSIONS_RESULTS = "android.content.pm.extra.REQUEST_PERMISSIONS_RESULTS"; @@ -4086,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 @@ -4110,7 +4115,7 @@ package android.content.pm { public static interface PackageManager.OnPermissionsChangedListener { method public void onPermissionsChanged(int); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") public default void onPermissionsChanged(int, @NonNull String); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public default void onPermissionsChanged(int, @NonNull String); } public static final class PackageManager.UninstallCompleteCallback implements android.os.Parcelable { @@ -4192,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 @@ -4533,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 { @@ -10918,13 +10927,13 @@ package android.permission { method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_APP_HIBERNATION) public void onGetUnusedAppCount(@NonNull java.util.function.IntConsumer); method @BinderThread public abstract void onGrantOrUpgradeDefaultRuntimePermissions(@NonNull Runnable); method @Deprecated @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onOneTimePermissionSessionTimeout(@NonNull String, int); method @Deprecated @BinderThread public void onRestoreDelayedRuntimePermissionsBackup(@NonNull String, @NonNull android.os.UserHandle, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @Deprecated @BinderThread public void onRestoreRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermission(@NonNull String, @NonNull String, @NonNull Runnable); method @BinderThread public abstract void onRevokeRuntimePermissions(@NonNull java.util.Map<java.lang.String,java.util.List<java.lang.String>>, boolean, int, @NonNull String, @NonNull java.util.function.Consumer<java.util.Map<java.lang.String,java.util.List<java.lang.String>>>); method @Deprecated @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, @NonNull Runnable); - method @FlaggedApi("android.permission.flags.device_aware_permission_apis") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable); + method @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") @BinderThread public void onRevokeSelfPermissionsOnKill(@NonNull String, @NonNull java.util.List<java.lang.String>, int, @NonNull Runnable); method @Deprecated @BinderThread public abstract void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull String, @NonNull String, int, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onSetRuntimePermissionGrantStateByDeviceAdmin(@NonNull String, @NonNull android.permission.AdminPermissionControlParams, @NonNull java.util.function.Consumer<java.lang.Boolean>); method @BinderThread public void onStageAndApplyRuntimePermissionsBackup(@NonNull android.os.UserHandle, @NonNull java.io.InputStream, @NonNull Runnable); @@ -17170,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 { @@ -17290,6 +17302,10 @@ package android.view.displayhash { package android.view.inputmethod { + public final class InputMethodInfo implements android.os.Parcelable { + method @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") public boolean isVirtualDeviceOnly(); + } + public final class InputMethodManager { method @Nullable @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public android.view.inputmethod.InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull android.os.UserHandle); } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 98a78cfaa38c..39f2737dc880 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -259,6 +259,7 @@ package android.app { field public static final String OPSTR_ACTIVITY_RECOGNITION_SOURCE = "android:activity_recognition_source"; field public static final String OPSTR_MANAGE_ONGOING_CALLS = "android:manage_ongoing_calls"; field public static final String OPSTR_RECORD_AUDIO_HOTWORD = "android:record_audio_hotword"; + field public static final String OPSTR_RESERVED_FOR_TESTING = "android:reserved_for_testing"; field public static final String OPSTR_USE_ICC_AUTH_WITH_DEVICE_IDENTIFIER = "android:use_icc_auth_with_device_identifier"; field public static final int OP_COARSE_LOCATION = 0; // 0x0 field public static final int OP_RECORD_AUDIO = 27; // 0x1b @@ -902,7 +903,7 @@ package android.content { ctor public AttributionSource(int, @Nullable String, @Nullable String, @NonNull android.os.IBinder); ctor public AttributionSource(int, @Nullable String, @Nullable String, @Nullable java.util.Set<java.lang.String>, @Nullable android.content.AttributionSource); ctor @FlaggedApi("android.permission.flags.attribution_source_constructor") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], @Nullable android.content.AttributionSource); - ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); + ctor @FlaggedApi("android.permission.flags.device_aware_permission_apis_enabled") public AttributionSource(int, int, @Nullable String, @Nullable String, @NonNull android.os.IBinder, @Nullable String[], int, @Nullable android.content.AttributionSource); method public void enforceCallingPid(); } @@ -1165,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); } @@ -3617,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/accessibilityservice/AccessibilityGestureEvent.java b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java index 8e01779c6fac..15e29c2499f0 100644 --- a/core/java/android/accessibilityservice/AccessibilityGestureEvent.java +++ b/core/java/android/accessibilityservice/AccessibilityGestureEvent.java @@ -150,12 +150,13 @@ public final class AccessibilityGestureEvent implements Parcelable { private final int mDisplayId; private List<MotionEvent> mMotionEvents = new ArrayList<>(); -/** - * Constructs an AccessibilityGestureEvent to be dispatched to an accessibility service. - * @param gestureId the id number of the gesture. - * @param displayId the display on which this gesture was performed. - * @param motionEvents the motion events that lead to this gesture. - */ + /** + * Constructs an AccessibilityGestureEvent to be dispatched to an accessibility service. + * + * @param gestureId the id number of the gesture. + * @param displayId the display on which this gesture was performed. + * @param motionEvents the motion events that lead to this gesture. + */ public AccessibilityGestureEvent( int gestureId, int displayId, @NonNull List<MotionEvent> motionEvents) { mGestureId = gestureId; @@ -205,6 +206,29 @@ public final class AccessibilityGestureEvent implements Parcelable { return mMotionEvents; } + /** + * When we asynchronously use {@link AccessibilityGestureEvent}, we should make a copy, + * because motionEvent may be recycled before we use async. + * + * @hide + */ + @NonNull + public AccessibilityGestureEvent copyForAsync() { + return new AccessibilityGestureEvent(mGestureId, mDisplayId, + mMotionEvents.stream().map(MotionEvent::copy).toList()); + } + + /** + * After we use {@link AccessibilityGestureEvent} asynchronously, we should recycle the + * MotionEvent, avoid memory leaks. + * + * @hide + */ + public void recycle() { + mMotionEvents.forEach(MotionEvent::recycle); + mMotionEvents.clear(); + } + @NonNull @Override public String toString() { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index c52d27ea6608..ffed40538702 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -5572,7 +5572,7 @@ public class Activity extends ContextThemeWrapper * @see #shouldShowRequestPermissionRationale * @see Context#DEVICE_ID_DEFAULT */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public final void requestPermissions(@NonNull String[] permissions, int requestCode, int deviceId) { if (requestCode < 0) { @@ -5645,7 +5645,7 @@ public class Activity extends ContextThemeWrapper * * @see #requestPermissions */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults, int deviceId) { onRequestPermissionsResult(requestCode, permissions, grantResults); @@ -5678,7 +5678,7 @@ public class Activity extends ContextThemeWrapper * @see #requestPermissions * @see #onRequestPermissionsResult */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public boolean shouldShowRequestPermissionRationale(@NonNull String permission, int deviceId) { final PackageManager packageManager = getDeviceId() == deviceId ? getPackageManager() : createDeviceContext(deviceId).getPackageManager(); @@ -9387,6 +9387,7 @@ public class Activity extends ContextThemeWrapper * @param allowed {@code true} to disable the UID restrictions; {@code false} to revert back to * the default behaviour */ + @FlaggedApi(android.security.Flags.FLAG_ASM_RESTRICTIONS_ENABLED) public void setAllowCrossUidActivitySwitchFromBelow(boolean allowed) { ActivityClient.getInstance().setAllowCrossUidActivitySwitchFromBelow(mToken, allowed); } 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/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 71fe47e7b949..ec43184bd42c 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -16,8 +16,8 @@ package android.app; -import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED; import static android.permission.flags.Flags.FLAG_OP_ENABLE_MOBILE_DATA_BY_USER; +import static android.view.contentprotection.flags.Flags.FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED; import static java.lang.Long.max; @@ -1521,9 +1521,17 @@ public class AppOpsManager { */ public static final int OP_MEDIA_ROUTING_CONTROL = AppProtoEnums.APP_OP_MEDIA_ROUTING_CONTROL; + /** + * Op code for use by tests to avoid interfering history logs that the wider system might + * trigger. + * + * @hide + */ + public static final int OP_RESERVED_FOR_TESTING = AppProtoEnums.APP_OP_RESERVED_FOR_TESTING; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 141; + public static final int _NUM_OP = 142; /** * All app ops represented as strings. @@ -1671,6 +1679,7 @@ public class AppOpsManager { OPSTR_CREATE_ACCESSIBILITY_OVERLAY, OPSTR_MEDIA_ROUTING_CONTROL, OPSTR_ENABLE_MOBILE_DATA_BY_USER, + OPSTR_RESERVED_FOR_TESTING, }) public @interface AppOpString {} @@ -2330,6 +2339,17 @@ public class AppOpsManager { public static final String OPSTR_ENABLE_MOBILE_DATA_BY_USER = "android:enable_mobile_data_by_user"; + /** + * Reserved for use by appop tests so that operations done legitimately by the platform don't + * interfere with expected results. Platform code should never use this. + * + * @hide + */ + @TestApi + @SuppressLint("UnflaggedApi") + public static final String OPSTR_RESERVED_FOR_TESTING = + "android:reserved_for_testing"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -2887,6 +2907,8 @@ public class AppOpsManager { .setPermission(Manifest.permission.MEDIA_ROUTING_CONTROL).build(), new AppOpInfo.Builder(OP_ENABLE_MOBILE_DATA_BY_USER, OPSTR_ENABLE_MOBILE_DATA_BY_USER, "ENABLE_MOBILE_DATA_BY_USER").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), + new AppOpInfo.Builder(OP_RESERVED_FOR_TESTING, OPSTR_RESERVED_FOR_TESTING, + "OP_RESERVED_FOR_TESTING").setDefaultMode(AppOpsManager.MODE_ALLOWED).build(), }; // The number of longs needed to form a full bitmask of app ops 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/CompanionDeviceService.java b/core/java/android/companion/CompanionDeviceService.java index c99a45764de7..4d0267ca0cbb 100644 --- a/core/java/android/companion/CompanionDeviceService.java +++ b/core/java/android/companion/CompanionDeviceService.java @@ -40,7 +40,6 @@ import java.util.concurrent.Executor; /** * A service that receives calls from the system with device events. - * See {@link #onDeviceEvent(AssociationInfo, int)}. * * <p> * Companion applications must create a service that {@code extends} @@ -311,10 +310,7 @@ public abstract class CompanionDeviceService extends Service { * Called by system whenever a device associated with this app is connected. * * @param associationInfo A record for the companion device. - * - * @deprecated please override {@link #onDeviceEvent(AssociationInfo, int)} instead. */ - @Deprecated @MainThread public void onDeviceAppeared(@NonNull AssociationInfo associationInfo) { if (!associationInfo.isSelfManaged()) { @@ -326,10 +322,7 @@ public abstract class CompanionDeviceService extends Service { * Called by system whenever a device associated with this app is disconnected. * * @param associationInfo A record for the companion device. - * - * @deprecated please override {@link #onDeviceEvent(AssociationInfo, int)} instead. */ - @Deprecated @MainThread public void onDeviceDisappeared(@NonNull AssociationInfo associationInfo) { if (!associationInfo.isSelfManaged()) { 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/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java index 0d73e44f5197..0253ddd93a44 100644 --- a/core/java/android/companion/virtual/VirtualDeviceParams.java +++ b/core/java/android/companion/virtual/VirtualDeviceParams.java @@ -258,6 +258,7 @@ public final class VirtualDeviceParams implements Parcelable { // Mapping of @PolicyType to @DevicePolicy @NonNull private final SparseIntArray mDevicePolicies; @Nullable private final ComponentName mHomeComponent; + @Nullable private final ComponentName mInputMethodComponent; @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs; @Nullable private final IVirtualSensorCallback mVirtualSensorCallback; private final int mAudioPlaybackSessionId; @@ -273,6 +274,7 @@ public final class VirtualDeviceParams implements Parcelable { @Nullable String name, @NonNull SparseIntArray devicePolicies, @Nullable ComponentName homeComponent, + @Nullable ComponentName inputMethodComponent, @NonNull List<VirtualSensorConfig> virtualSensorConfigs, @Nullable IVirtualSensorCallback virtualSensorCallback, int audioPlaybackSessionId, @@ -289,6 +291,7 @@ public final class VirtualDeviceParams implements Parcelable { mName = name; mDevicePolicies = Objects.requireNonNull(devicePolicies); mHomeComponent = homeComponent; + mInputMethodComponent = inputMethodComponent; mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs); mVirtualSensorCallback = virtualSensorCallback; mAudioPlaybackSessionId = audioPlaybackSessionId; @@ -312,6 +315,7 @@ public final class VirtualDeviceParams implements Parcelable { mAudioPlaybackSessionId = parcel.readInt(); mAudioRecordingSessionId = parcel.readInt(); mHomeComponent = parcel.readTypedObject(ComponentName.CREATOR); + mInputMethodComponent = parcel.readTypedObject(ComponentName.CREATOR); } /** @@ -336,6 +340,18 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Returns the custom component used as input method on all displays owned by this virtual + * device. + * + * @see Builder#setInputMethodComponent + */ + @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) + @Nullable + public ComponentName getInputMethodComponent() { + return mInputMethodComponent; + } + + /** * Returns the user handles with matching managed accounts on the remote device to which * this virtual device is streaming. * @@ -532,6 +548,7 @@ public final class VirtualDeviceParams implements Parcelable { dest.writeInt(mAudioPlaybackSessionId); dest.writeInt(mAudioRecordingSessionId); dest.writeTypedObject(mHomeComponent, flags); + dest.writeTypedObject(mInputMethodComponent, flags); } @Override @@ -563,6 +580,8 @@ public final class VirtualDeviceParams implements Parcelable { && Objects.equals(mActivityPolicyExemptions, that.mActivityPolicyExemptions) && mDefaultActivityPolicy == that.mDefaultActivityPolicy && Objects.equals(mName, that.mName) + && Objects.equals(mHomeComponent, that.mHomeComponent) + && Objects.equals(mInputMethodComponent, that.mInputMethodComponent) && mAudioPlaybackSessionId == that.mAudioPlaybackSessionId && mAudioRecordingSessionId == that.mAudioRecordingSessionId; } @@ -572,7 +591,8 @@ public final class VirtualDeviceParams implements Parcelable { int hashCode = Objects.hash( mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions, mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName, - mDevicePolicies, mHomeComponent, mAudioPlaybackSessionId, mAudioRecordingSessionId); + mDevicePolicies, mHomeComponent, mInputMethodComponent, mAudioPlaybackSessionId, + mAudioRecordingSessionId); for (int i = 0; i < mDevicePolicies.size(); i++) { hashCode = 31 * hashCode + mDevicePolicies.keyAt(i); hashCode = 31 * hashCode + mDevicePolicies.valueAt(i); @@ -593,6 +613,7 @@ public final class VirtualDeviceParams implements Parcelable { + " mName=" + mName + " mDevicePolicies=" + mDevicePolicies + " mHomeComponent=" + mHomeComponent + + " mInputMethodComponent=" + mInputMethodComponent + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId + " mAudioRecordingSessionId=" + mAudioRecordingSessionId + ")"; @@ -612,6 +633,8 @@ public final class VirtualDeviceParams implements Parcelable { pw.println(prefix + "mActivityPolicyExemptions=" + mActivityPolicyExemptions); pw.println(prefix + "mDevicePolicies=" + mDevicePolicies); pw.println(prefix + "mVirtualSensorConfigs=" + mVirtualSensorConfigs); + pw.println(prefix + "mHomeComponent=" + mHomeComponent); + pw.println(prefix + "mInputMethodComponent=" + mInputMethodComponent); pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId); pw.println(prefix + "mAudioRecordingSessionId=" + mAudioRecordingSessionId); } @@ -644,16 +667,17 @@ public final class VirtualDeviceParams implements Parcelable { private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED; private boolean mDefaultActivityPolicyConfigured = false; @Nullable private String mName; - @NonNull private SparseIntArray mDevicePolicies = new SparseIntArray(); + @NonNull private final SparseIntArray mDevicePolicies = new SparseIntArray(); private int mAudioPlaybackSessionId = AUDIO_SESSION_ID_GENERATE; private int mAudioRecordingSessionId = AUDIO_SESSION_ID_GENERATE; - @NonNull private List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>(); + @NonNull private final List<VirtualSensorConfig> mVirtualSensorConfigs = new ArrayList<>(); @Nullable private Executor mVirtualSensorCallbackExecutor; @Nullable private VirtualSensorCallback mVirtualSensorCallback; @Nullable private Executor mVirtualSensorDirectChannelCallbackExecutor; @Nullable private VirtualSensorDirectChannelCallback mVirtualSensorDirectChannelCallback; @Nullable private ComponentName mHomeComponent; + @Nullable private ComponentName mInputMethodComponent; private static class VirtualSensorCallbackDelegate extends IVirtualSensorCallback.Stub { @NonNull @@ -749,6 +773,28 @@ public final class VirtualDeviceParams implements Parcelable { } /** + * Specifies a component to be used as input method on all displays owned by this virtual + * device. + * + * @param inputMethodComponent The component name to be used as input method. Must comply to + * all general input method requirements described in the guide to + * <a href="{@docRoot}guide/topics/text/creating-input-method.html"> + * Creating an Input Method</a>. If the given component is not available for any user that + * may interact with the virtual device, then there will effectively be no IME on this + * device's displays for that user. + * + * @see android.inputmethodservice.InputMethodService + * @attr ref android.R.styleable#InputMethod_isVirtualDeviceOnly + * @attr ref android.R.styleable#InputMethod_showInInputMethodPicker + */ + @FlaggedApi(Flags.FLAG_VDM_CUSTOM_IME) + @NonNull + public Builder setInputMethodComponent(@Nullable ComponentName inputMethodComponent) { + mInputMethodComponent = inputMethodComponent; + return this; + } + + /** * Sets the user handles with matching managed accounts on the remote device to which * this virtual device is streaming. The caller is responsible for verifying the presence * and legitimacy of a matching managed account on the remote device. @@ -1136,6 +1182,7 @@ public final class VirtualDeviceParams implements Parcelable { mName, mDevicePolicies, mHomeComponent, + mInputMethodComponent, mVirtualSensorConfigs, virtualSensorCallbackDelegate, mAudioPlaybackSessionId, diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig index f0477d47f723..ce2490b8efb8 100644 --- a/core/java/android/companion/virtual/flags.aconfig +++ b/core/java/android/companion/virtual/flags.aconfig @@ -39,6 +39,13 @@ flag { } flag { + name: "vdm_custom_ime" + namespace: "virtual_devices" + description: "Enable custom IME API" + bug: "287269288" +} + +flag { name: "vdm_custom_home" namespace: "virtual_devices" description: "Enable custom home API" diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java index b2074a6e7309..a1357c91b2cf 100644 --- a/core/java/android/content/AttributionSource.java +++ b/core/java/android/content/AttributionSource.java @@ -173,7 +173,7 @@ public final class AttributionSource implements Parcelable { /** @hide */ @TestApi - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public AttributionSource(int uid, int pid, @Nullable String packageName, @Nullable String attributionTag, @NonNull IBinder token, @Nullable String[] renouncedPermissions, @@ -539,7 +539,7 @@ public final class AttributionSource implements Parcelable { * <p> * This device ID is used for permissions checking during attribution source validation. */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public int getDeviceId() { return mAttributionSourceState.deviceId; } @@ -727,7 +727,7 @@ public final class AttributionSource implements Parcelable { * * @return the builder */ - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public @NonNull Builder setDeviceId(int deviceId) { checkNotUsed(); mBuilderFieldsSet |= 0x12; diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index c86ccfdaa7d4..c7a75ed5ea9c 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -117,6 +117,7 @@ import java.util.Objects; * developer guide.</p> * </div> */ +@android.ravenwood.annotation.RavenwoodKeepPartialClass public abstract class ContentProvider implements ContentInterface, ComponentCallbacks2 { private static final String TAG = "ContentProvider"; @@ -2781,6 +2782,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep private Uri maybeGetUriWithoutUserId(Uri uri) { if (mSingleUser) { return uri; @@ -2789,6 +2791,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static int getUserIdFromAuthority(String auth, int defaultUserId) { if (auth == null) return defaultUserId; int end = auth.lastIndexOf('@'); @@ -2803,17 +2806,20 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static int getUserIdFromAuthority(String auth) { return getUserIdFromAuthority(auth, UserHandle.USER_CURRENT); } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static int getUserIdFromUri(Uri uri, int defaultUserId) { if (uri == null) return defaultUserId; return getUserIdFromAuthority(uri.getAuthority(), defaultUserId); } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static int getUserIdFromUri(Uri uri) { return getUserIdFromUri(uri, UserHandle.USER_CURRENT); } @@ -2824,6 +2830,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * @hide */ @TestApi + @android.ravenwood.annotation.RavenwoodKeep public @NonNull static UserHandle getUserHandleFromUri(@NonNull Uri uri) { return UserHandle.of(getUserIdFromUri(uri, Process.myUserHandle().getIdentifier())); } @@ -2834,6 +2841,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall * If there is no userId in the authority, it symply returns the argument * @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static String getAuthorityWithoutUserId(String auth) { if (auth == null) return null; int end = auth.lastIndexOf('@'); @@ -2841,6 +2849,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static Uri getUriWithoutUserId(Uri uri) { if (uri == null) return null; Uri.Builder builder = uri.buildUpon(); @@ -2849,6 +2858,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall } /** @hide */ + @android.ravenwood.annotation.RavenwoodKeep public static boolean uriHasUserId(Uri uri) { if (uri == null) return false; return !TextUtils.isEmpty(uri.getUserInfo()); @@ -2872,6 +2882,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall */ @NonNull @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @android.ravenwood.annotation.RavenwoodKeep public static Uri createContentUriForUser( @NonNull Uri contentUri, @NonNull UserHandle userHandle) { if (!ContentResolver.SCHEME_CONTENT.equals(contentUri.getScheme())) { @@ -2898,6 +2909,7 @@ public abstract class ContentProvider implements ContentInterface, ComponentCall /** @hide */ @UnsupportedAppUsage + @android.ravenwood.annotation.RavenwoodKeep public static Uri maybeAddUserId(Uri uri, int userId) { if (uri == null) return null; if (userId != UserHandle.USER_CURRENT diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index 457fd63fa3d8..e395127dfaf3 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -379,7 +379,8 @@ public class PackageInstaller { /** * If true, the requestor of the unarchival has specified that the app should be unarchived - * for all users. + * for all users. Sent as part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} + * intent. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ALL_USERS = @@ -396,6 +397,9 @@ public class PackageInstaller { * with an intent for a corresponding follow-up action (e.g. storage clearing dialog) or a * failure dialog. * + * <p> Used as part of {@link #requestUnarchive} to return the status of the unarchival through + * the {@link IntentSender}. + * * @see #requestUnarchive */ @FlaggedApi(Flags.FLAG_ARCHIVING) @@ -704,7 +708,8 @@ public class PackageInstaller { /** * The installer responsible for the unarchival is disabled. * - * <p> Should only be used by the system. + * <p> The system will return this status if appropriate. Installers do not need to verify for + * this error. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_INSTALLER_DISABLED = 4; @@ -712,7 +717,8 @@ public class PackageInstaller { /** * The installer responsible for the unarchival has been uninstalled * - * <p> Should only be used by the system. + * <p> The system will return this status if appropriate. Installers do not need to verify for + * this error. */ @FlaggedApi(Flags.FLAG_ARCHIVING) public static final int UNARCHIVAL_ERROR_INSTALLER_UNINSTALLED = 5; @@ -1238,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, @@ -1948,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 @@ -1979,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..e2243292ab8d 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -731,7 +731,7 @@ public abstract class PackageManager { * @see VirtualDeviceManager.VirtualDevice#getPersistentDeviceId() * @see VirtualDeviceManager#PERSISTENT_DEVICE_ID_DEFAULT */ - @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) default void onPermissionsChanged(int uid, @NonNull String persistentDeviceId) { Objects.requireNonNull(persistentDeviceId); if (Objects.equals(persistentDeviceId, @@ -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 @@ -4875,7 +4893,7 @@ public abstract class PackageManager { * @hide */ @SystemApi - @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(android.permission.flags.Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public static final String EXTRA_REQUEST_PERMISSIONS_DEVICE_ID = "android.content.pm.extra.REQUEST_PERMISSIONS_DEVICE_ID"; 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/database/ContentObserver.java b/core/java/android/database/ContentObserver.java index 39c9400e7064..4322bedafdeb 100644 --- a/core/java/android/database/ContentObserver.java +++ b/core/java/android/database/ContentObserver.java @@ -31,6 +31,7 @@ import android.os.UserHandle; import java.util.Arrays; import java.util.Collection; +import java.util.concurrent.Executor; /** * Receives call backs for changes to content. @@ -54,6 +55,7 @@ public abstract class ContentObserver { private Transport mTransport; // guarded by mLock Handler mHandler; + private final Executor mExecutor; /** * Creates a content observer. @@ -62,6 +64,18 @@ public abstract class ContentObserver { */ public ContentObserver(Handler handler) { mHandler = handler; + mExecutor = null; + } + + /** + * @hide + * Creates a content observer with an executor. + * + * @param executor The executor to run {@link #onChange} on, or null if none. + * @param unused a second argument to avoid source incompatibility. + */ + public ContentObserver(@Nullable Executor executor, int unused) { + mExecutor = executor; } /** @@ -306,12 +320,19 @@ public abstract class ContentObserver { /** @hide */ public final void dispatchChange(boolean selfChange, @NonNull Collection<Uri> uris, @NotifyFlags int flags, @UserIdInt int userId) { - if (mHandler == null) { - onChange(selfChange, uris, flags, userId); - } else { + if (mExecutor != null) { + mExecutor.execute(() -> { + onChange(selfChange, uris, flags, userId); + }); + } else if (mHandler != null) { + // Supporting Handler directly rather than wrapping in a HandlerExecutor + // avoids introducing a RejectedExecutionException for legacy code when + // the post fails. mHandler.post(() -> { onChange(selfChange, uris, flags, userId); }); + } else { + onChange(selfChange, uris, flags, userId); } } diff --git a/core/java/android/database/ExecutorContentObserver.java b/core/java/android/database/ExecutorContentObserver.java new file mode 100644 index 000000000000..3ea807dc305d --- /dev/null +++ b/core/java/android/database/ExecutorContentObserver.java @@ -0,0 +1,38 @@ +/* + * 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.database; + +import android.annotation.Nullable; + +import java.util.concurrent.Executor; + +/** + * @hide + * + * Receives callbacks for changes to content. + * Must be implemented by objects which are added to a {@link ContentObservable}. + */ +public abstract class ExecutorContentObserver extends ContentObserver { + /** + * Creates a content observer that uses an executor for change handling. + * + * @param executor The executor to run {@link #onChange} on, or null if none. + */ + public ExecutorContentObserver(@Nullable Executor executor) { + super(executor, 0); + } +} diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java index bb8924c3919a..3ab889ddfa5d 100644 --- a/core/java/android/hardware/camera2/CameraCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraCharacteristics.java @@ -35,6 +35,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.camera.flags.Flags; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; @@ -206,6 +207,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri private List<CameraCharacteristics.Key<?>> mKeysNeedingPermission; private List<CaptureRequest.Key<?>> mAvailableRequestKeys; private List<CaptureRequest.Key<?>> mAvailableSessionKeys; + private List<CameraCharacteristics.Key<?>> mAvailableSessionCharacteristicsKeys; private List<CaptureRequest.Key<?>> mAvailablePhysicalRequestKeys; private List<CaptureResult.Key<?>> mAvailableResultKeys; private ArrayList<RecommendedStreamConfigurationMap> mRecommendedConfigurations; @@ -546,6 +548,27 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri } /** + * <p>Get the keys in Camera Characteristics whose values are capture session specific. + * The session specific characteristics can be acquired by calling + * CameraDevice.getSessionCharacteristics(). </p> + * + * <p>Note that getAvailableSessionKeys returns the CaptureRequest keys that are difficult to + * apply per-frame, whereas this function returns CameraCharacteristics keys that are dependent + * on a particular SessionConfiguration.</p> + * + * @return List of CameraCharacteristic keys containing characterisitics specific to a session + * configuration. For Android 15, this list only contains CONTROL_ZOOM_RATIO_RANGE. + */ + @NonNull + @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY) + public List<CameraCharacteristics.Key<?>> getAvailableSessionCharacteristicsKeys() { + if (mAvailableSessionCharacteristicsKeys == null) { + mAvailableSessionCharacteristicsKeys = Arrays.asList(CONTROL_ZOOM_RATIO_RANGE); + } + return mAvailableSessionCharacteristicsKeys; + } + + /** * <p>Returns a subset of {@link #getAvailableCaptureRequestKeys} keys that can * be overridden for physical devices backing a logical multi-camera.</p> * @@ -1332,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 @@ -3273,7 +3317,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * stream configurations are the same as for applications targeting SDK version older than * 31.</p> * <p>Refer to {@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} and - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations } + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">the table</a> * for additional mandatory stream configurations on a per-capability basis.</p> * <p>*1: For JPEG format, the sizes may be restricted by below conditions:</p> * <ul> @@ -3392,11 +3436,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link android.hardware.camera2.CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL } * and {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES }. * This is an app-readable conversion of the mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations guideline} based on specific device level and capabilities. + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">guideline</a>. + * based on specific device level and capabilities. * Clients can use the array as a quick reference to find an appropriate camera stream * combination. * As per documentation, the stream combinations with given PREVIEW, RECORD and @@ -3425,11 +3470,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>An array of mandatory concurrent stream combinations. * This is an app-readable conversion of the concurrent mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#concurrent-stream-guaranteed-configurations guideline} for each device which has its Id present in the set returned by + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#concurrent-stream-guaranteed-configurations">guideline</a> + * for each device which has its Id present in the set returned by * {@link android.hardware.camera2.CameraManager#getConcurrentCameraIds }. * Clients can use the array as a quick reference to find an appropriate camera stream * combination. @@ -3534,7 +3580,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>If a camera device supports multi-resolution output streams for a particular format, for * each of its mandatory stream combinations, the camera device will support using a * MultiResolutionImageReader for the MAXIMUM stream of supported formats. Refer to - * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs } + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a> * for additional details.</p> * <p>To use multi-resolution input streams, the supported formats can be queried by {@link android.hardware.camera2.params.MultiResolutionStreamConfigurationMap#getInputFormats }. * A reprocessable CameraCaptureSession can then be created using an {@link android.hardware.camera2.params.InputConfiguration InputConfiguration} constructed with @@ -3543,7 +3589,7 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@code YUV} output, or multi-resolution {@code PRIVATE} input and multi-resolution * {@code PRIVATE} output, {@code JPEG} and {@code YUV} are guaranteed to be supported * multi-resolution output stream formats. Refer to - * {@link android.hardware.camera2.CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs }} + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-additional-guaranteed-combinations-with-multiresolutionoutputs">the table</a> * for details about the additional mandatory stream combinations in this case.</p> * <p><b>Optional</b> - The value for this key may be {@code null} on some devices.</p> */ @@ -3656,11 +3702,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@link android.hardware.camera2.CaptureRequest } has {@link CaptureRequest#SENSOR_PIXEL_MODE android.sensor.pixelMode} set * to {@link android.hardware.camera2.CameraMetadata#SENSOR_PIXEL_MODE_MAXIMUM_RESOLUTION }. * This is an app-readable conversion of the maximum resolution mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors guideline} for each device which has the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#additional-guaranteed-combinations-for-ultra-high-resolution-sensors">guideline</a> + * for each device which has the * {@link android.hardware.camera2.CameraMetadata#REQUEST_AVAILABLE_CAPABILITIES_ULTRA_HIGH_RESOLUTION_SENSOR } * capability. * Clients can use the array as a quick reference to find an appropriate camera stream @@ -3683,11 +3730,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * 10-bit output capability * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } * This is an app-readable conversion of the 10 bit output mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations guideline} for each device which has the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">guideline</a> + * for each device which has the * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_DYNAMIC_RANGE_TEN_BIT } * capability. * Clients can use the array as a quick reference to find an appropriate camera stream @@ -3708,11 +3756,12 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * {@code PREVIEW_STABILIZATION} in {@link CameraCharacteristics#CONTROL_AVAILABLE_VIDEO_STABILIZATION_MODES android.control.availableVideoStabilizationModes}. * This is an app-readable conversion of the preview stabilization mandatory stream * combination - * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations tables}.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">tables</a>.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#preview-stabilization-guaranteed-stream-configurations guideline} for each device which supports {@code PREVIEW_STABILIZATION} + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#preview-stabilization-guaranteed-stream-configurations">guideline</a> + * for each device which supports {@code PREVIEW_STABILIZATION} * Clients can use the array as a quick reference to find an appropriate camera stream * combination. * The mandatory stream combination array will be {@code null} in case the device does not @@ -3785,8 +3834,8 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri * <p>The guaranteed stream combinations related to stream use case for a camera device with * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } * capability is documented in the camera device - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline}. The application is strongly recommended to use one of the guaranteed stream - * combinations. + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a>. + * The application is strongly recommended to use one of the guaranteed stream combinations. * If the application creates a session with a stream combination not in the guaranteed * list, or with mixed DEFAULT and non-DEFAULT use cases within the same session, * the camera device may ignore some stream use cases due to hardware constraints @@ -3822,11 +3871,13 @@ public final class CameraCharacteristics extends CameraMetadata<CameraCharacteri /** * <p>An array of mandatory stream combinations with stream use cases. * This is an app-readable conversion of the mandatory stream combination - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations tables} with each stream's use case being set.</p> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">tables</a> + * with each stream's use case being set.</p> * <p>The array of * {@link android.hardware.camera2.params.MandatoryStreamCombination combinations} is * generated according to the documented - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations guideline} for a camera device with + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guildeline</a> + * for a camera device with * {@link android.hardware.camera2.CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES_STREAM_USE_CASE } * capability. * The mandatory stream combination array will be {@code null} in case the device doesn't diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index 58cba414fc47..3835c5201946 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -16,6 +16,7 @@ package android.hardware.camera2; +import android.annotation.FlaggedApi; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; @@ -28,6 +29,8 @@ import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Handler; import android.view.Surface; +import com.android.internal.camera.flags.Flags; + import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.List; @@ -1412,7 +1415,35 @@ public abstract class CameraDevice implements AutoCloseable { throw new UnsupportedOperationException("Subclasses must override this method"); } - /** + /** + * <p>Get camera characteristics for a particular session configuration by the camera device.</p> + * + * <p>The camera characteristics returned here is typically more limited than the characteristics + * returned from {@link CameraManager#getCameraCharacteristics}. The keys that have more limited + * values are listed in + * {@link CameraCharacteristics#getAvailableSessionCharacteristicsKeys}. </p> + * + * <p>Other than that, the characteristics returned here can be used in the same way as those + * returned from {@link CameraManager#getCameraCharacteristics}.</p> + * + * @param sessionConfig : The session configuration for which characteristics are fetched. + * @return CameraCharacteristics specific to a given session configuration. + * @throws UnsupportedOperationException if the query operation is not supported by the camera + * device + * @throws IllegalArgumentException if the session configuration is invalid + * @throws CameraAccessException if the camera device is no longer connected or has + * encountered a fatal error + * @throws IllegalStateException if the camera device has been closed + * @see android.hardware.camera2.CameraCharacteristics#getAvailableSessionCharacteristicsKeys + */ + @NonNull + @FlaggedApi(Flags.FLAG_FEATURE_COMBINATION_QUERY) + public CameraCharacteristics getSessionCharacteristics( + @NonNull SessionConfiguration sessionConfig) throws CameraAccessException { + throw new UnsupportedOperationException("Subclasses must override this method"); + } + + /** * A callback objects for receiving updates about the state of a camera device. * * <p>A callback instance must be provided to the {@link CameraManager#openCamera} method to diff --git a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java index d4d1ab373157..8196bf505e02 100644 --- a/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java +++ b/core/java/android/hardware/camera2/CameraExtensionCharacteristics.java @@ -1058,6 +1058,12 @@ public final class CameraExtensionCharacteristics { * <p>The set returned is not modifiable, so any attempts to modify it will throw * a {@code UnsupportedOperationException}.</p> * + * <p>Devices launching on Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} + * or newer versions are required to support {@link CaptureRequest#CONTROL_AF_MODE}, + * {@link CaptureRequest#CONTROL_AF_REGIONS}, {@link CaptureRequest#CONTROL_AF_TRIGGER}, + * {@link CaptureRequest#CONTROL_ZOOM_RATIO} for + * {@link CameraExtensionCharacteristics#EXTENSION_NIGHT}.</p> + * * @param extension the extension type * * @return non-modifiable set of capture keys supported by camera extension session initialized @@ -1139,6 +1145,12 @@ public final class CameraExtensionCharacteristics { * and the {@link CameraExtensionSession.ExtensionCaptureCallback#onCaptureResultAvailable} * callback will not be fired.</p> * + * <p>Devices launching on Android {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} + * or newer versions are required to support {@link CaptureResult#CONTROL_AF_MODE}, + * {@link CaptureResult#CONTROL_AF_REGIONS}, {@link CaptureResult#CONTROL_AF_TRIGGER}, + * {@link CaptureResult#CONTROL_AF_STATE}, {@link CaptureResult#CONTROL_ZOOM_RATIO} for + * {@link CameraExtensionCharacteristics#EXTENSION_NIGHT}.</p> + * * @param extension the extension type * * @return non-modifiable set of capture result keys supported by camera extension session 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..93fbe8aee8d4 100644 --- a/core/java/android/hardware/camera2/CameraMetadata.java +++ b/core/java/android/hardware/camera2/CameraMetadata.java @@ -1216,8 +1216,7 @@ public abstract class CameraMetadata<TKey> { * <ul> * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li> * <li>All mandatory stream combinations for this specific capability as per - * documentation - * {@link android.hardware.camera2.CameraDevice#10-bit-output-additional-guaranteed-configurations }</li> + * <a href="CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li> * <li>In case the device is not able to capture some combination of supported * standard 8-bit and/or 10-bit dynamic range profiles within the same capture request, * then those constraints must be listed in @@ -1256,8 +1255,8 @@ public abstract class CameraMetadata<TKey> { * </ul> * <p>{@link android.hardware.camera2.CameraCharacteristics#SCALER_AVAILABLE_STREAM_USE_CASES } * lists all of the supported stream use cases.</p> - * <p>Refer to - * {@link android.hardware.camera2.CameraDevice#stream-use-case-capability-additional-guaranteed-configurations } + * <p>Refer to the + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#stream-use-case-capability-additional-guaranteed-configurations">guideline</a> * for the mandatory stream combinations involving stream use cases, which can also be * queried via {@link android.hardware.camera2.params.MandatoryStreamCombination }.</p> * @see CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES @@ -1756,9 +1755,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device does not have enough capabilities to qualify as a <code>FULL</code> device or * better.</p> - * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#limited-level-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <p>Only the stream configurations listed in the <code>LEGACY</code> and <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#limited-level-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>All <code>LIMITED</code> devices support the <code>BACKWARDS_COMPATIBLE</code> capability, indicating basic * support for color image capture. The only exception is that the device may * alternatively support only the <code>DEPTH_OUTPUT</code> capability, if it can only output depth @@ -1784,9 +1783,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device is capable of supporting advanced imaging applications.</p> - * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#full-level-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <p>The stream configurations listed in the <code>FULL</code>, <code>LEGACY</code> and <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#full-level-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>A <code>FULL</code> device will support below capabilities:</p> * <ul> * <li><code>BURST_CAPTURE</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains @@ -1814,9 +1813,9 @@ public abstract class CameraMetadata<TKey> { /** * <p>This camera device is running in backward compatibility mode.</p> - * <p>Only the stream configurations listed in the <code>LEGACY</code> table in the - * {@link android.hardware.camera2.CameraDevice#legacy-level-guaranteed-configurations } - * documentation are supported.</p> + * <p>Only the stream configurations listed in the <code>LEGACY</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#legacy-level-guaranteed-configurations">table</a> + * in the documentation are supported.</p> * <p>A <code>LEGACY</code> device does not support per-frame control, manual sensor control, manual * post-processing, arbitrary cropping regions, and has relaxed performance constraints. * No additional capabilities beyond <code>BACKWARD_COMPATIBLE</code> will ever be listed by a @@ -1839,9 +1838,9 @@ public abstract class CameraMetadata<TKey> { * <p>This camera device is capable of YUV reprocessing and RAW data capture, in addition to * FULL-level capabilities.</p> * <p>The stream configurations listed in the <code>LEVEL_3</code>, <code>RAW</code>, <code>FULL</code>, <code>LEGACY</code> and - * <code>LIMITED</code> tables in the - * {@link android.hardware.camera2.CameraDevice#level-3-additional-guaranteed-configurations } - * documentation are guaranteed to be supported.</p> + * <code>LIMITED</code> + * <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#level-3-additional-guaranteed-configurations">tables</a> + * in the documentation are guaranteed to be supported.</p> * <p>The following additional capabilities are guaranteed to be supported:</p> * <ul> * <li><code>YUV_REPROCESSING</code> capability ({@link CameraCharacteristics#REQUEST_AVAILABLE_CAPABILITIES android.request.availableCapabilities} contains @@ -2333,6 +2332,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 +4113,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/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java index 3851e368fb62..ccb24e7d2457 100644 --- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java +++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java @@ -786,6 +786,18 @@ public class CameraDeviceImpl extends CameraDevice } } + @Override + public CameraCharacteristics getSessionCharacteristics( + @NonNull SessionConfiguration sessionConfig) throws CameraAccessException, + UnsupportedOperationException, IllegalArgumentException { + synchronized (mInterfaceLock) { + checkIfCameraClosedOrInError(); + CameraMetadataNative info = mRemoteDevice.getSessionCharacteristics(sessionConfig); + + return new CameraCharacteristics(info); + } + } + /** * For use by backwards-compatibility code only. */ diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java index b6b1968bfcdd..2129260b0ae8 100644 --- a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java +++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java @@ -16,19 +16,12 @@ package android.hardware.camera2.impl; -import static android.hardware.camera2.CameraAccessException.CAMERA_DISABLED; -import static android.hardware.camera2.CameraAccessException.CAMERA_DISCONNECTED; -import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE; -import static android.hardware.camera2.CameraAccessException.CAMERA_ERROR; -import static android.hardware.camera2.CameraAccessException.MAX_CAMERAS_IN_USE; -import static android.hardware.camera2.CameraAccessException.CAMERA_DEPRECATED_HAL; - import android.hardware.ICameraService; -import android.hardware.camera2.CameraManager; import android.hardware.camera2.CameraAccessException; +import android.hardware.camera2.CameraManager; import android.hardware.camera2.CaptureRequest; -import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.ICameraDeviceCallbacks; +import android.hardware.camera2.ICameraDeviceUser; import android.hardware.camera2.ICameraOfflineSession; import android.hardware.camera2.impl.CameraMetadataNative; import android.hardware.camera2.params.OutputConfiguration; @@ -205,6 +198,28 @@ public class ICameraDeviceUserWrapper { } } + /** + * Fetches the CameraCharacteristics for a given session configuration. + */ + public CameraMetadataNative getSessionCharacteristics(SessionConfiguration sessionConfig) + throws CameraAccessException { + try { + return mRemoteDevice.getSessionCharacteristics(sessionConfig); + } catch (ServiceSpecificException e) { + if (e.errorCode == ICameraService.ERROR_INVALID_OPERATION) { + throw new UnsupportedOperationException("Session characteristics query not " + + "supported"); + } else if (e.errorCode == ICameraService.ERROR_ILLEGAL_ARGUMENT) { + throw new IllegalArgumentException("Invalid session configuration"); + } + + throw e; + } catch (Throwable t) { + CameraManager.throwAsPublicException(t); + throw new UnsupportedOperationException("Unexpected exception", t); + } + } + public long flush() throws CameraAccessException { try { return mRemoteDevice.flush(); diff --git a/core/java/android/os/BatteryConsumer.java b/core/java/android/os/BatteryConsumer.java index 6a83cee10309..05a1abeaf479 100644 --- a/core/java/android/os/BatteryConsumer.java +++ b/core/java/android/os/BatteryConsumer.java @@ -33,6 +33,7 @@ import java.util.ArrayList; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass public abstract class BatteryConsumer { private static final String TAG = "BatteryConsumer"; 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/IVibratorManagerService.aidl b/core/java/android/os/IVibratorManagerService.aidl index 0f2756914fa6..65e498e14475 100644 --- a/core/java/android/os/IVibratorManagerService.aidl +++ b/core/java/android/os/IVibratorManagerService.aidl @@ -41,5 +41,5 @@ interface IVibratorManagerService { // There is no order guarantee with respect to the two-way APIs above like // vibrate/isVibrating/cancel. oneway void performHapticFeedback(int uid, int deviceId, String opPkg, int constant, - boolean always, String reason, IBinder token); + boolean always, String reason); } diff --git a/core/java/android/os/SystemVibratorManager.java b/core/java/android/os/SystemVibratorManager.java index bc85412e851b..8e8392302824 100644 --- a/core/java/android/os/SystemVibratorManager.java +++ b/core/java/android/os/SystemVibratorManager.java @@ -39,6 +39,7 @@ public class SystemVibratorManager extends VibratorManager { private final IVibratorManagerService mService; private final Context mContext; + private final int mUid; private final Binder mToken = new Binder(); private final Object mLock = new Object(); @GuardedBy("mLock") @@ -56,6 +57,7 @@ public class SystemVibratorManager extends VibratorManager { public SystemVibratorManager(Context context) { super(context); mContext = context; + mUid = Process.myUid(); mService = IVibratorManagerService.Stub.asInterface( ServiceManager.getService(Context.VIBRATOR_MANAGER_SERVICE)); } @@ -152,8 +154,7 @@ public class SystemVibratorManager extends VibratorManager { } try { mService.performHapticFeedback( - Process.myUid(), mContext.getDeviceId(), mPackageName, constant, always, reason, - mToken); + mUid, mContext.getDeviceId(), mPackageName, constant, always, reason); } catch (RemoteException e) { Log.w(TAG, "Failed to perform haptic feedback.", e); } 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/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index 437668c9a7de..69d86a6604ad 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -44,10 +44,3 @@ flag { description: "Enables the independent keyboard vibration settings feature" bug: "289107579" } - -flag { - namespace: "haptics" - name: "adaptive_haptics_enabled" - description: "Enables the adaptive haptics feature" - bug: "305961689" -} diff --git a/core/java/android/permission/PermissionControllerService.java b/core/java/android/permission/PermissionControllerService.java index 9fe2599dd39b..5a12760f4e3f 100644 --- a/core/java/android/permission/PermissionControllerService.java +++ b/core/java/android/permission/PermissionControllerService.java @@ -318,7 +318,7 @@ public abstract class PermissionControllerService extends Service { * a virtual device. See {@link Context#DEVICE_ID_DEFAULT} */ @BinderThread - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public void onOneTimePermissionSessionTimeout(@NonNull String packageName, int deviceId) { onOneTimePermissionSessionTimeout(packageName); @@ -393,7 +393,7 @@ public abstract class PermissionControllerService extends Service { * @see android.content.Context#revokeSelfPermissionsOnKill(java.util.Collection) */ @BinderThread - @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS) + @FlaggedApi(Flags.FLAG_DEVICE_AWARE_PERMISSION_APIS_ENABLED) public void onRevokeSelfPermissionsOnKill(@NonNull String packageName, @NonNull List<String> permissions, int deviceId, @NonNull Runnable callback) { onRevokeSelfPermissionsOnKill(packageName, permissions, callback); diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig index cbeb82175bfd..d09c2290b38a 100644 --- a/core/java/android/permission/flags.aconfig +++ b/core/java/android/permission/flags.aconfig @@ -1,7 +1,7 @@ package: "android.permission.flags" flag { - name: "device_aware_permission_apis" + name: "device_aware_permission_apis_enabled" is_fixed_read_only: true namespace: "permissions" description: "enable device aware permission APIs" 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/security/flags.aconfig b/core/java/android/security/flags.aconfig index 0dc04133e664..28ef70b14026 100644 --- a/core/java/android/security/flags.aconfig +++ b/core/java/android/security/flags.aconfig @@ -8,6 +8,13 @@ flag { } flag { + name: "mgf1_digest_setter" + namespace: "hardware_backed_security" + description: "Feature flag for mgf1 digest setter in key generation and import parameters." + bug: "308378912" +} + +flag { name: "fix_unlocked_device_required_keys_v2" namespace: "hardware_backed_security" description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys" 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/notification/DeviceEffectsApplier.java b/core/java/android/service/notification/DeviceEffectsApplier.java index 234ff4dd0852..5194cdd47933 100644 --- a/core/java/android/service/notification/DeviceEffectsApplier.java +++ b/core/java/android/service/notification/DeviceEffectsApplier.java @@ -16,6 +16,8 @@ package android.service.notification; +import android.service.notification.ZenModeConfig.ConfigChangeOrigin; + /** * Responsible for making any service calls needed to apply the set of {@link ZenDeviceEffects} that * make sense for the current platform. @@ -33,6 +35,13 @@ public interface DeviceEffectsApplier { * * <p>This will be called whenever the set of consolidated effects changes (normally through * the activation or deactivation of zen rules). + * + * @param effects The effects that should be active and inactive. + * @param source The origin of the change. Because the application of specific effects can be + * disruptive (e.g. lead to Activity recreation), that operation can in some + * cases be deferred (e.g. until screen off). However, if the effects are + * changing as a result of an explicit user action, then it makes sense to + * apply them immediately regardless. */ - void apply(ZenDeviceEffects effects); + void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int source); } diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java index f6128ea80c3b..a5b087c05dfa 100644 --- a/core/java/android/service/notification/ZenModeConfig.java +++ b/core/java/android/service/notification/ZenModeConfig.java @@ -26,6 +26,7 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK; import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_SCREEN_OFF; import android.annotation.FlaggedApi; +import android.annotation.IntDef; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlarmManager; @@ -61,6 +62,8 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Arrays; import java.util.Calendar; import java.util.Date; @@ -79,7 +82,66 @@ import java.util.UUID; * @hide */ public class ZenModeConfig implements Parcelable { - private static String TAG = "ZenModeConfig"; + private static final String TAG = "ZenModeConfig"; + + /** + * The {@link ZenModeConfig} is being updated because of an unknown reason. + */ + public static final int UPDATE_ORIGIN_UNKNOWN = 0; + + /** + * The {@link ZenModeConfig} is being updated because of system initialization (i.e. load from + * storage, on device boot). + */ + public static final int UPDATE_ORIGIN_INIT = 1; + + /** The {@link ZenModeConfig} is being updated (replaced) because of a user switch or unlock. */ + public static final int UPDATE_ORIGIN_INIT_USER = 2; + + /** The {@link ZenModeConfig} is being updated because of a user action, for example: + * <ul> + * <li>{@link NotificationManager#setAutomaticZenRuleState} with a + * {@link Condition#source} equal to {@link Condition#SOURCE_USER_ACTION}.</li> + * <li>Adding, updating, or removing a rule from Settings.</li> + * <li>Directly activating or deactivating/snoozing a rule through some UI affordance (e.g. + * Quick Settings).</li> + * </ul> + */ + public static final int UPDATE_ORIGIN_USER = 3; + + /** + * The {@link ZenModeConfig} is being "independently" updated by an app, and not as a result of + * a user's action inside that app (for example, activating an {@link AutomaticZenRule} based on + * a previously set schedule). + */ + public static final int UPDATE_ORIGIN_APP = 4; + + /** + * The {@link ZenModeConfig} is being updated by the System or SystemUI. Note that this only + * includes cases where the call is coming from the System/SystemUI but the change is not due to + * a user action (e.g. automatically activating a schedule-based rule). If the change is a + * result of a user action (e.g. activating a rule by tapping on its QS tile) then + * {@link #UPDATE_ORIGIN_USER} is used instead. + */ + public static final int UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI = 5; + + /** + * The {@link ZenModeConfig} is being updated (replaced) because the user's DND configuration + * is being restored from a backup. + */ + public static final int UPDATE_ORIGIN_RESTORE_BACKUP = 6; + + @IntDef(prefix = { "UPDATE_ORIGIN_" }, value = { + UPDATE_ORIGIN_UNKNOWN, + UPDATE_ORIGIN_INIT, + UPDATE_ORIGIN_INIT_USER, + UPDATE_ORIGIN_USER, + UPDATE_ORIGIN_APP, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + UPDATE_ORIGIN_RESTORE_BACKUP + }) + @Retention(RetentionPolicy.SOURCE) + public @interface ConfigChangeOrigin {} public static final int SOURCE_ANYONE = Policy.PRIORITY_SENDERS_ANY; public static final int SOURCE_CONTACT = Policy.PRIORITY_SENDERS_CONTACTS; 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..5cbb42e0e346 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -826,11 +826,19 @@ public final class ViewRootImpl implements ViewParent, * The resolved pointer icon type requested by this window. * A null value indicates the resolved pointer icon has not yet been calculated. */ + // TODO(b/293587049): Remove pointer icon tracking by type when refactor is complete. @Nullable private Integer mPointerIconType = null; private PointerIcon mCustomPointerIcon = null; /** + * The resolved pointer icon requested by this window. + * A null value indicates the resolved pointer icon has not yet been calculated. + */ + @Nullable + private PointerIcon mResolvedPointerIcon = null; + + /** * see {@link #playSoundEffect(int)} */ AudioManager mAudioManager; @@ -3216,6 +3224,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 +3939,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; @@ -7399,12 +7418,14 @@ public final class ViewRootImpl implements ViewParent, // Other apps or the window manager may change the icon type outside of // this app, therefore the icon type has to be reset on enter/exit event. mPointerIconType = null; + mResolvedPointerIcon = null; } if (action != MotionEvent.ACTION_HOVER_EXIT) { // Resolve the pointer icon if (!updatePointerIcon(event) && action == MotionEvent.ACTION_HOVER_MOVE) { mPointerIconType = null; + mResolvedPointerIcon = null; } } @@ -7459,6 +7480,7 @@ public final class ViewRootImpl implements ViewParent, private void resetPointerIcon(MotionEvent event) { mPointerIconType = null; + mResolvedPointerIcon = null; updatePointerIcon(event); } @@ -7496,6 +7518,21 @@ public final class ViewRootImpl implements ViewParent, pointerIcon = mView.onResolvePointerIcon(event, pointerIndex); } + if (enablePointerChoreographer()) { + if (pointerIcon == null) { + pointerIcon = PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED); + } + if (Objects.equals(mResolvedPointerIcon, pointerIcon)) { + return true; + } + mResolvedPointerIcon = pointerIcon; + + InputManagerGlobal.getInstance() + .setPointerIcon(pointerIcon, event.getDisplayId(), + event.getDeviceId(), event.getPointerId(0), getInputToken()); + return true; + } + final int pointerType = (pointerIcon != null) ? pointerIcon.getType() : PointerIcon.TYPE_NOT_SPECIFIED; @@ -7503,34 +7540,18 @@ public final class ViewRootImpl implements ViewParent, mPointerIconType = pointerType; mCustomPointerIcon = null; if (mPointerIconType != PointerIcon.TYPE_CUSTOM) { - if (enablePointerChoreographer()) { - InputManagerGlobal - .getInstance() - .setPointerIcon(PointerIcon.getSystemIcon(mContext, pointerType), - event.getDisplayId(), event.getDeviceId(), - event.getPointerId(pointerIndex), getInputToken()); - } else { - InputManagerGlobal - .getInstance() - .setPointerIconType(pointerType); - } + InputManagerGlobal + .getInstance() + .setPointerIconType(pointerType); return true; } } if (mPointerIconType == PointerIcon.TYPE_CUSTOM && !pointerIcon.equals(mCustomPointerIcon)) { mCustomPointerIcon = pointerIcon; - if (enablePointerChoreographer()) { - InputManagerGlobal - .getInstance() - .setPointerIcon(mCustomPointerIcon, - event.getDisplayId(), event.getDeviceId(), - event.getPointerId(pointerIndex), getInputToken()); - } else { - InputManagerGlobal - .getInstance() - .setCustomPointerIcon(mCustomPointerIcon); - } + InputManagerGlobal + .getInstance() + .setCustomPointerIcon(mCustomPointerIcon); } return true; } @@ -12005,18 +12026,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 +12045,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 +12168,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/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 7ab5446a2994..e057660961f6 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -39,6 +39,13 @@ flag { flag { namespace: "accessibility" + name: "copy_events_for_gesture_detection" + description: "Creates copies of MotionEvents and GestureEvents in GestureMatcher" + bug: "280130713" +} + +flag { + namespace: "accessibility" name: "flash_notification_system_api" description: "Makes flash notification APIs as system APIs for calling from mainline module" bug: "303131332" @@ -77,4 +84,4 @@ flag { namespace: "accessibility" description: "Feature flag for system pinch zoom gesture detector and related opt-out apis" bug: "283323770" -}
\ No newline at end of file +} 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/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java index 8b55494c75d4..d38a95e713b3 100644 --- a/core/java/android/view/inputmethod/InputMethodInfo.java +++ b/core/java/android/view/inputmethod/InputMethodInfo.java @@ -16,9 +16,11 @@ package android.view.inputmethod; +import android.annotation.FlaggedApi; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; +import android.annotation.SystemApi; import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -113,6 +115,11 @@ public final class InputMethodInfo implements Parcelable { final boolean mIsVrOnly; /** + * IME only supports virtual devices. + */ + final boolean mIsVirtualDeviceOnly; + + /** * The unique string Id to identify the input method. This is generated * from the input method component. */ @@ -239,6 +246,7 @@ public final class InputMethodInfo implements Parcelable { String settingsActivityComponent = null; String stylusHandwritingSettingsActivity = null; boolean isVrOnly; + boolean isVirtualDeviceOnly; int isDefaultResId = 0; XmlResourceParser parser = null; @@ -277,6 +285,8 @@ public final class InputMethodInfo implements Parcelable { } isVrOnly = sa.getBoolean(com.android.internal.R.styleable.InputMethod_isVrOnly, false); + isVirtualDeviceOnly = sa.getBoolean( + com.android.internal.R.styleable.InputMethod_isVirtualDeviceOnly, false); isDefaultResId = sa.getResourceId( com.android.internal.R.styleable.InputMethod_isDefault, 0); supportsSwitchingToNextInputMethod = sa.getBoolean( @@ -382,6 +392,7 @@ public final class InputMethodInfo implements Parcelable { mSuppressesSpellChecker = suppressesSpellChecker; mShowInInputMethodPicker = showInInputMethodPicker; mIsVrOnly = isVrOnly; + mIsVirtualDeviceOnly = isVirtualDeviceOnly; } /** @@ -399,6 +410,7 @@ public final class InputMethodInfo implements Parcelable { mSuppressesSpellChecker = source.mSuppressesSpellChecker; mShowInInputMethodPicker = source.mShowInInputMethodPicker; mIsVrOnly = source.mIsVrOnly; + mIsVirtualDeviceOnly = source.mIsVirtualDeviceOnly; mService = source.mService; mSubtypes = source.mSubtypes; mHandledConfigChanges = source.mHandledConfigChanges; @@ -418,6 +430,7 @@ public final class InputMethodInfo implements Parcelable { mSuppressesSpellChecker = source.readBoolean(); mShowInInputMethodPicker = source.readBoolean(); mIsVrOnly = source.readBoolean(); + mIsVirtualDeviceOnly = source.readBoolean(); mService = ResolveInfo.CREATOR.createFromParcel(source); mSubtypes = new InputMethodSubtypeArray(source); mHandledConfigChanges = source.readInt(); @@ -435,7 +448,8 @@ public final class InputMethodInfo implements Parcelable { settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, false /* isVrOnly */, - 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */, + false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, + false /* supportsStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); } @@ -453,8 +467,9 @@ public final class InputMethodInfo implements Parcelable { settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, false /* isVrOnly */, - 0 /* handledConfigChanges */, supportStylusHandwriting, - stylusHandwritingSettingsActivityAttr, false /* inlineSuggestionsEnabled */); + false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, + supportStylusHandwriting, stylusHandwritingSettingsActivityAttr, + false /* inlineSuggestionsEnabled */); } /** @@ -468,7 +483,8 @@ public final class InputMethodInfo implements Parcelable { this(buildFakeResolveInfo(packageName, className, label), false /* isAuxIme */, settingsActivity, null /* subtypes */, 0 /* isDefaultResId */, false /* forceDefault */, true /* supportsSwitchingToNextInputMethod */, - false /* inlineSuggestionsEnabled */, false /* isVrOnly */, handledConfigChanges, + false /* inlineSuggestionsEnabled */, false /* isVrOnly */, + false /* isVirtualDeviceOnly */, handledConfigChanges, false /* supportsStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); @@ -483,7 +499,7 @@ public final class InputMethodInfo implements Parcelable { boolean forceDefault) { this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault, true /* supportsSwitchingToNextInputMethod */, false /* inlineSuggestionsEnabled */, - false /* isVrOnly */, 0 /* handledconfigChanges */, + false /* isVrOnly */, false /* isVirtualDeviceOnly */, 0 /* handledconfigChanges */, false /* supportsStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); @@ -498,6 +514,7 @@ public final class InputMethodInfo implements Parcelable { boolean supportsSwitchingToNextInputMethod, boolean isVrOnly) { this(ri, isAuxIme, settingsActivity, subtypes, isDefaultResId, forceDefault, supportsSwitchingToNextInputMethod, false /* inlineSuggestionsEnabled */, isVrOnly, + false /* isVirtualDeviceOnly */, 0 /* handledConfigChanges */, false /* supportsStylusHandwriting */, null /* stylusHandwritingSettingsActivityAttr */, false /* inlineSuggestionsEnabled */); @@ -510,8 +527,8 @@ public final class InputMethodInfo implements Parcelable { public InputMethodInfo(ResolveInfo ri, boolean isAuxIme, String settingsActivity, List<InputMethodSubtype> subtypes, int isDefaultResId, boolean forceDefault, boolean supportsSwitchingToNextInputMethod, boolean inlineSuggestionsEnabled, - boolean isVrOnly, int handledConfigChanges, boolean supportsStylusHandwriting, - String stylusHandwritingSettingsActivityAttr, + boolean isVrOnly, boolean isVirtualDeviceOnly, int handledConfigChanges, + boolean supportsStylusHandwriting, String stylusHandwritingSettingsActivityAttr, boolean supportsInlineSuggestionsWithTouchExploration) { final ServiceInfo si = ri.serviceInfo; mService = ri; @@ -528,6 +545,7 @@ public final class InputMethodInfo implements Parcelable { mSuppressesSpellChecker = false; mShowInInputMethodPicker = true; mIsVrOnly = isVrOnly; + mIsVirtualDeviceOnly = isVirtualDeviceOnly; mHandledConfigChanges = handledConfigChanges; mSupportsStylusHandwriting = supportsStylusHandwriting; mStylusHandwritingSettingsActivityAttr = stylusHandwritingSettingsActivityAttr; @@ -635,6 +653,16 @@ public final class InputMethodInfo implements Parcelable { } /** + * Returns true if IME supports only virtual devices. + * @hide + */ + @FlaggedApi(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME) + @SystemApi + public boolean isVirtualDeviceOnly() { + return mIsVirtualDeviceOnly; + } + + /** * Return the count of the subtypes of Input Method. */ public int getSubtypeCount() { @@ -732,6 +760,7 @@ public final class InputMethodInfo implements Parcelable { pw.println(prefix + "mId=" + mId + " mSettingsActivityName=" + mSettingsActivityName + " mIsVrOnly=" + mIsVrOnly + + " mIsVirtualDeviceOnly=" + mIsVirtualDeviceOnly + " mSupportsSwitchingToNextInputMethod=" + mSupportsSwitchingToNextInputMethod + " mInlineSuggestionsEnabled=" + mInlineSuggestionsEnabled + " mSupportsInlineSuggestionsWithTouchExploration=" @@ -851,6 +880,7 @@ public final class InputMethodInfo implements Parcelable { dest.writeBoolean(mSuppressesSpellChecker); dest.writeBoolean(mShowInInputMethodPicker); dest.writeBoolean(mIsVrOnly); + dest.writeBoolean(mIsVirtualDeviceOnly); mService.writeToParcel(dest, flags); mSubtypes.writeToParcel(dest); dest.writeInt(mHandledConfigChanges); diff --git a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml b/core/java/android/window/ITrustedPresentationListener.aidl index be1f081d5b8f..b33128abb7e5 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayNarrowBack/res/values-sw600dp/config.xml +++ b/core/java/android/window/ITrustedPresentationListener.aidl @@ -1,13 +1,11 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/** - * Copyright (c) 2023, The Android Open Source Project +/* + * 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 + * 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, @@ -15,8 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ ---> -<resources> - <!-- If true, attach the navigation bar to the app during app transition --> - <bool name="config_attachNavBarToAppDuringTransition">false</bool> -</resources> + +package android.window; + +/** + * @hide + */ +oneway interface ITrustedPresentationListener { + void onTrustedPresentationChanged(in int[] enteredTrustedStateIds, in int[] exitedTrustedStateIds); +}
\ No newline at end of file diff --git a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml b/core/java/android/window/TrustedPresentationListener.java index be1f081d5b8f..02fd6d98fb0d 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayWideBack/res/values-sw600dp/config.xml +++ b/core/java/android/window/TrustedPresentationListener.java @@ -1,13 +1,11 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/** - * Copyright (c) 2023, The Android Open Source Project +/* + * 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 + * 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, @@ -15,8 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ ---> -<resources> - <!-- If true, attach the navigation bar to the app during app transition --> - <bool name="config_attachNavBarToAppDuringTransition">false</bool> -</resources> + +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/responsible_apis.aconfig b/core/java/android/window/flags/responsible_apis.aconfig index f828cff14e88..ad0e9a487c53 100644 --- a/core/java/android/window/flags/responsible_apis.aconfig +++ b/core/java/android/window/flags/responsible_apis.aconfig @@ -41,3 +41,10 @@ flag { description: "Prevent a task to restart based on a visible window during task switch." bug: "171459802" } + +flag { + name: "bal_respect_app_switch_state_when_check_bound_by_foreground_uid" + namespace: "responsible_apis" + description: "Prevent BAL based on it is bound by foreground Uid but the app switch is stopped." + bug: "171459802" +} 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/app/SuspendedAppActivity.java b/core/java/com/android/internal/app/SuspendedAppActivity.java index 1e0b2a0263cf..efc1455ecd45 100644 --- a/core/java/com/android/internal/app/SuspendedAppActivity.java +++ b/core/java/com/android/internal/app/SuspendedAppActivity.java @@ -80,7 +80,8 @@ public class SuspendedAppActivity extends AlertActivity // Suspension conditions were modified, dismiss any related visible dialogs. final String[] modified = intent.getStringArrayExtra( Intent.EXTRA_CHANGED_PACKAGE_LIST); - if (ArrayUtils.contains(modified, mSuspendedPackage)) { + if (ArrayUtils.contains(modified, mSuspendedPackage) + && !isPackageSuspended(mSuspendedPackage)) { if (!isFinishing()) { Slog.w(TAG, "Package " + mSuspendedPackage + " has modified" + " suspension conditions while dialog was visible. Finishing."); @@ -92,6 +93,15 @@ public class SuspendedAppActivity extends AlertActivity } }; + private boolean isPackageSuspended(String packageName) { + try { + return mPm.isPackageSuspended(packageName); + } catch (PackageManager.NameNotFoundException ne) { + Slog.e(TAG, "Package " + packageName + " not found", ne); + } + return false; + } + private CharSequence getAppLabel(String packageName) { try { return mPm.getApplicationInfoAsUser(packageName, 0, mUserId).loadLabel(mPm); 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/net/TEST_MAPPING b/core/java/com/android/internal/net/TEST_MAPPING new file mode 100644 index 000000000000..971ad36eecba --- /dev/null +++ b/core/java/com/android/internal/net/TEST_MAPPING @@ -0,0 +1,12 @@ +{ + "postsubmit": [ + { + "name": "FrameworksNetTests", + "options": [ + { + "exclude-annotation": "com.android.testutils.SkipPresubmit" + } + ] + } + ] +} diff --git a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java index 1f44b338f3f7..ed943cb2385d 100644 --- a/core/java/com/android/internal/os/LongArrayMultiStateCounter.java +++ b/core/java/com/android/internal/os/LongArrayMultiStateCounter.java @@ -55,15 +55,20 @@ import java.util.concurrent.atomic.AtomicReference; * * @hide */ +@android.ravenwood.annotation.RavenwoodKeepWholeClass +@android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( + "com.android.hoststubgen.nativesubstitution.LongArrayMultiStateCounter_host") public final class LongArrayMultiStateCounter implements Parcelable { /** * Container for a native equivalent of a long[]. */ + @android.ravenwood.annotation.RavenwoodKeepWholeClass + @android.ravenwood.annotation.RavenwoodNativeSubstitutionClass( + "com.android.hoststubgen.nativesubstitution" + + ".LongArrayMultiStateCounter_host$LongArrayContainer_host") public static class LongArrayContainer { - private static final NativeAllocationRegistry sRegistry = - NativeAllocationRegistry.createMalloced( - LongArrayContainer.class.getClassLoader(), native_getReleaseFunc()); + private static NativeAllocationRegistry sRegistry; // Visible to other objects in this package so that it can be passed to @CriticalNative // methods. @@ -73,9 +78,26 @@ public final class LongArrayMultiStateCounter implements Parcelable { public LongArrayContainer(int length) { mLength = length; mNativeObject = native_init(length); + registerNativeAllocation(); + } + + @android.ravenwood.annotation.RavenwoodReplace + private void registerNativeAllocation() { + if (sRegistry == null) { + synchronized (LongArrayMultiStateCounter.class) { + if (sRegistry == null) { + sRegistry = NativeAllocationRegistry.createMalloced( + LongArrayContainer.class.getClassLoader(), native_getReleaseFunc()); + } + } + } sRegistry.registerNativeAllocation(this, mNativeObject); } + private void registerNativeAllocation$ravenwood() { + // No-op under ravenwood + } + /** * Copies the supplied values into the underlying native array. */ @@ -124,19 +146,17 @@ public final class LongArrayMultiStateCounter implements Parcelable { private static native long native_getReleaseFunc(); @FastNative - private native void native_setValues(long nativeObject, long[] array); + private static native void native_setValues(long nativeObject, long[] array); @FastNative - private native void native_getValues(long nativeObject, long[] array); + private static native void native_getValues(long nativeObject, long[] array); @FastNative - private native boolean native_combineValues(long nativeObject, long[] array, + private static native boolean native_combineValues(long nativeObject, long[] array, int[] indexMap); } - private static final NativeAllocationRegistry sRegistry = - NativeAllocationRegistry.createMalloced( - LongArrayMultiStateCounter.class.getClassLoader(), native_getReleaseFunc()); + private static volatile NativeAllocationRegistry sRegistry; private static final AtomicReference<LongArrayContainer> sTmpArrayContainer = new AtomicReference<>(); @@ -152,12 +172,30 @@ public final class LongArrayMultiStateCounter implements Parcelable { mStateCount = stateCount; mLength = arrayLength; mNativeObject = native_init(stateCount, arrayLength); + registerNativeAllocation(); + } + + @android.ravenwood.annotation.RavenwoodReplace + private void registerNativeAllocation() { + if (sRegistry == null) { + synchronized (LongArrayMultiStateCounter.class) { + if (sRegistry == null) { + sRegistry = NativeAllocationRegistry.createMalloced( + LongArrayMultiStateCounter.class.getClassLoader(), + native_getReleaseFunc()); + } + } + } sRegistry.registerNativeAllocation(this, mNativeObject); } + private void registerNativeAllocation$ravenwood() { + // No-op under ravenwood + } + private LongArrayMultiStateCounter(Parcel in) { mNativeObject = native_initFromParcel(in); - sRegistry.registerNativeAllocation(this, mNativeObject); + registerNativeAllocation(); mStateCount = native_getStateCount(mNativeObject); mLength = native_getArrayLength(mNativeObject); @@ -361,10 +399,10 @@ public final class LongArrayMultiStateCounter implements Parcelable { long longArrayContainerNativeObject, int state); @FastNative - private native String native_toString(long nativeObject); + private static native String native_toString(long nativeObject); @FastNative - private native void native_writeToParcel(long nativeObject, Parcel dest, int flags); + private static native void native_writeToParcel(long nativeObject, Parcel dest, int flags); @FastNative private static native long native_initFromParcel(Parcel parcel); 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.bp b/core/jni/Android.bp index f365dbb1d46a..2a744e343ccd 100644 --- a/core/jni/Android.bp +++ b/core/jni/Android.bp @@ -49,8 +49,6 @@ cc_library_shared_for_libandroid_runtime { "-Wno-unused-parameter", "-Wunused", "-Wunreachable-code", - - "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash", ], cppflags: ["-Wno-conversion-null"], @@ -284,8 +282,6 @@ cc_library_shared_for_libandroid_runtime { "libscrypt_static", "libstatssocket_lazy", "libskia", - "libtextclassifier_hash_static", - "libexpresslog_jni", ], shared_libs: [ @@ -372,7 +368,6 @@ cc_library_shared_for_libandroid_runtime { "bionic_libc_platform_headers", "dnsproxyd_protocol_headers", "flatbuffer_headers", - "libtextclassifier_hash_headers", "tensorflow_headers", ], runtime_libs: [ diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp index 50253cf9e457..c24d21dda68c 100644 --- a/core/jni/AndroidRuntime.cpp +++ b/core/jni/AndroidRuntime.cpp @@ -201,7 +201,6 @@ extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env); extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env); extern int register_com_android_internal_content_om_OverlayManagerImpl(JNIEnv* env); -extern int register_com_android_modules_expresslog_Utils(JNIEnv* env); extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env); extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env); extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env); @@ -1590,7 +1589,6 @@ static const RegJNIRec gRegJNI[] = { REG_JNI(register_android_os_incremental_IncrementalManager), REG_JNI(register_com_android_internal_content_om_OverlayConfig), REG_JNI(register_com_android_internal_content_om_OverlayManagerImpl), - REG_JNI(register_com_android_modules_expresslog_Utils), REG_JNI(register_com_android_internal_net_NetworkUtilsInternal), REG_JNI(register_com_android_internal_os_ClassLoaderFactory), REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter), 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/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp index dab47e9a9e28..76b05eac82af 100644 --- a/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp +++ b/core/jni/com_android_internal_os_LongArrayMultiStateCounter.cpp @@ -107,7 +107,7 @@ static void native_getCounts(jlong nativePtr, jlong longArrayContainerNativePtr, *vector = counter->getCount(state); } -static jobject native_toString(JNIEnv *env, jobject self, jlong nativePtr) { +static jobject native_toString(JNIEnv *env, jclass, jlong nativePtr) { battery::LongArrayMultiStateCounter *counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); return env->NewStringUTF(counter->toString().c_str()); @@ -127,7 +127,7 @@ static void throwWriteRE(JNIEnv *env, binder_status_t status) { } \ } -static void native_writeToParcel(JNIEnv *env, jobject self, jlong nativePtr, jobject jParcel, +static void native_writeToParcel(JNIEnv *env, jclass, jlong nativePtr, jobject jParcel, jint flags) { battery::LongArrayMultiStateCounter *counter = reinterpret_cast<battery::LongArrayMultiStateCounter *>(nativePtr); @@ -161,7 +161,7 @@ static void throwReadException(JNIEnv *env, binder_status_t status) { } \ } -static jlong native_initFromParcel(JNIEnv *env, jclass theClass, jobject jParcel) { +static jlong native_initFromParcel(JNIEnv *env, jclass, jobject jParcel) { ndk::ScopedAParcel parcel(AParcel_fromJavaParcel(env, jParcel)); int32_t stateCount; @@ -253,7 +253,7 @@ static jlong native_getReleaseFunc_LongArrayContainer() { return reinterpret_cast<jlong>(native_dispose_LongArrayContainer); } -static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, +static void native_setValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, jlongArray jarray) { std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); ScopedLongArrayRO scopedArray(env, jarray); @@ -264,7 +264,7 @@ static void native_setValues_LongArrayContainer(JNIEnv *env, jobject self, jlong std::copy(array, array + size, vector->data()); } -static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, +static void native_getValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, jlongArray jarray) { std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); ScopedLongArrayRW scopedArray(env, jarray); @@ -273,7 +273,7 @@ static void native_getValues_LongArrayContainer(JNIEnv *env, jobject self, jlong std::copy(vector->data(), vector->data() + vector->size(), scopedArray.get()); } -static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jobject self, jlong nativePtr, +static jboolean native_combineValues_LongArrayContainer(JNIEnv *env, jclass, jlong nativePtr, jlongArray jarray, jintArray jindexMap) { std::vector<uint64_t> *vector = reinterpret_cast<std::vector<uint64_t> *>(nativePtr); ScopedLongArrayRW scopedArray(env, jarray); diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index 698c5bad3801..e1c1a42eed81 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -3854,6 +3854,11 @@ <!-- Specifies if an IME can only be used while a device is in VR mode or on a dedicated device --> <attr name="isVrOnly" format="boolean"/> + <!-- Specifies if an IME can only be used on a display created by a virtual device. + @see android.companion.virtual.VirtualDeviceParams.Builder#setInputMethodComponent + @hide @SystemApi --> + <!-- @FlaggedApi("android.companion.virtual.flags.vdm_custom_ime") --> + <attr name="isVirtualDeviceOnly" format="boolean"/> <attr name="__removed2" format="boolean" /> <!-- Specifies whether the IME supports showing inline suggestions. --> <attr name="supportsInlineSuggestions" format="boolean" /> 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/config.xml b/core/res/res/values/config.xml index 1229453e5736..387a1083c6d6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -1404,6 +1404,9 @@ <!-- Shutdown if the battery temperature exceeds (this value * 0.1) Celsius. --> <integer name="config_shutdownBatteryTemperature">680</integer> + <!-- Shutdown if battery is critically low and the device is not powered. --> + <bool name="config_shutdownIfNoPower">true</bool> + <!-- Display low battery warning when battery level dips to this value --> <integer name="config_lowBatteryWarningLevel">20</integer> diff --git a/core/res/res/values/public-staging.xml b/core/res/res/values/public-staging.xml index 4cd4f638e191..f10e7f8c337e 100644 --- a/core/res/res/values/public-staging.xml +++ b/core/res/res/values/public-staging.xml @@ -112,6 +112,11 @@ <staging-public-group type="attr" first-id="0x01bd0000"> <!-- @FlaggedApi("android.content.res.default_locale") --> <public name="defaultLocale"/> + <!-- @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/res/res/values/strings.xml b/core/res/res/values/strings.xml index 73a7e4296a57..1f6ac80a133c 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -6312,11 +6312,6 @@ ul.</string> <!-- Content of connected display unavailable due to thermals notification. [CHAR LIMIT=NONE] --> <string name="connected_display_thermally_unavailable_notification_content">Your device is too warm and can\'t mirror to the display until it cools down</string> - <!-- Title of cable don't support displays notifications. [CHAR LIMIT=NONE] --> - <string name="connected_display_cable_dont_support_displays_notification_title">Cable may not support displays</string> - <!-- Content of cable don't support displays notification. [CHAR LIMIT=NONE] --> - <string name="connected_display_cable_dont_support_displays_notification_content">Your USB-C cable may not connect to displays properly</string> - <!-- Name of concurrent display notifications. [CHAR LIMIT=NONE] --> <string name="concurrent_display_notification_name">Dual screen</string> <!-- Title of concurrent display active notification. [CHAR LIMIT=NONE] --> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 93aacdff57df..a5b102836816 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2094,6 +2094,7 @@ <java-symbol type="integer" name="config_autoBrightnessShortTermModelTimeout" /> <java-symbol type="integer" name="config_progressTimeoutFallbackHome" /> <java-symbol type="integer" name="config_shutdownBatteryTemperature" /> + <java-symbol type="bool" name="config_shutdownIfNoPower" /> <java-symbol type="integer" name="config_undockedHdmiRotation" /> <java-symbol type="integer" name="config_virtualKeyQuietTimeMillis" /> <java-symbol type="integer" name="config_brightness_ramp_rate_fast" /> @@ -5106,8 +5107,6 @@ <java-symbol type="string" name="connected_display_unavailable_notification_title"/> <java-symbol type="string" name="connected_display_unavailable_notification_content"/> <java-symbol type="string" name="connected_display_thermally_unavailable_notification_content"/> - <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_title"/> - <java-symbol type="string" name="connected_display_cable_dont_support_displays_notification_content"/> <java-symbol type="string" name="concurrent_display_notification_name"/> <java-symbol type="string" name="concurrent_display_notification_active_title"/> <java-symbol type="string" name="concurrent_display_notification_active_content"/> diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp index 0ad349ba5281..6706c916a0e2 100644 --- a/core/tests/coretests/Android.bp +++ b/core/tests/coretests/Android.bp @@ -179,10 +179,13 @@ android_ravenwood_test { "androidx.test.ext.junit", "mockito_ravenwood", "platform-test-annotations", + "flag-junit", ], srcs: [ + "src/android/os/BuildTest.java", "src/android/os/FileUtilsTest.java", "src/android/util/**/*.java", + "src/com/android/internal/os/LongArrayMultiStateCounterTest.java", "src/com/android/internal/util/**/*.java", "testdoubles/src/com/android/internal/util/**/*.java", ], diff --git a/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml b/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml new file mode 100644 index 000000000000..1905365808bc --- /dev/null +++ b/core/tests/coretests/res/xml/ime_meta_virtual_device_only.xml @@ -0,0 +1,23 @@ +<?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. +--> + +<input-method + xmlns:android="http://schemas.android.com/apk/res/android" + android:settingsActivity="com.android.inputmethod.latin.settings.SettingsActivity" + android:isVirtualDeviceOnly="true"> +</input-method> diff --git a/core/tests/coretests/src/android/os/BuildTest.java b/core/tests/coretests/src/android/os/BuildTest.java index 2295eb989108..3162e6da15c5 100644 --- a/core/tests/coretests/src/android/os/BuildTest.java +++ b/core/tests/coretests/src/android/os/BuildTest.java @@ -16,19 +16,37 @@ package android.os; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +import android.platform.test.annotations.IgnoreUnderRavenwood; +import android.platform.test.flag.junit.SetFlagsRule; +import android.platform.test.ravenwood.RavenwoodRule; + import androidx.test.filters.SmallTest; +import androidx.test.runner.AndroidJUnit4; import junit.framework.Assert; -import junit.framework.TestCase; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; /** * Provides test cases for android.os.Build and, in turn, many of the * system properties set by the build system. */ -public class BuildTest extends TestCase { - +@RunWith(AndroidJUnit4.class) +public class BuildTest { private static final String TAG = "BuildTest"; + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + /** * Asserts that a String is non-null and non-empty. If it is not, * an AssertionFailedError is thrown with the given message. @@ -50,7 +68,9 @@ public class BuildTest extends TestCase { /** * Asserts that all android.os.Build fields are non-empty and/or in a valid range. */ + @Test @SmallTest + @IgnoreUnderRavenwood(blockedBy = Build.class) public void testBuildFields() throws Exception { assertNotEmpty("ID", Build.ID); assertNotEmpty("DISPLAY", Build.DISPLAY); @@ -72,4 +92,16 @@ public class BuildTest extends TestCase { // (e.g., must be a C identifier, must be a valid filename, must not contain any spaces) // add tests for them. } + + @Test + public void testFlagEnabled() throws Exception { + mSetFlagsRule.enableFlags(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM); + assertTrue(Flags.androidOsBuildVanillaIceCream()); + } + + @Test + public void testFlagDisabled() throws Exception { + mSetFlagsRule.disableFlags(Flags.FLAG_ANDROID_OS_BUILD_VANILLA_ICE_CREAM); + assertFalse(Flags.androidOsBuildVanillaIceCream()); + } } diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java index 7c4136d62b8f..9300d1e5cb95 100644 --- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java +++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java @@ -831,6 +831,7 @@ public class DeviceConfigTest { } @Test + @FlakyTest(bugId = 299483542) public void onPropertiesChangedListener_setPropertiesCallback() { DeviceConfig.setProperty(NAMESPACE, KEY, VALUE, false); DeviceConfig.setProperty(NAMESPACE, KEY2, VALUE2, false); 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/android/view/inputmethod/InputMethodInfoTest.java b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java index 7bef55e23919..909af7b4c5fb 100644 --- a/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java +++ b/core/tests/coretests/src/android/view/inputmethod/InputMethodInfoTest.java @@ -26,6 +26,7 @@ import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.Parcel; +import android.platform.test.flag.junit.SetFlagsRule; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -33,6 +34,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.frameworks.coretests.R; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -40,6 +42,10 @@ import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class InputMethodInfoTest { + @Rule + public SetFlagsRule mSetFlagsRule = + new SetFlagsRule(SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); + @Test public void testEqualsAndHashCode() throws Exception { final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta); @@ -111,6 +117,19 @@ public class InputMethodInfoTest { assertThat(clone.isVrOnly(), is(true)); } + @Test + public void testIsVirtualDeviceOnly() throws Exception { + mSetFlagsRule.enableFlags(android.companion.virtual.flags.Flags.FLAG_VDM_CUSTOM_IME); + + final InputMethodInfo imi = buildInputMethodForTest(R.xml.ime_meta_virtual_device_only); + + assertThat(imi.isVirtualDeviceOnly(), is(true)); + + final InputMethodInfo clone = cloneViaParcel(imi); + + assertThat(clone.isVirtualDeviceOnly(), is(true)); + } + private InputMethodInfo buildInputMethodForTest(final @XmlRes int metaDataRes) throws Exception { final Context context = InstrumentationRegistry.getContext(); 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/keystore/java/android/security/Authorization.java b/keystore/java/android/security/Authorization.java index b4b3e9275035..4ec5e1b67c5d 100644 --- a/keystore/java/android/security/Authorization.java +++ b/keystore/java/android/security/Authorization.java @@ -26,7 +26,6 @@ import android.os.ServiceManager; import android.os.ServiceSpecificException; import android.os.StrictMode; import android.security.authorization.IKeystoreAuthorization; -import android.security.authorization.LockScreenEvent; import android.system.keystore2.ResponseCode; import android.util.Log; @@ -76,26 +75,37 @@ public class Authorization { } /** - * Informs keystore2 about lock screen event. + * Tells Keystore that the device is now unlocked for a user. * - * @param locked - whether it is a lock (true) or unlock (false) event - * @param syntheticPassword - if it is an unlock event with the password, pass the synthetic - * password provided by the LockSettingService - * @param unlockingSids - KeyMint secure user IDs that should be permitted to unlock - * UNLOCKED_DEVICE_REQUIRED keys. + * @param userId - the user's Android user ID + * @param password - a secret derived from the user's synthetic password, if the unlock method + * is LSKF (or equivalent) and thus has made the synthetic password available + * @return 0 if successful or a {@code ResponseCode}. + */ + public static int onDeviceUnlocked(int userId, @Nullable byte[] password) { + StrictMode.noteDiskWrite(); + try { + getService().onDeviceUnlocked(userId, password); + return 0; + } catch (RemoteException | NullPointerException e) { + Log.w(TAG, "Can not connect to keystore", e); + return SYSTEM_ERROR; + } catch (ServiceSpecificException e) { + return e.errorCode; + } + } + + /** + * Tells Keystore that the device is now locked for a user. * + * @param userId - the user's Android user ID + * @param unlockingSids - list of biometric SIDs with which the device may be unlocked again * @return 0 if successful or a {@code ResponseCode}. */ - public static int onLockScreenEvent(@NonNull boolean locked, @NonNull int userId, - @Nullable byte[] syntheticPassword, @Nullable long[] unlockingSids) { + public static int onDeviceLocked(int userId, @NonNull long[] unlockingSids) { StrictMode.noteDiskWrite(); try { - if (locked) { - getService().onLockScreenEvent(LockScreenEvent.LOCK, userId, null, unlockingSids); - } else { - getService().onLockScreenEvent( - LockScreenEvent.UNLOCK, userId, syntheticPassword, unlockingSids); - } + getService().onDeviceLocked(userId, unlockingSids); return 0; } catch (RemoteException | NullPointerException e) { Log.w(TAG, "Can not connect to keystore", e); diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java index 231fa4837441..4982f3732089 100644 --- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java +++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java @@ -618,7 +618,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see #isMgf1DigestsSpecified() */ @NonNull - @FlaggedApi("MGF1_DIGEST_SETTER") + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { if (mMgf1Digests.isEmpty()) { throw new IllegalStateException("Mask generation function (MGF) not specified"); @@ -633,7 +633,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * @see #getMgf1Digests() */ @NonNull - @FlaggedApi("MGF1_DIGEST_SETTER") + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) public boolean isMgf1DigestsSpecified() { return !mMgf1Digests.isEmpty(); } @@ -1292,7 +1292,7 @@ public final class KeyGenParameterSpec implements AlgorithmParameterSpec, UserAu * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull - @FlaggedApi("MGF1_DIGEST_SETTER") + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) public Builder setMgf1Digests(@NonNull @KeyProperties.DigestEnum String... mgf1Digests) { mMgf1Digests = Set.of(mgf1Digests); return this; diff --git a/keystore/java/android/security/keystore/KeyProtection.java b/keystore/java/android/security/keystore/KeyProtection.java index c1e3bab5d37c..7b6b2d142f95 100644 --- a/keystore/java/android/security/keystore/KeyProtection.java +++ b/keystore/java/android/security/keystore/KeyProtection.java @@ -401,7 +401,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see #isMgf1DigestsSpecified() */ @NonNull - @FlaggedApi("MGF1_DIGEST_SETTER") + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) public @KeyProperties.DigestEnum Set<String> getMgf1Digests() { if (mMgf1Digests.isEmpty()) { throw new IllegalStateException("Mask generation function (MGF) not specified"); @@ -416,7 +416,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * @see #getMgf1Digests() */ @NonNull - @FlaggedApi("MGF1_DIGEST_SETTER") + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) public boolean isMgf1DigestsSpecified() { return !mMgf1Digests.isEmpty(); } @@ -799,7 +799,7 @@ public final class KeyProtection implements ProtectionParameter, UserAuthArgs { * <p>See {@link KeyProperties}.{@code DIGEST} constants. */ @NonNull - @FlaggedApi("MGF1_DIGEST_SETTER") + @FlaggedApi(android.security.Flags.FLAG_MGF1_DIGEST_SETTER) public Builder setMgf1Digests(@Nullable @KeyProperties.DigestEnum String... mgf1Digests) { mMgf1Digests = Set.of(mgf1Digests); return this; diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java index ed4b485f3927..9c05a3a768a0 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreKeyPairGeneratorSpi.java @@ -28,6 +28,7 @@ import android.hardware.security.keymint.SecurityLevel; import android.hardware.security.keymint.Tag; import android.os.Build; import android.os.StrictMode; +import android.security.Flags; import android.security.KeyPairGeneratorSpec; import android.security.KeyStore2; import android.security.KeyStoreException; @@ -853,6 +854,22 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, mgf1Digest )); }); + + /* If the MGF1 Digest setter is not set, fall back to the previous behaviour: + * Add, as MGF1 Digest function, all the primary digests. + * Avoid adding the default MGF1 digest as it will have been included in the + * mKeymasterMgf1Digests field. + */ + if (!getMgf1DigestSetterFlag()) { + final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster( + DEFAULT_MGF1_DIGEST); + ArrayUtils.forEach(mKeymasterDigests, (digest) -> { + if (digest != defaultMgf1Digest) { + params.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, digest)); + } + }); + } } }); ArrayUtils.forEach(mKeymasterSignaturePaddings, (padding) -> { @@ -928,6 +945,16 @@ public abstract class AndroidKeyStoreKeyPairGeneratorSpi extends KeyPairGenerato return params; } + private static boolean getMgf1DigestSetterFlag() { + try { + return Flags.mgf1DigestSetter(); + } catch (SecurityException e) { + Log.w(TAG, "Cannot read MGF1 Digest setter flag value", e); + return false; + } + } + + private void addAlgorithmSpecificParameters(List<KeyParameter> params) { switch (mKeymasterAlgorithm) { case KeymasterDefs.KM_ALGORITHM_RSA: diff --git a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java index ddbd93e458fd..2d8c5a380c6b 100644 --- a/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java +++ b/keystore/java/android/security/keystore2/AndroidKeyStoreSpi.java @@ -25,6 +25,7 @@ import android.hardware.security.keymint.HardwareAuthenticatorType; import android.hardware.security.keymint.KeyParameter; import android.hardware.security.keymint.SecurityLevel; import android.os.StrictMode; +import android.security.Flags; import android.security.GateKeeper; import android.security.KeyStore2; import android.security.KeyStoreParameter; @@ -256,6 +257,15 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { } } + private static boolean getMgf1DigestSetterFlag() { + try { + return Flags.mgf1DigestSetter(); + } catch (SecurityException e) { + Log.w(NAME, "Cannot read MGF1 Digest setter flag value", e); + return false; + } + } + @Override public Date engineGetCreationDate(String alias) { KeyEntryResponse response = getKeyMetadata(alias); @@ -537,11 +547,31 @@ public class AndroidKeyStoreSpi extends KeyStoreSpi { /* Because of default MGF1 digest is SHA-1. It has to be added in Key * characteristics. Otherwise, crypto operations will fail with Incompatible * MGF1 digest. + * If the MGF1 Digest setter flag isn't set, then the condition in the + * if clause above must be false (cannot have MGF1 digests specified if the + * flag was off). In that case, in addition to adding the default MGF1 + * digest, we have to add all the other digests as MGF1 Digests. + * */ importArgs.add(KeyStore2ParameterUtils.makeEnum( KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, KeyProperties.Digest.toKeymaster(DEFAULT_MGF1_DIGEST) )); + if (!getMgf1DigestSetterFlag()) { + final int defaultMgf1Digest = KeyProperties.Digest.toKeymaster( + DEFAULT_MGF1_DIGEST); + for (String digest : spec.getDigests()) { + int digestToAddAsMgf1Digest = KeyProperties.Digest.toKeymaster( + digest); + // Do not add the default MGF1 digest as it has been added above. + if (digestToAddAsMgf1Digest != defaultMgf1Digest) { + importArgs.add(KeyStore2ParameterUtils.makeEnum( + KeymasterDefs.KM_TAG_RSA_OAEP_MGF_DIGEST, + digestToAddAsMgf1Digest + )); + } + } + } } } } 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/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 85ea8097a2c1..7a3210e0a46d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -637,7 +637,7 @@ public class Bubble implements BubbleViewProvider { * @return the last time this bubble was updated or accessed, whichever is most recent. */ long getLastActivity() { - return isAppBubble() ? Long.MAX_VALUE : Math.max(mLastUpdated, mLastAccessed); + return Math.max(mLastUpdated, mLastAccessed); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index 595a4afbfc86..bbb4b74c2a17 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -784,8 +784,7 @@ public class BubbleData { if (bubble.getPendingIntentCanceled() || !(reason == Bubbles.DISMISS_AGED || reason == Bubbles.DISMISS_USER_GESTURE - || reason == Bubbles.DISMISS_RELOAD_FROM_DISK) - || bubble.isAppBubble()) { + || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)) { return; } if (DEBUG_BUBBLE_DATA) { 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/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java index 8eb4a5a1a4c9..f5c01d063707 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java @@ -770,6 +770,7 @@ public class PipAnimationController { } if (mContentOverlay != null) { mContentOverlay.onAnimationEnd(tx, destBounds); + clearContentOverlay(); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java index 850b06a0fb7d..a2bd47c5285e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java @@ -109,7 +109,7 @@ public abstract class PipContentOverlay { @Override public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - // Do nothing. Color overlay should be fully opaque by now. + // Do nothing. Color overlay should be fully opaque by now, ready for fade out. } private float[] getContentOverlayColor(Context context) { @@ -167,7 +167,7 @@ public abstract class PipContentOverlay { @Override public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - atomicTx.remove(mLeash); + // Do nothing. Snapshot overlay should be fully opaque by now, ready for fade out. } } @@ -193,19 +193,12 @@ public abstract class PipContentOverlay { MAX_APP_ICON_SIZE_DP, context.getResources().getDisplayMetrics()); mAppIconSizePx = Math.min(maxAppIconSizePx, appIconSizePx); - final int appWidth = appBounds.width(); - final int appHeight = appBounds.height(); - - // In order to have the overlay always cover the pip window during the transition, the - // overlay will be drawn with the max size of the start and end bounds in different - // rotation. - final int overlaySize = Math.max(Math.max(appWidth, appHeight), - Math.max(destinationBounds.width(), destinationBounds.height())) + 1; + final int overlaySize = getOverlaySize(appBounds, destinationBounds); mOverlayHalfSize = overlaySize >> 1; // When the activity is in the secondary split, make sure the scaling center is not // offset. - mAppBounds = new Rect(0, 0, appWidth, appHeight); + mAppBounds = new Rect(0, 0, appBounds.width(), appBounds.height()); mBitmap = Bitmap.createBitmap(overlaySize, overlaySize, Bitmap.Config.ARGB_8888); prepareAppIconOverlay(appIcon); @@ -215,6 +208,21 @@ public abstract class PipContentOverlay { .build(); } + /** + * Returns the size of the app icon overlay. + * + * In order to have the overlay always cover the pip window during the transition, + * the overlay will be drawn with the max size of the start and end bounds in different + * rotation. + */ + public static int getOverlaySize(Rect appBounds, Rect destinationBounds) { + final int appWidth = appBounds.width(); + final int appHeight = appBounds.height(); + + return Math.max(Math.max(appWidth, appHeight), + Math.max(destinationBounds.width(), destinationBounds.height())) + 1; + } + @Override public void attach(SurfaceControl.Transaction tx, SurfaceControl parentLeash) { tx.show(mLeash); @@ -248,7 +256,7 @@ public abstract class PipContentOverlay { @Override public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) { - atomicTx.remove(mLeash); + // Do nothing. Icon overlay should be fully opaque by now, ready for fade out. } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java index c1164fca22f2..743b1ea197bc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java @@ -156,74 +156,77 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, // These callbacks are called on the update thread private final PipAnimationController.PipAnimationCallback mPipAnimationCallback = new PipAnimationController.PipAnimationCallback() { - private boolean mIsCancelled; - @Override - public void onPipAnimationStart(TaskInfo taskInfo, - PipAnimationController.PipTransitionAnimator animator) { - final int direction = animator.getTransitionDirection(); - mIsCancelled = false; - sendOnPipTransitionStarted(direction); - } - - @Override - public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, - PipAnimationController.PipTransitionAnimator animator) { - final int direction = animator.getTransitionDirection(); - if (mIsCancelled) { - sendOnPipTransitionFinished(direction); - maybePerformFinishResizeCallback(); - return; - } - final int animationType = animator.getAnimationType(); - final Rect destinationBounds = animator.getDestinationBounds(); - if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) { - fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay, true /* withStartDelay*/); - } - if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS - && direction == TRANSITION_DIRECTION_TO_PIP) { - // Notify the display to continue the deferred orientation change. - final WindowContainerTransaction wct = new WindowContainerTransaction(); - wct.scheduleFinishEnterPip(mToken, destinationBounds); - mTaskOrganizer.applyTransaction(wct); - // The final task bounds will be applied by onFixedRotationFinished so that all - // coordinates are in new rotation. - mSurfaceTransactionHelper.round(tx, mLeash, isInPip()); - mDeferredAnimEndTransaction = tx; - return; - } - final boolean isExitPipDirection = isOutPipDirection(direction) - || isRemovePipDirection(direction); - if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP - || isExitPipDirection) { - // execute the finish resize callback if needed after the transaction is committed - tx.addTransactionCommittedListener(mMainExecutor, - PipTaskOrganizer.this::maybePerformFinishResizeCallback); - - // Finish resize as long as we're not exiting PIP, or, if we are, only if this is - // the end of an exit PIP animation. - // This is necessary in case there was a resize animation ongoing when exit PIP - // started, in which case the first resize will be skipped to let the exit - // operation handle the final resize out of PIP mode. See b/185306679. - finishResizeDelayedIfNeeded(() -> { - finishResize(tx, destinationBounds, direction, animationType); - sendOnPipTransitionFinished(direction); - }); - } - } + private boolean mIsCancelled; - @Override - public void onPipAnimationCancel(TaskInfo taskInfo, - PipAnimationController.PipTransitionAnimator animator) { - final int direction = animator.getTransitionDirection(); - mIsCancelled = true; - if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) { - fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay, true /* withStartDelay */); - } - sendOnPipTransitionCancelled(direction); - } - }; + @Override + public void onPipAnimationStart(TaskInfo taskInfo, + PipAnimationController.PipTransitionAnimator animator) { + final int direction = animator.getTransitionDirection(); + mIsCancelled = false; + sendOnPipTransitionStarted(direction); + } + + @Override + public void onPipAnimationEnd(TaskInfo taskInfo, SurfaceControl.Transaction tx, + PipAnimationController.PipTransitionAnimator animator) { + final int direction = animator.getTransitionDirection(); + if (mIsCancelled) { + sendOnPipTransitionFinished(direction); + maybePerformFinishResizeCallback(); + return; + } + final int animationType = animator.getAnimationType(); + final Rect destinationBounds = animator.getDestinationBounds(); + if (isInPipDirection(direction) && mPipOverlay != null) { + fadeOutAndRemoveOverlay(mPipOverlay, + null /* callback */, true /* withStartDelay*/); + } + if (mWaitForFixedRotation && animationType == ANIM_TYPE_BOUNDS + && direction == TRANSITION_DIRECTION_TO_PIP) { + // Notify the display to continue the deferred orientation change. + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.scheduleFinishEnterPip(mToken, destinationBounds); + mTaskOrganizer.applyTransaction(wct); + // The final task bounds will be applied by onFixedRotationFinished so + // that all coordinates are in new rotation. + mSurfaceTransactionHelper.round(tx, mLeash, isInPip()); + mDeferredAnimEndTransaction = tx; + return; + } + final boolean isExitPipDirection = isOutPipDirection(direction) + || isRemovePipDirection(direction); + if (mPipTransitionState.getTransitionState() != PipTransitionState.EXITING_PIP + || isExitPipDirection) { + // execute the finish resize callback if needed after the transaction is + // committed + tx.addTransactionCommittedListener(mMainExecutor, + PipTaskOrganizer.this::maybePerformFinishResizeCallback); + + // Finish resize as long as we're not exiting PIP, or, if we are, only if + // this is the end of an exit PIP animation. + // This is necessary in case there was a resize animation ongoing when + // exit PIP started, in which case the first resize will be skipped to + // let the exit operation handle the final resize out of PIP mode. + // See b/185306679. + finishResizeDelayedIfNeeded(() -> { + finishResize(tx, destinationBounds, direction, animationType); + sendOnPipTransitionFinished(direction); + }); + } + } + + @Override + public void onPipAnimationCancel(TaskInfo taskInfo, + PipAnimationController.PipTransitionAnimator animator) { + final int direction = animator.getTransitionDirection(); + mIsCancelled = true; + if (isInPipDirection(direction) && mPipOverlay != null) { + fadeOutAndRemoveOverlay(mPipOverlay, + null /* callback */, true /* withStartDelay */); + } + sendOnPipTransitionCancelled(direction); + } + }; /** * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu. @@ -327,11 +330,18 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, /** * An optional overlay used to mask content changing between an app in/out of PiP, only set if - * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true. + * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true, only in gesture nav. */ @Nullable SurfaceControl mSwipePipToHomeOverlay; + /** + * An optional overlay used to mask content changing between an app in/out of PiP, only set if + * {@link PipTransitionState#getInSwipePipToHomeTransition()} is false. + */ + @Nullable + SurfaceControl mPipOverlay; + public PipTaskOrganizer(Context context, @NonNull SyncTransactionQueue syncTransactionQueue, @NonNull PipTransitionState pipTransitionState, @@ -1766,6 +1776,7 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, animator.setSnapshotContentOverlay(snapshot, sourceHintRect); } } + mPipOverlay = animator.getContentOverlayLeash(); // The destination bounds are used for the end rect of animation and the final bounds // after animation finishes. So after the animation is started, the destination bounds // can be updated to new rotation (computeRotatedBounds has changed the DisplayLayout @@ -1879,6 +1890,14 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, } private void removeContentOverlay(SurfaceControl surface, Runnable callback) { + if (mPipOverlay != null) { + if (mPipOverlay != surface) { + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: trying to remove overlay (%s) which is not local reference (%s)", + TAG, surface, mPipOverlay); + } + mPipOverlay = null; + } if (mPipTransitionState.getTransitionState() == PipTransitionState.UNDEFINED) { // Avoid double removal, which is fatal. return; @@ -1907,11 +1926,11 @@ public class PipTaskOrganizer implements ShellTaskOrganizer.TaskListener, private void cancelCurrentAnimator() { final PipAnimationController.PipTransitionAnimator<?> animator = mPipAnimationController.getCurrentAnimator(); + // remove any overlays if present + if (mPipOverlay != null) { + removeContentOverlay(mPipOverlay, null /* callback */); + } if (animator != null) { - if (animator.getContentOverlayLeash() != null) { - removeContentOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay); - } PipAnimationController.quietCancel(animator); mPipAnimationController.resetAnimatorState(); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java index fe4980a9eb16..0f3c16220dee 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java @@ -43,6 +43,7 @@ import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SP import static com.android.wm.shell.transition.Transitions.TRANSIT_REMOVE_PIP; import android.animation.Animator; +import android.annotation.IntDef; import android.app.ActivityManager; import android.app.TaskInfo; import android.content.Context; @@ -76,6 +77,8 @@ import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.util.TransitionUtil; import java.io.PrintWriter; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.Optional; /** @@ -86,6 +89,18 @@ public class PipTransition extends PipTransitionController { private static final String TAG = PipTransition.class.getSimpleName(); + /** No fixed rotation, or fixed rotation state is undefined. */ + private static final int FIXED_ROTATION_UNDEFINED = 0; + /** + * Fixed rotation detected via callbacks (see PipController#startSwipePipToHome()); + * this is used in the swipe PiP to home case, since the transitions itself isn't supposed to + * see the fixed rotation. + */ + private static final int FIXED_ROTATION_CALLBACK = 1; + + /** Fixed rotation detected in the incoming transition. */ + private static final int FIXED_ROTATION_TRANSITION = 2; + private final Context mContext; private final PipTransitionState mPipTransitionState; private final PipDisplayLayoutState mPipDisplayLayoutState; @@ -106,17 +121,28 @@ public class PipTransition extends PipTransitionController { /** The Task window that is currently in PIP windowing mode. */ @Nullable private WindowContainerToken mCurrentPipTaskToken; - /** Whether display is in fixed rotation. */ - private boolean mInFixedRotation; + + @IntDef(prefix = { "FIXED_ROTATION_" }, value = { + FIXED_ROTATION_UNDEFINED, + FIXED_ROTATION_CALLBACK, + FIXED_ROTATION_TRANSITION + }) + @Retention(RetentionPolicy.SOURCE) + public @interface FixedRotationState {} + + /** Fixed rotation state of the display. */ + private @FixedRotationState int mFixedRotationState = FIXED_ROTATION_UNDEFINED; /** * The rotation that the display will apply after expanding PiP to fullscreen. This is only - * meaningful if {@link #mInFixedRotation} is true. + * meaningful if {@link #mFixedRotationState} is {@link #FIXED_ROTATION_TRANSITION}. */ @Surface.Rotation private int mEndFixedRotation; /** Whether the PIP window has fade out for fixed rotation. */ private boolean mHasFadeOut; + private Rect mInitBounds = new Rect(); + /** Used for setting transform to a transaction from animator. */ private final PipAnimationController.PipTransactionHandler mTransactionConsumer = new PipAnimationController.PipTransactionHandler() { @@ -181,8 +207,16 @@ public class PipTransition extends PipTransitionController { @NonNull Transitions.TransitionFinishCallback finishCallback) { final TransitionInfo.Change currentPipTaskChange = findCurrentPipTaskChange(info); final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); - mInFixedRotation = fixedRotationChange != null; - mEndFixedRotation = mInFixedRotation + if (mFixedRotationState == FIXED_ROTATION_TRANSITION) { + // If we are just about to process potential fixed rotation information, + // then fixed rotation state should either be UNDEFINED or CALLBACK. + ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "%s: startAnimation() should start with clear fixed rotation state", TAG); + mFixedRotationState = FIXED_ROTATION_UNDEFINED; + } + mFixedRotationState = fixedRotationChange != null + ? FIXED_ROTATION_TRANSITION : mFixedRotationState; + mEndFixedRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; @@ -347,6 +381,10 @@ public class PipTransition extends PipTransitionController { @Override public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted, @Nullable SurfaceControl.Transaction finishT) { + // Transition either finished pre-emptively, got merged, or aborted, + // so update fixed rotation state to default. + mFixedRotationState = FIXED_ROTATION_UNDEFINED; + if (transition != mExitTransition) { return; } @@ -408,7 +446,8 @@ public class PipTransition extends PipTransitionController { // done at the start. But if it is running fixed rotation, there will be a seamless // display transition later. So the last rotation transform needs to be kept to // avoid flickering, and then the display transition will reset the transform. - if (!mInFixedRotation && mFinishTransaction != null) { + if (mFixedRotationState != FIXED_ROTATION_TRANSITION + && mFinishTransaction != null) { mFinishTransaction.merge(tx); } } else { @@ -426,12 +465,27 @@ public class PipTransition extends PipTransitionController { mSurfaceTransactionHelper.crop(tx, leash, destinationBounds) .resetScale(tx, leash, destinationBounds) .round(tx, leash, true /* applyCornerRadius */); + if (mPipOrganizer.mSwipePipToHomeOverlay != null && !mInitBounds.isEmpty()) { + // Resetting the scale for pinned task while re-adjusting its crop, + // also scales the overlay. So we need to update the overlay leash too. + Rect overlayBounds = new Rect(destinationBounds); + final int overlaySize = PipContentOverlay.PipAppIconOverlay + .getOverlaySize(mInitBounds, destinationBounds); + + overlayBounds.offsetTo( + (destinationBounds.width() - overlaySize) / 2, + (destinationBounds.height() - overlaySize) / 2); + mSurfaceTransactionHelper.resetScale(tx, + mPipOrganizer.mSwipePipToHomeOverlay, overlayBounds); + } } + mInitBounds.setEmpty(); wct.setBoundsChangeTransaction(taskInfo.token, tx); } final int displayRotation = taskInfo.getConfiguration().windowConfiguration .getDisplayRotation(); - if (enteringPip && mInFixedRotation && mEndFixedRotation != displayRotation + if (enteringPip && mFixedRotationState == FIXED_ROTATION_TRANSITION + && mEndFixedRotation != displayRotation && hasValidLeash) { // Launcher may update the Shelf height during the animation, which will update the // destination bounds. Because this is in fixed rotation, We need to make sure the @@ -451,6 +505,8 @@ public class PipTransition extends PipTransitionController { mFinishTransaction = null; callFinishCallback(wct); } + // This is the end of transition on the Shell side so update the fixed rotation state. + mFixedRotationState = FIXED_ROTATION_UNDEFINED; finishResizeForMenu(destinationBounds); } @@ -467,6 +523,7 @@ public class PipTransition extends PipTransitionController { // mFinishCallback might be null with an outdated mCurrentPipTaskToken // for example, when app crashes while in PiP and exit transition has not started mCurrentPipTaskToken = null; + mFixedRotationState = FIXED_ROTATION_UNDEFINED; if (mFinishCallback == null) return; mFinishCallback.onTransitionFinished(null /* wct */); mFinishCallback = null; @@ -475,6 +532,9 @@ public class PipTransition extends PipTransitionController { @Override public void onFixedRotationStarted() { + if (mFixedRotationState == FIXED_ROTATION_UNDEFINED) { + mFixedRotationState = FIXED_ROTATION_CALLBACK; + } fadeEnteredPipIfNeed(false /* show */); } @@ -656,7 +716,7 @@ public class PipTransition extends PipTransitionController { // Check if it is fixed rotation. final int rotationDelta; - if (mInFixedRotation) { + if (mFixedRotationState == FIXED_ROTATION_TRANSITION) { final int startRotation = pipChange.getStartRotation(); final int endRotation = mEndFixedRotation; rotationDelta = deltaRotation(startRotation, endRotation); @@ -873,11 +933,13 @@ public class PipTransition extends PipTransitionController { final int startRotation = pipChange.getStartRotation(); // Check again in case some callers use startEnterAnimation directly so the flag was not // set in startAnimation, e.g. from DefaultMixedHandler. - if (!mInFixedRotation) { + if (mFixedRotationState != FIXED_ROTATION_TRANSITION) { mEndFixedRotation = pipChange.getEndFixedRotation(); - mInFixedRotation = mEndFixedRotation != ROTATION_UNDEFINED; + mFixedRotationState = mEndFixedRotation != ROTATION_UNDEFINED + ? FIXED_ROTATION_TRANSITION : mFixedRotationState; } - final int endRotation = mInFixedRotation ? mEndFixedRotation : pipChange.getEndRotation(); + final int endRotation = mFixedRotationState == FIXED_ROTATION_TRANSITION + ? mEndFixedRotation : pipChange.getEndRotation(); setBoundsStateForEntry(taskInfo.topActivity, taskInfo.pictureInPictureParams, taskInfo.topActivityInfo); @@ -888,10 +950,15 @@ public class PipTransition extends PipTransitionController { final Rect destinationBounds = mPipBoundsAlgorithm.getEntryDestinationBounds(); final Rect currentBounds = pipChange.getStartAbsBounds(); + + // Cache the start bounds for overlay manipulations as a part of finishCallback. + mInitBounds.set(currentBounds); + int rotationDelta = deltaRotation(startRotation, endRotation); Rect sourceHintRect = PipBoundsAlgorithm.getValidSourceHintRect( taskInfo.pictureInPictureParams, currentBounds, destinationBounds); - if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { + if (rotationDelta != Surface.ROTATION_0 + && mFixedRotationState == FIXED_ROTATION_TRANSITION) { // Need to get the bounds of new rotation in old rotation for fixed rotation, computeEnterPipRotatedBounds(rotationDelta, startRotation, endRotation, taskInfo, destinationBounds, sourceHintRect); @@ -955,10 +1022,12 @@ public class PipTransition extends PipTransitionController { } else { throw new RuntimeException("Unrecognized animation type: " + enterAnimationType); } + mPipOrganizer.mPipOverlay = animator.getContentOverlayLeash(); animator.setTransitionDirection(TRANSITION_DIRECTION_TO_PIP) .setPipAnimationCallback(mPipAnimationCallback) .setDuration(mEnterExitAnimationDuration); - if (rotationDelta != Surface.ROTATION_0 && mInFixedRotation) { + if (rotationDelta != Surface.ROTATION_0 + && mFixedRotationState == FIXED_ROTATION_TRANSITION) { // For fixed rotation, the animation destination bounds is in old rotation coordinates. // Set the destination bounds to new coordinates after the animation is finished. // ComputeRotatedBounds has changed the DisplayLayout without affecting the animation. @@ -997,13 +1066,17 @@ public class PipTransition extends PipTransitionController { @NonNull SurfaceControl leash, @Nullable Rect sourceHintRect, @NonNull Rect destinationBounds, @NonNull ActivityManager.RunningTaskInfo pipTaskInfo) { - if (mInFixedRotation) { + if (mFixedRotationState == FIXED_ROTATION_TRANSITION) { // If rotation changes when returning to home, the transition should contain both the // entering PiP and the display change (PipController#startSwipePipToHome has updated // the display layout to new rotation). So it is not expected to see fixed rotation. ProtoLog.w(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "%s: SwipePipToHome should not use fixed rotation %d", TAG, mEndFixedRotation); } + Rect appBounds = pipTaskInfo.configuration.windowConfiguration.getAppBounds(); + if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) { + mInitBounds.set(appBounds); + } final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay; if (swipePipToHomeOverlay != null) { // Launcher fade in the overlay on top of the fullscreen Task. It is possible we diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java index 20c57fa5e566..04911c0bc064 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransitionController.java @@ -78,9 +78,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH if (direction == TRANSITION_DIRECTION_REMOVE_STACK) { return; } - if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) { - mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay, true /* withStartDelay*/); + if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) { + mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, + null /* callback */, true /* withStartDelay*/); } onFinishResize(taskInfo, animator.getDestinationBounds(), direction, tx); sendOnPipTransitionFinished(direction); @@ -90,9 +90,9 @@ public abstract class PipTransitionController implements Transitions.TransitionH public void onPipAnimationCancel(TaskInfo taskInfo, PipAnimationController.PipTransitionAnimator animator) { final int direction = animator.getTransitionDirection(); - if (isInPipDirection(direction) && animator.getContentOverlayLeash() != null) { - mPipOrganizer.fadeOutAndRemoveOverlay(animator.getContentOverlayLeash(), - animator::clearContentOverlay, true /* withStartDelay */); + if (isInPipDirection(direction) && mPipOrganizer.mPipOverlay != null) { + mPipOrganizer.fadeOutAndRemoveOverlay(mPipOrganizer.mPipOverlay, + null /* callback */, true /* withStartDelay */); } sendOnPipTransitionCancelled(animator.getTransitionDirection()); } 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 79c20761abed..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; @@ -822,8 +823,8 @@ public class RecentsTransitionHandler implements Transitions.TransitionHandler { } else if (!didMergeThings) { // Didn't recognize anything in incoming transition so don't merge it. Slog.w(TAG, "Don't know how to merge this transition, foundRecentsClosing=" - + foundRecentsClosing); - if (foundRecentsClosing) { + + foundRecentsClosing + " recentsTaskId=" + mRecentsTaskId); + if (foundRecentsClosing || mRecentsTaskId < 0) { mWillFinishToHome = false; cancel(false /* toHome */, false /* withScreenshots */, "didn't merge"); } @@ -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/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index 56f1c784f3a7..7b5709769369 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -847,9 +847,12 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1)) .orElse(null); if (taskInfo != null) { - startTask(taskInfo.taskId, position, options); - ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, - "Start task in background"); + if (ENABLE_SHELL_TRANSITIONS) { + mStageCoordinator.startTask(taskInfo.taskId, position, options); + } else { + startTask(taskInfo.taskId, position, options); + } + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "Start task in background"); return; } if (samePackage(packageName1, packageName2, userId1, userId2)) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java index be685b57f779..449bef5608c9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java @@ -263,6 +263,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, mStartIntent2 = startIntent2; mActivatePosition = position; } + SplitRequest(int taskId1, int position) { + mActivateTaskId = taskId1; + mActivatePosition = position; + } SplitRequest(int taskId1, int taskId2, int position) { mActivateTaskId = taskId1; mActivateTaskId2 = taskId2; @@ -556,6 +560,27 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + /** Use this method to launch an existing Task via a taskId */ + void startTask(int taskId, @SplitPosition int position, @Nullable Bundle options) { + mSplitRequest = new SplitRequest(taskId, position); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + options = resolveStartStage(STAGE_TYPE_UNDEFINED, position, options, null /* wct */); + wct.startTask(taskId, options); + // If this should be mixed, send the task to avoid split handle transition directly. + if (mMixedHandler != null && mMixedHandler.shouldSplitEnterMixed(taskId, mTaskOrganizer)) { + mTaskOrganizer.applyTransaction(wct); + return; + } + + // If split screen is not activated, we're expecting to open a pair of apps to split. + final int extraTransitType = mMainStage.isActive() + ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN; + prepareEnterSplitScreen(wct, null /* taskInfo */, position, !mIsDropEntering); + + mSplitTransitions.startEnterTransition(TRANSIT_TO_FRONT, wct, null, this, + extraTransitType, !mIsDropEntering); + } + /** Launches an activity into split. */ void startIntent(PendingIntent intent, Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options) { @@ -1593,7 +1618,9 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, // Ensure to evict old splitting tasks because the new split pair might be composed by // one of the splitting tasks, evicting the task when finishing entering transition // won't guarantee to put the task to the indicated new position. - mMainStage.evictAllChildren(wct); + if (!mIsDropEntering) { + mMainStage.evictAllChildren(wct); + } mMainStage.reparentTopTask(wct); prepareSplitLayout(wct, resizeAnim); } 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..9f20f49b4094 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 @@ -48,9 +48,9 @@ import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.ShellTaskOrganizer; 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 +298,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) { @@ -902,6 +902,18 @@ public class DefaultMixedHandler implements Transitions.TransitionHandler, return false; } + /** Use to when split use taskId to enter, check if this enter transition should be mixed or + * not.*/ + public boolean shouldSplitEnterMixed(int taskId, ShellTaskOrganizer shellTaskOrganizer) { + // Check if this intent package is same as pip one or not, if true we want let the pip + // task enter split. + if (mPipHandler != null) { + return mPipHandler.isInPipPackage( + SplitScreenUtils.getPackageName(taskId, shellTaskOrganizer)); + } + return false; + } + /** @return whether the transition-request represents a pip-entry. */ public boolean requestHasPipEnter(TransitionRequestInfo request) { return mPipHandler.requestHasPipEnter(request); 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/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java index 4bca96b187b5..dab762f233e2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java @@ -221,6 +221,22 @@ public class BubbleDataTest extends ShellTestCase { } @Test + public void testAddAppBubble_setsTime() { + // Setup + mBubbleData.setListener(mListener); + + // Test + assertThat(mAppBubble.getLastActivity()).isEqualTo(0); + setCurrentTime(1000); + mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/, + false /* showInShade */); + + // Verify + assertThat(mBubbleData.getBubbleInStackWithKey(mAppBubble.getKey())).isEqualTo(mAppBubble); + assertThat(mAppBubble.getLastActivity()).isEqualTo(1000); + } + + @Test public void testRemoveBubble() { // Setup sendUpdatedEntryAtTime(mEntryA1, 1000); @@ -1162,7 +1178,7 @@ public class BubbleDataTest extends ShellTestCase { } @Test - public void test_removeAppBubble_skipsOverflow() { + public void test_removeAppBubble_overflows() { String appBubbleKey = mAppBubble.getKey(); mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/, false /* showInShade */); @@ -1170,7 +1186,7 @@ public class BubbleDataTest extends ShellTestCase { mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_USER_GESTURE); - assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull(); + assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isEqualTo(mAppBubble); assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull(); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java index 99cd4f391153..855b7ee04702 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java @@ -235,7 +235,7 @@ public class SplitScreenControllerTests extends ShellTestCase { mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null); - verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), + verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); verify(mSplitScreenController, never()).supportMultiInstancesSplit(any()); verify(mStageCoordinator, never()).switchSplitPosition(any()); @@ -243,7 +243,6 @@ public class SplitScreenControllerTests extends ShellTestCase { @Test public void startIntent_multiInstancesSupported_startTaskInBackgroundAfterSplitActivated() { - doReturn(true).when(mSplitScreenController).supportMultiInstancesSplit(any()); doNothing().when(mSplitScreenController).startTask(anyInt(), anyInt(), any()); Intent startIntent = createStartIntent("startActivity"); PendingIntent pendingIntent = @@ -260,8 +259,8 @@ public class SplitScreenControllerTests extends ShellTestCase { mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, SPLIT_POSITION_TOP_OR_LEFT, null); - - verify(mSplitScreenController).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), + verify(mSplitScreenController, never()).supportMultiInstancesSplit(any()); + verify(mStageCoordinator).startTask(anyInt(), eq(SPLIT_POSITION_TOP_OR_LEFT), isNull()); } 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/libs/hwui/jni/YuvToJpegEncoder.cpp b/libs/hwui/jni/YuvToJpegEncoder.cpp index 0275e4f13b3b..353300186555 100644 --- a/libs/hwui/jni/YuvToJpegEncoder.cpp +++ b/libs/hwui/jni/YuvToJpegEncoder.cpp @@ -401,7 +401,7 @@ bool P010Yuv420ToJpegREncoder::encode(JNIEnv* env, if (int success = jpegREncoder.encodeJPEGR(&p010, &yuv420, hdrTransferFunction, &jpegR, jpegQuality, - exif.length > 0 ? &exif : NULL); success != android::OK) { + exif.length > 0 ? &exif : NULL); success != JPEGR_NO_ERROR) { ALOGW("Encode JPEG/R failed, error code: %d.", success); return false; } diff --git a/libs/input/PointerController.cpp b/libs/input/PointerController.cpp index 65e16056c106..bba9c9764eee 100644 --- a/libs/input/PointerController.cpp +++ b/libs/input/PointerController.cpp @@ -218,6 +218,15 @@ void PointerController::setPresentation(Presentation presentation) { mLocked.presentation = presentation; + if (input_flags::enable_pointer_choreographer()) { + // When pointer choreographer is enabled, the presentation mode is only set once when the + // PointerController is constructed, before the display viewport is provided. + // TODO(b/293587049): Clean up the PointerController interface after pointer choreographer + // is permanently enabled. The presentation can be set in the constructor. + mCursorController.setStylusHoverMode(presentation == Presentation::STYLUS_HOVER); + return; + } + if (!mCursorController.isViewportValid()) { return; } 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/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt index e761a33cf67a..caceb6f6ec4a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt @@ -38,6 +38,8 @@ import androidx.compose.ui.viewinterop.AndroidView import com.android.settingslib.spa.framework.theme.divider import com.github.mikephil.charting.charts.BarChart import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.components.YAxis +import com.github.mikephil.charting.components.YAxis.AxisDependency import com.github.mikephil.charting.data.BarData import com.github.mikephil.charting.data.BarDataSet import com.github.mikephil.charting.data.BarEntry @@ -90,6 +92,10 @@ interface BarChartModel { /** If set to true, touch gestures are enabled on the [BarChart]. */ val enableBarchartTouch: Boolean get() = true + + /** The renderer provider for x-axis. */ + val xAxisRendererProvider: XAxisRendererProvider? + get() = null } data class BarChartData( @@ -143,6 +149,16 @@ fun BarChart(barChartModel: BarChartModel) { yOffset = 10f } + barChartModel.xAxisRendererProvider?.let { + setXAxisRenderer( + it.provideXAxisRenderer( + getViewPortHandler(), + getXAxis(), + getTransformer(YAxis.AxisDependency.LEFT) + ) + ) + } + axisLeft.apply { axisMaximum = barChartModel.yAxisMaxValue axisMinimum = barChartModel.yAxisMinValue diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/XAxisRendererProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/XAxisRendererProvider.kt new file mode 100644 index 000000000000..6569d25dd00d --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/XAxisRendererProvider.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.settingslib.spa.widget.chart + +import com.github.mikephil.charting.components.XAxis +import com.github.mikephil.charting.renderer.XAxisRenderer +import com.github.mikephil.charting.utils.Transformer +import com.github.mikephil.charting.utils.ViewPortHandler + +/** A provider for [XAxisRenderer] objects. */ +fun interface XAxisRendererProvider { + + /** Provides an object of [XAxisRenderer] type. */ + fun provideXAxisRenderer( + viewPortHandler: ViewPortHandler, + xAxis: XAxis, + transformer: Transformer + ): XAxisRenderer +}
\ No newline at end of file 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/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java index 5860bdabe764..4384400eaa88 100644 --- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java +++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java @@ -89,8 +89,14 @@ public class InputMethodSettingValuesWrapper { public void refreshAllInputMethodAndSubtypes() { mMethodList.clear(); - mMethodList.addAll(mImm.getInputMethodListAsUser( - mContentResolver.getUserId(), DirectBootAwareness.ANY)); + List<InputMethodInfo> imis = mImm.getInputMethodListAsUser( + mContentResolver.getUserId(), DirectBootAwareness.ANY); + for (int i = 0; i < imis.size(); ++i) { + InputMethodInfo imi = imis.get(i); + if (!imi.isVirtualDeviceOnly()) { + mMethodList.add(imi); + } + } } public List<InputMethodInfo> getInputMethodList() { 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/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 7cec99d4189f..8f459c647316 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -95,7 +95,9 @@ final class SettingsState { static final int SETTINGS_VERSION_NEW_ENCODING = 121; + // LINT.IfChange public static final int MAX_LENGTH_PER_STRING = 32768; + // LINT.ThenChange(/services/core/java/com/android/server/audio/AudioDeviceInventory.java:settings_max_length_per_string) private static final long WRITE_SETTINGS_DELAY_MILLIS = 200; private static final long MAX_WRITE_SETTINGS_DELAY_MILLIS = 2000; diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index c2c5e001a5df..7061e2cb8a4e 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -281,6 +281,7 @@ android_library { "com_android_systemui_flags_lib", "com_android_systemui_shared_flags_lib", "flag-junit-base", + "platform-parametric-runner-lib", "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", "androidx.recyclerview_recyclerview", diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp index 8b12f3c738cc..6c75b4346739 100644 --- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp +++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp @@ -59,6 +59,4 @@ android_app { platform_apis: true, resource_dirs: ["res"], certificate: "platform", - // This app uses allowlisted privileged permissions. - privileged: true, } 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/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index c52a89c9b05e..a26b311b1f8f 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -138,6 +138,13 @@ flag { } flag { + name: "qs_new_tiles" + namespace: "systemui" + description: "Use the new tiles in the Quick Settings. Should have no behavior changes." + bug: "241772429" +} + +flag { name: "coroutine_tracing" namespace: "systemui" description: "Adds thread-local data to System UI's global coroutine scopes to " @@ -204,6 +211,13 @@ flag { } flag { + name: "enable_layout_tracing" + namespace: "systemui" + description: "Enables detailed traversal slices during measure and layout in perfetto traces" + bug: "315274804" +} + +flag { name: "quick_settings_visual_haptics_longpress" namespace: "systemui" description: "Enable special visual and haptic effects for quick settings tiles with long-press actions" 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..47f5663f25a7 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 @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalFoundationApi::class) + package com.android.systemui.bouncer.ui.composable import android.app.AlertDialog @@ -29,18 +31,18 @@ import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width -import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.KeyboardArrowDown @@ -51,6 +53,7 @@ import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.windowsizeclass.WindowHeightSizeClass import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue @@ -78,6 +81,7 @@ import com.android.compose.animation.scene.SceneScope import com.android.compose.animation.scene.SceneTransitionLayout import com.android.compose.animation.scene.transitions import com.android.compose.modifiers.thenIf +import com.android.compose.windowsizeclass.LocalWindowSizeClass import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel @@ -100,37 +104,41 @@ fun BouncerContent( dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, ) { - val isFullScreenUserSwitcherEnabled = viewModel.isUserSwitcherVisible val isSideBySideSupported by viewModel.isSideBySideSupported.collectAsState() val layout = calculateLayout(isSideBySideSupported = isSideBySideSupported) - when (layout) { - BouncerSceneLayout.STANDARD -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = modifier, - ) - BouncerSceneLayout.SIDE_BY_SIDE -> - SideBySideLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, - modifier = modifier, - ) - BouncerSceneLayout.STACKED -> - StackedLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - isUserSwitcherVisible = isFullScreenUserSwitcherEnabled, - modifier = modifier, - ) - BouncerSceneLayout.SPLIT -> - SplitLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - modifier = modifier, - ) + Box( + // Allows the content within each of the layouts to react to the appearance and + // disappearance of the IME, which is also known as the software keyboard. + // + // Despite the keyboard only being part of the password bouncer, adding it at this level is + // both necessary to properly handle the keyboard in all layouts and harmless in cases when + // the keyboard isn't used (like the PIN or pattern auth methods). + modifier = modifier.imePadding(), + ) { + when (layout) { + BouncerSceneLayout.STANDARD_BOUNCER -> + StandardLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.BESIDE_USER_SWITCHER -> + BesideUserSwitcherLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.BELOW_USER_SWITCHER -> + BelowUserSwitcherLayout( + viewModel = viewModel, + ) + BouncerSceneLayout.SPLIT_BOUNCER -> + SplitLayout( + viewModel = viewModel, + ) + } + + Dialog( + viewModel = viewModel, + dialogFactory = dialogFactory, + ) } } @@ -141,15 +149,333 @@ fun BouncerContent( @Composable private fun StandardLayout( viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, modifier: Modifier = Modifier, - layout: BouncerSceneLayout = BouncerSceneLayout.STANDARD, - outputOnly: Boolean = false, +) { + val isHeightExpanded = + LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded + + FoldAware( + modifier = + modifier.padding( + top = 92.dp, + bottom = 48.dp, + ), + viewModel = viewModel, + aboveFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + StatusMessage( + viewModel = viewModel, + modifier = Modifier, + ) + + OutputArea( + viewModel = viewModel, + modifier = Modifier.padding(top = if (isHeightExpanded) 96.dp else 64.dp), + ) + } + }, + belowFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + Box( + modifier = Modifier.weight(1f), + ) { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = false, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + }, + ) +} + +/** + * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable + * by double-tapping on the side. + */ +@Composable +private fun SplitLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val authMethod by viewModel.authMethodViewModel.collectAsState() + + Row( + modifier = + modifier + .fillMaxHeight() + .padding( + horizontal = 24.dp, + vertical = if (authMethod is PasswordBouncerViewModel) 24.dp else 48.dp, + ), + ) { + // Left side (in left-to-right locales). + Box( + modifier = Modifier.fillMaxHeight().weight(1f), + ) { + when (authMethod) { + is PinBouncerViewModel -> { + StatusMessage( + viewModel = viewModel, + modifier = Modifier.align(Alignment.TopCenter), + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.align(Alignment.Center)) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter).padding(top = 48.dp), + ) + } + is PatternBouncerViewModel -> { + StatusMessage( + viewModel = viewModel, + modifier = Modifier.align(Alignment.TopCenter), + ) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter).padding(vertical = 48.dp), + ) + } + is PasswordBouncerViewModel -> { + ActionArea( + viewModel = viewModel, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + else -> Unit + } + } + + // Right side (in right-to-left locales). + Box( + modifier = Modifier.fillMaxHeight().weight(1f), + ) { + when (authMethod) { + is PinBouncerViewModel, + is PatternBouncerViewModel -> { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 8.dp, + centerPatternDotsVertically = true, + modifier = Modifier.align(Alignment.Center), + ) + } + is PasswordBouncerViewModel -> { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth().align(Alignment.Center), + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + } + } + else -> Unit + } + } + } +} + +/** + * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap + * anywhere on the background to flip their positions. + */ +@Composable +private fun BesideUserSwitcherLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val layoutDirection = LocalLayoutDirection.current + val isLeftToRight = layoutDirection == LayoutDirection.Ltr + val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } + val isHeightExpanded = + LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Expanded + val authMethod by viewModel.authMethodViewModel.collectAsState() + + Row( + modifier = + modifier.pointerInput(Unit) { + detectTapGestures( + onDoubleTap = { offset -> + // Depending on where the user double tapped, switch the elements such that + // the endContent is closer to the side that was double tapped. + setSwapped(offset.x < size.width / 2) + } + ) + }, + ) { + val animatedOffset by + animateFloatAsState( + targetValue = + if (!isSwapped) { + // A non-swapped element has its natural placement so it's not offset. + 0f + } else if (isLeftToRight) { + // A swapped element has its elements offset horizontally. In the case of + // LTR locales, this means pushing the element to the right, hence the + // positive number. + 1f + } else { + // A swapped element has its elements offset horizontally. In the case of + // RTL locales, this means pushing the element to the left, hence the + // negative number. + -1f + }, + label = "offset", + ) + + fun Modifier.swappable(inversed: Boolean = false): Modifier { + return graphicsLayer { + translationX = + size.width * + animatedOffset * + if (inversed) { + // A negative sign is used to make sure this is offset in the direction + // that's opposite to the direction that the user switcher is pushed in. + -1 + } else { + 1 + } + alpha = animatedAlpha(animatedOffset) + } + } + + UserSwitcher( + viewModel = viewModel, + modifier = Modifier.weight(1f).align(Alignment.CenterVertically).swappable(), + ) + + FoldAware( + modifier = + Modifier.weight(1f) + .padding( + if (isHeightExpanded) { + PaddingValues(vertical = 128.dp) + } else { + PaddingValues(top = 94.dp, bottom = 48.dp) + } + ) + .swappable(inversed = true), + viewModel = viewModel, + aboveFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + } + }, + belowFold = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + val isOutputAreaVisible = authMethod !is PatternBouncerViewModel + // If there is an output area and the window is not tall enough, spacing needs + // to be added between the input and the output areas (otherwise the two get + // very squished together). + val addSpacingBetweenOutputAndInput = isOutputAreaVisible && !isHeightExpanded + + Box( + modifier = + Modifier.weight(1f) + .padding(top = (if (addSpacingBetweenOutputAndInput) 24 else 0).dp), + ) { + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = true, + modifier = Modifier.align(Alignment.BottomCenter), + ) + } + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + }, + ) + } +} + +/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ +@Composable +private fun BelowUserSwitcherLayout( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + Column( + modifier = + modifier.padding( + vertical = 128.dp, + ) + ) { + UserSwitcher( + viewModel = viewModel, + modifier = Modifier.fillMaxWidth(), + ) + + Spacer(Modifier.weight(1f)) + + Box(modifier = Modifier.fillMaxWidth()) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth(), + ) { + StatusMessage( + viewModel = viewModel, + ) + + OutputArea(viewModel = viewModel, modifier = Modifier.padding(top = 24.dp)) + + InputArea( + viewModel = viewModel, + pinButtonRowVerticalSpacing = 12.dp, + centerPatternDotsVertically = true, + modifier = Modifier.padding(top = 128.dp), + ) + + ActionArea( + viewModel = viewModel, + modifier = Modifier.padding(top = 48.dp), + ) + } + } + } +} + +@Composable +private fun FoldAware( + viewModel: BouncerViewModel, + aboveFold: @Composable BoxScope.() -> Unit, + belowFold: @Composable BoxScope.() -> Unit, + modifier: Modifier = Modifier, ) { val foldPosture: FoldPosture by foldPosture() val isSplitAroundTheFoldRequired by viewModel.isFoldSplitRequired.collectAsState() - val isSplitAroundTheFold = - foldPosture == FoldPosture.Tabletop && !outputOnly && isSplitAroundTheFoldRequired + val isSplitAroundTheFold = foldPosture == FoldPosture.Tabletop && isSplitAroundTheFoldRequired val currentSceneKey = if (isSplitAroundTheFold) SceneKeys.SplitSceneKey else SceneKeys.ContiguousSceneKey @@ -160,115 +486,57 @@ private fun StandardLayout( modifier = modifier, ) { scene(SceneKeys.ContiguousSceneKey) { - FoldSplittable( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = layout, - outputOnly = outputOnly, + FoldableScene( + aboveFold = aboveFold, + belowFold = belowFold, isSplit = false, ) } scene(SceneKeys.SplitSceneKey) { - FoldSplittable( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = layout, - outputOnly = outputOnly, + FoldableScene( + aboveFold = aboveFold, + belowFold = belowFold, isSplit = true, ) } } } -/** - * Renders the "standard" layout of the bouncer, where the bouncer is rendered on its own (no user - * switcher UI) and laid out vertically, centered horizontally. - * - * If [isSplit] is `true`, the top and bottom parts of the bouncer are split such that they don't - * render across the location of the fold hardware when the device is fully or part-way unfolded - * with the fold hinge in a horizontal position. - * - * If [outputOnly] is `true`, only the "output" part of the UI is shown (where the entered PIN - * "shapes" appear), if `false`, the entire UI is shown, including the area where the user can enter - * their PIN or pattern. - */ @Composable -private fun SceneScope.FoldSplittable( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - layout: BouncerSceneLayout, - outputOnly: Boolean, +private fun SceneScope.FoldableScene( + aboveFold: @Composable BoxScope.() -> Unit, + belowFold: @Composable BoxScope.() -> Unit, isSplit: Boolean, modifier: Modifier = Modifier, ) { - val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() - val dialogMessage: String? by viewModel.throttlingDialogMessage.collectAsState() - var dialog: Dialog? by remember { mutableStateOf(null) } - val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() val splitRatio = LocalContext.current.resources.getFloat( R.dimen.motion_layout_half_fold_bouncer_height_ratio ) - Column(modifier = modifier.padding(horizontal = 32.dp)) { + Column( + modifier = modifier.fillMaxHeight(), + ) { // Content above the fold, when split on a foldable device in a "table top" posture: Box( modifier = Modifier.element(SceneElements.AboveFold) - .fillMaxWidth() .then( if (isSplit) { Modifier.weight(splitRatio) - } else if (outputOnly) { - Modifier.fillMaxHeight() } else { Modifier } ), ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxWidth().padding(top = layout.topPadding), - ) { - Crossfade( - targetState = message, - label = "Bouncer message", - animationSpec = if (message.isUpdateAnimated) tween() else snap(), - ) { message -> - Text( - text = message.text, - color = MaterialTheme.colorScheme.onSurface, - style = MaterialTheme.typography.bodyLarge, - ) - } - - if (!outputOnly) { - Spacer(Modifier.height(layout.spacingBetweenMessageAndEnteredInput)) - - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.OUTPUT_ONLY, - layout = layout, - ) - } - } - - if (outputOnly) { - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.OUTPUT_ONLY, - layout = layout, - modifier = Modifier.align(Alignment.Center), - ) - } + aboveFold() } // Content below the fold, when split on a foldable device in a "table top" posture: Box( modifier = Modifier.element(SceneElements.BelowFold) - .fillMaxWidth() .weight( if (isSplit) { 1 - splitRatio @@ -277,73 +545,40 @@ private fun SceneScope.FoldSplittable( } ), ) { - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = Modifier.fillMaxSize() - ) { - if (!outputOnly) { - Box(Modifier.weight(1f)) { - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.INPUT_ONLY, - layout = layout, - modifier = Modifier.align(Alignment.BottomCenter), - ) - } - } - - Spacer(Modifier.height(48.dp)) - - val actionButtonModifier = Modifier.height(56.dp) - - actionButton.let { actionButtonViewModel -> - if (actionButtonViewModel != null) { - BouncerActionButton( - viewModel = actionButtonViewModel, - modifier = actionButtonModifier, - ) - } else { - Spacer(modifier = actionButtonModifier) - } - } - - Spacer(Modifier.height(layout.bottomPadding)) - } + belowFold() } + } +} - if (dialogMessage != null) { - if (dialog == null) { - dialog = - dialogFactory().apply { - setMessage(dialogMessage) - setButton( - DialogInterface.BUTTON_NEUTRAL, - context.getString(R.string.ok), - ) { _, _ -> - viewModel.onThrottlingDialogDismissed() - } - setCancelable(false) - setCanceledOnTouchOutside(false) - show() - } - } - } else { - dialog?.dismiss() - dialog = null - } +@Composable +private fun StatusMessage( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val message: BouncerViewModel.MessageViewModel by viewModel.message.collectAsState() + + Crossfade( + targetState = message, + label = "Bouncer message", + animationSpec = if (message.isUpdateAnimated) tween() else snap(), + modifier = modifier, + ) { + Text( + text = it.text, + color = MaterialTheme.colorScheme.onSurface, + style = MaterialTheme.typography.bodyLarge, + ) } } /** - * Renders the user input area, where the user interacts with the UI to enter their credentials. + * Renders the user output area, where the user sees what they entered. * - * For example, this can be the pattern input area, the password text box, or pin pad. + * For example, this can be the PIN shapes or password text field. */ @Composable -private fun UserInputArea( +private fun OutputArea( viewModel: BouncerViewModel, - visibility: UserInputAreaVisibility, - layout: BouncerSceneLayout, modifier: Modifier = Modifier, ) { val authMethodViewModel: AuthMethodBouncerViewModel? by @@ -351,66 +586,115 @@ private fun UserInputArea( when (val nonNullViewModel = authMethodViewModel) { is PinBouncerViewModel -> - when (visibility) { - UserInputAreaVisibility.OUTPUT_ONLY -> - PinInputDisplay( - viewModel = nonNullViewModel, - modifier = modifier, - ) - UserInputAreaVisibility.INPUT_ONLY -> - PinPad( - viewModel = nonNullViewModel, - layout = layout, - modifier = modifier, - ) - } + PinInputDisplay( + viewModel = nonNullViewModel, + modifier = modifier, + ) is PasswordBouncerViewModel -> - if (visibility == UserInputAreaVisibility.INPUT_ONLY) { - PasswordBouncer( - viewModel = nonNullViewModel, - modifier = modifier, - ) - } - is PatternBouncerViewModel -> - if (visibility == UserInputAreaVisibility.INPUT_ONLY) { - PatternBouncer( - viewModel = nonNullViewModel, - layout = layout, - modifier = modifier.aspectRatio(1f, matchHeightConstraintsFirst = false), - ) - } + PasswordBouncer( + viewModel = nonNullViewModel, + modifier = modifier, + ) else -> Unit } } /** - * Renders the action button on the bouncer, which triggers either Return to Call or Emergency Call. + * Renders the user input area, where the user enters their credentials. + * + * For example, this can be the pattern input area or the PIN pad. */ -@OptIn(ExperimentalFoundationApi::class) @Composable -private fun BouncerActionButton( - viewModel: BouncerActionButtonModel, +private fun InputArea( + viewModel: BouncerViewModel, + pinButtonRowVerticalSpacing: Dp, + centerPatternDotsVertically: Boolean, modifier: Modifier = Modifier, ) { - Button( - onClick = viewModel.onClick, - modifier = - modifier.thenIf(viewModel.onLongClick != null) { - Modifier.combinedClickable( - onClick = viewModel.onClick, - onLongClick = viewModel.onLongClick, + val authMethodViewModel: AuthMethodBouncerViewModel? by + viewModel.authMethodViewModel.collectAsState() + + when (val nonNullViewModel = authMethodViewModel) { + is PinBouncerViewModel -> { + PinPad( + viewModel = nonNullViewModel, + verticalSpacing = pinButtonRowVerticalSpacing, + modifier = modifier, + ) + } + is PatternBouncerViewModel -> { + PatternBouncer( + viewModel = nonNullViewModel, + centerDotsVertically = centerPatternDotsVertically, + modifier = modifier, + ) + } + else -> Unit + } +} + +@Composable +private fun ActionArea( + viewModel: BouncerViewModel, + modifier: Modifier = Modifier, +) { + val actionButton: BouncerActionButtonModel? by viewModel.actionButton.collectAsState() + + actionButton?.let { actionButtonViewModel -> + Box( + modifier = modifier, + ) { + Button( + onClick = actionButtonViewModel.onClick, + modifier = + Modifier.height(56.dp).thenIf(actionButtonViewModel.onLongClick != null) { + Modifier.combinedClickable( + onClick = actionButtonViewModel.onClick, + onLongClick = actionButtonViewModel.onLongClick, + ) + }, + colors = + ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.tertiaryContainer, + contentColor = MaterialTheme.colorScheme.onTertiaryContainer, + ), + ) { + Text( + text = actionButtonViewModel.label, + style = MaterialTheme.typography.bodyMedium, ) - }, - colors = - ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.tertiaryContainer, - contentColor = MaterialTheme.colorScheme.onTertiaryContainer, - ), - ) { - Text( - text = viewModel.label, - style = MaterialTheme.typography.bodyMedium, - ) + } + } + } +} + +@Composable +private fun Dialog( + viewModel: BouncerViewModel, + dialogFactory: BouncerDialogFactory, +) { + val dialogMessage: String? by viewModel.dialogMessage.collectAsState() + var dialog: Dialog? by remember { mutableStateOf(null) } + + if (dialogMessage != null) { + if (dialog == null) { + dialog = + dialogFactory().apply { + setMessage(dialogMessage) + setButton( + DialogInterface.BUTTON_NEUTRAL, + context.getString(R.string.ok), + ) { _, _ -> + viewModel.onDialogDismissed() + } + setCancelable(false) + setCanceledOnTouchOutside(false) + show() + } + } + } else { + dialog?.dismiss() + dialog = null } } @@ -420,6 +704,14 @@ private fun UserSwitcher( viewModel: BouncerViewModel, modifier: Modifier = Modifier, ) { + if (!viewModel.isUserSwitcherVisible) { + // Take up the same space as the user switcher normally would, but with nothing inside it. + Box( + modifier = modifier, + ) + return + } + val selectedUserImage by viewModel.selectedUserImage.collectAsState(null) val dropdownItems by viewModel.userSwitcherDropdown.collectAsState(emptyList()) @@ -539,195 +831,10 @@ private fun UserSwitcherDropdownMenu( } } -/** - * Renders the bouncer UI in split mode, with half on one side and half on the other side, swappable - * by double-tapping on the side. - */ -@Composable -private fun SplitLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - modifier: Modifier = Modifier, -) { - SwappableLayout( - startContent = { startContentModifier -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.SPLIT, - outputOnly = true, - modifier = startContentModifier, - ) - }, - endContent = { endContentModifier -> - UserInputArea( - viewModel = viewModel, - visibility = UserInputAreaVisibility.INPUT_ONLY, - layout = BouncerSceneLayout.SPLIT, - modifier = endContentModifier, - ) - }, - layout = BouncerSceneLayout.SPLIT, - modifier = modifier, - ) -} - -/** - * Arranges the given two contents side-by-side, supporting a double tap anywhere on the background - * to flip their positions. - */ -@Composable -private fun SwappableLayout( - startContent: @Composable (Modifier) -> Unit, - endContent: @Composable (Modifier) -> Unit, - layout: BouncerSceneLayout, - modifier: Modifier = Modifier, -) { - val layoutDirection = LocalLayoutDirection.current - val isLeftToRight = layoutDirection == LayoutDirection.Ltr - val (isSwapped, setSwapped) = rememberSaveable(isLeftToRight) { mutableStateOf(!isLeftToRight) } - - Row( - modifier = - modifier.pointerInput(Unit) { - detectTapGestures( - onDoubleTap = { offset -> - // Depending on where the user double tapped, switch the elements such that - // the endContent is closer to the side that was double tapped. - setSwapped(offset.x < size.width / 2) - } - ) - }, - ) { - val animatedOffset by - animateFloatAsState( - targetValue = - if (!isSwapped) { - // When startContent is first, both elements have their natural placement so - // they are not offset in any way. - 0f - } else if (isLeftToRight) { - // Since startContent is not first, the elements have to be swapped - // horizontally. In the case of LTR locales, this means pushing startContent - // to the right, hence the positive number. - 1f - } else { - // Since startContent is not first, the elements have to be swapped - // horizontally. In the case of RTL locales, this means pushing startContent - // to the left, hence the negative number. - -1f - }, - label = "offset", - ) - - startContent( - Modifier.fillMaxHeight().weight(1f).graphicsLayer { - translationX = size.width * animatedOffset - alpha = animatedAlpha(animatedOffset) - } - ) - - Box( - modifier = - Modifier.fillMaxHeight().weight(1f).graphicsLayer { - // A negative sign is used to make sure this is offset in the direction that's - // opposite of the direction that the user switcher is pushed in. - translationX = -size.width * animatedOffset - alpha = animatedAlpha(animatedOffset) - } - ) { - endContent(Modifier.align(layout.swappableEndContentAlignment).widthIn(max = 400.dp)) - } - } -} - -/** - * Arranges the bouncer contents and user switcher contents side-by-side, supporting a double tap - * anywhere on the background to flip their positions. - * - * In situations when [isUserSwitcherVisible] is `false`, one of two things may happen: either the - * UI for the bouncer will be shown on its own, taking up one side, with the other side just being - * empty space or, if that kind of "stand-alone side-by-side" isn't supported, the standard - * rendering of the bouncer will be used instead of the side-by-side layout. - */ -@Composable -private fun SideBySideLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - isUserSwitcherVisible: Boolean, - modifier: Modifier = Modifier, -) { - SwappableLayout( - startContent = { startContentModifier -> - if (isUserSwitcherVisible) { - UserSwitcher( - viewModel = viewModel, - modifier = startContentModifier, - ) - } else { - Box( - modifier = startContentModifier, - ) - } - }, - endContent = { endContentModifier -> - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.SIDE_BY_SIDE, - modifier = endContentModifier, - ) - }, - layout = BouncerSceneLayout.SIDE_BY_SIDE, - modifier = modifier, - ) -} - -/** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */ -@Composable -private fun StackedLayout( - viewModel: BouncerViewModel, - dialogFactory: BouncerDialogFactory, - isUserSwitcherVisible: Boolean, - modifier: Modifier = Modifier, -) { - Column( - modifier = modifier, - ) { - if (isUserSwitcherVisible) { - UserSwitcher( - viewModel = viewModel, - modifier = Modifier.fillMaxWidth().weight(1f), - ) - } - - StandardLayout( - viewModel = viewModel, - dialogFactory = dialogFactory, - layout = BouncerSceneLayout.STACKED, - modifier = Modifier.fillMaxWidth().weight(1f), - ) - } -} - interface BouncerDialogFactory { operator fun invoke(): AlertDialog } -/** Enumerates all supported user-input area visibilities. */ -private enum class UserInputAreaVisibility { - /** - * Only the area where the user enters the input is shown; the area where the input is reflected - * back to the user is not shown. - */ - INPUT_ONLY, - /** - * Only the area where the input is reflected back to the user is shown; the area where the - * input is entered by the user is not shown. - */ - OUTPUT_ONLY, -} - /** * Calculates an alpha for the user switcher and bouncer such that it's at `1` when the offset of * the two reaches a stopping point but `0` in the middle of the transition. @@ -774,48 +881,3 @@ private object SceneElements { private val SceneTransitions = transitions { from(SceneKeys.ContiguousSceneKey, to = SceneKeys.SplitSceneKey) { spec = tween() } } - -/** Whether a more compact size should be used for various spacing dimensions. */ -internal val BouncerSceneLayout.isUseCompactSize: Boolean - get() = - when (this) { - BouncerSceneLayout.SIDE_BY_SIDE -> true - BouncerSceneLayout.SPLIT -> true - else -> false - } - -/** Amount of space to place between the message and the entered input UI elements, in dips. */ -private val BouncerSceneLayout.spacingBetweenMessageAndEnteredInput: Dp - get() = - when { - this == BouncerSceneLayout.STACKED -> 24.dp - isUseCompactSize -> 96.dp - else -> 128.dp - } - -/** Amount of space to place above the topmost UI element, in dips. */ -private val BouncerSceneLayout.topPadding: Dp - get() = - if (this == BouncerSceneLayout.SPLIT) { - 40.dp - } else { - 92.dp - } - -/** Amount of space to place below the bottommost UI element, in dips. */ -private val BouncerSceneLayout.bottomPadding: Dp - get() = - if (this == BouncerSceneLayout.SPLIT) { - 40.dp - } else { - 48.dp - } - -/** The in-a-box alignment for the content on the "end" side of a swappable layout. */ -private val BouncerSceneLayout.swappableEndContentAlignment: Alignment - get() = - if (this == BouncerSceneLayout.SPLIT) { - Alignment.Center - } else { - Alignment.BottomCenter - } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt index 08b7559dcf97..1c3d93c3b7e9 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerSceneLayout.kt @@ -26,8 +26,8 @@ import com.android.systemui.bouncer.ui.helper.calculateLayoutInternal /** * Returns the [BouncerSceneLayout] that should be used by the bouncer scene. If - * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.SIDE_BY_SIDE] is replaced by - * [BouncerSceneLayout.STANDARD]. + * [isSideBySideSupported] is `false`, then [BouncerSceneLayout.BESIDE_USER_SWITCHER] is replaced by + * [BouncerSceneLayout.STANDARD_BOUNCER]. */ @Composable fun calculateLayout( diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt index 279995959da2..09608115c456 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PasswordBouncer.kt @@ -17,7 +17,6 @@ package com.android.systemui.bouncer.ui.composable import android.view.ViewTreeObserver -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material3.LocalTextStyle @@ -31,7 +30,6 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.produceState import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.focus.FocusRequester @@ -81,42 +79,38 @@ internal fun PasswordBouncer( } } - Column( - horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier, - ) { - val color = MaterialTheme.colorScheme.onSurfaceVariant - val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() } + val color = MaterialTheme.colorScheme.onSurfaceVariant + val lineWidthPx = with(LocalDensity.current) { 2.dp.toPx() } - TextField( - value = password, - onValueChange = viewModel::onPasswordInputChanged, - enabled = isInputEnabled, - visualTransformation = PasswordVisualTransformation(), - singleLine = true, - textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), - keyboardOptions = - KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done, - ), - keyboardActions = - KeyboardActions( - onDone = { viewModel.onAuthenticateKeyPressed() }, - ), - modifier = - Modifier.focusRequester(focusRequester) - .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } - .drawBehind { - drawLine( - color = color, - start = Offset(x = 0f, y = size.height - lineWidthPx), - end = Offset(size.width, y = size.height - lineWidthPx), - strokeWidth = lineWidthPx, - ) - }, - ) - } + TextField( + value = password, + onValueChange = viewModel::onPasswordInputChanged, + enabled = isInputEnabled, + visualTransformation = PasswordVisualTransformation(), + singleLine = true, + textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center), + keyboardOptions = + KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done, + ), + keyboardActions = + KeyboardActions( + onDone = { viewModel.onAuthenticateKeyPressed() }, + ), + modifier = + modifier + .focusRequester(focusRequester) + .onFocusChanged { viewModel.onTextFieldFocusChanged(it.isFocused) } + .drawBehind { + drawLine( + color = color, + start = Offset(x = 0f, y = size.height - lineWidthPx), + end = Offset(size.width, y = size.height - lineWidthPx), + strokeWidth = lineWidthPx, + ) + }, + ) } /** Returns a [State] with `true` when the IME/keyboard is visible and `false` when it's not. */ diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt index a4b195508b3d..0a5f5d281f83 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt @@ -24,6 +24,8 @@ import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.detectDragGestures +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect @@ -48,7 +50,6 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.modifiers.thenIf import com.android.internal.R -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.PatternDotViewModel import kotlin.math.min @@ -61,11 +62,14 @@ import kotlinx.coroutines.launch * UI for the input part of a pattern-requiring version of the bouncer. * * The user can press, hold, and drag their pointer to select dots along a grid of dots. + * + * If [centerDotsVertically] is `true`, the dots should be centered along the axis of interest; if + * `false`, the dots will be pushed towards the end/bottom of the axis. */ @Composable internal fun PatternBouncer( viewModel: PatternBouncerViewModel, - layout: BouncerSceneLayout, + centerDotsVertically: Boolean, modifier: Modifier = Modifier, ) { DisposableEffect(Unit) { @@ -197,6 +201,14 @@ internal fun PatternBouncer( Canvas( modifier + // Because the width also includes spacing to the left and right of the leftmost and + // rightmost dots in the grid and because UX mocks specify the width without that + // spacing, the actual width needs to be defined slightly bigger than the UX mock width. + .width((262 * colCount / 2).dp) + // Because the height also includes spacing above and below the topmost and bottommost + // dots in the grid and because UX mocks specify the height without that spacing, the + // actual height needs to be defined slightly bigger than the UX mock height. + .height((262 * rowCount / 2).dp) // Need to clip to bounds to make sure that the lines don't follow the input pointer // when it leaves the bounds of the dot grid. .clipToBounds() @@ -260,7 +272,7 @@ internal fun PatternBouncer( availableSize = containerSize.height, spacingPerDot = spacing, dotCount = rowCount, - isCentered = layout.isCenteredVertically, + isCentered = centerDotsVertically, ) offset = Offset(horizontalOffset, verticalOffset) scale = (colCount * spacing) / containerSize.width @@ -423,10 +435,6 @@ private fun offset( } } -/** Whether the UI should be centered vertically. */ -private val BouncerSceneLayout.isCenteredVertically: Boolean - get() = this == BouncerSceneLayout.SPLIT - private const val DOT_DIAMETER_DP = 16 private const val SELECTED_DOT_DIAMETER_DP = 24 private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83 diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt index 8f5d9f4a1790..f505b9067140 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PinBouncer.kt @@ -52,7 +52,6 @@ import androidx.compose.ui.unit.dp import com.android.compose.animation.Easings import com.android.compose.grid.VerticalGrid import com.android.compose.modifiers.thenIf -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout import com.android.systemui.bouncer.ui.viewmodel.ActionButtonAppearance import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel import com.android.systemui.common.shared.model.ContentDescription @@ -70,7 +69,7 @@ import kotlinx.coroutines.launch @Composable fun PinPad( viewModel: PinBouncerViewModel, - layout: BouncerSceneLayout, + verticalSpacing: Dp, modifier: Modifier = Modifier, ) { DisposableEffect(Unit) { @@ -96,8 +95,8 @@ fun PinPad( VerticalGrid( columns = columns, - verticalSpacing = layout.verticalSpacing, - horizontalSpacing = calculateHorizontalSpacingBetweenColumns(layout.gridWidth), + verticalSpacing = verticalSpacing, + horizontalSpacing = calculateHorizontalSpacingBetweenColumns(gridWidth = 300.dp), modifier = modifier, ) { repeat(9) { index -> @@ -355,14 +354,6 @@ private fun calculateHorizontalSpacingBetweenColumns( return (gridWidth - (pinButtonMaxSize * columns)) / (columns - 1) } -/** The width of the grid of PIN pad buttons, in dips. */ -private val BouncerSceneLayout.gridWidth: Dp - get() = if (isUseCompactSize) 292.dp else 300.dp - -/** The spacing between rows of PIN pad buttons, in dips. */ -private val BouncerSceneLayout.verticalSpacing: Dp - get() = if (isUseCompactSize) 8.dp else 12.dp - /** Number of columns in the PIN pad grid. */ private const val columns = 3 /** Maximum size (width and height) of each PIN pad button. */ 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/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt index 5d8eaf7f3d15..58052cd60f39 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/GestureHandler.kt @@ -2,10 +2,9 @@ package com.android.compose.animation.scene import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection -import androidx.compose.ui.unit.IntSize interface DraggableHandler { - fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int = 1) + fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int = 1) fun onDelta(pixels: Float) fun onDragStopped(velocity: Float) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt index a0fba8076517..38738782c889 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt @@ -66,8 +66,8 @@ internal fun Modifier.multiPointerDraggable( orientation: Orientation, enabled: Boolean, startDragImmediately: Boolean, - onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit, - onDragDelta: (Float) -> Unit, + onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit, + onDragDelta: (delta: Float) -> Unit, onDragStopped: (velocity: Float) -> Unit, ): Modifier = this.then( @@ -86,7 +86,7 @@ private data class MultiPointerDraggableElement( private val enabled: Boolean, private val startDragImmediately: Boolean, private val onDragStarted: - (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit, + (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit, private val onDragDelta: (Float) -> Unit, private val onDragStopped: (velocity: Float) -> Unit, ) : ModifierNodeElement<MultiPointerDraggableNode>() { @@ -114,7 +114,7 @@ private class MultiPointerDraggableNode( orientation: Orientation, enabled: Boolean, var startDragImmediately: Boolean, - var onDragStarted: (layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) -> Unit, + var onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit, var onDragDelta: (Float) -> Unit, var onDragStopped: (velocity: Float) -> Unit, ) : PointerInputModifierNode, DelegatingNode(), CompositionLocalConsumerModifierNode { @@ -153,9 +153,9 @@ private class MultiPointerDraggableNode( return } - val onDragStart: (Offset, Int) -> Unit = { startedPosition, pointersDown -> + val onDragStart: (Offset, Float, Int) -> Unit = { startedPosition, overSlop, pointersDown -> velocityTracker.resetTracking() - onDragStarted(size, startedPosition, pointersDown) + onDragStarted(startedPosition, overSlop, pointersDown) } val onDragCancel: () -> Unit = { onDragStopped(/* velocity= */ 0f) } @@ -203,7 +203,7 @@ private class MultiPointerDraggableNode( private suspend fun PointerInputScope.detectDragGestures( orientation: Orientation, startDragImmediately: () -> Boolean, - onDragStart: (startedPosition: Offset, pointersDown: Int) -> Unit, + onDragStart: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> Unit, onDragEnd: () -> Unit, onDragCancel: () -> Unit, onDrag: (change: PointerInputChange, dragAmount: Float) -> Unit, @@ -241,7 +241,7 @@ private suspend fun PointerInputScope.detectDragGestures( } } - onDragStart(drag.position, pressed.size) + onDragStart(drag.position, overSlop, pressed.size) onDrag(drag, overSlop) val successful = diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt index 2986504b7dbc..ded6cc155b0b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt @@ -44,22 +44,22 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) { * Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the * gesture begins at the edge of the scrollable component (so that a scroll in that direction * can no longer be consumed). If the gesture is partially consumed by the scrollable component, - * there will be NO overscroll effect between scenes. + * there will be NO preview of the next scene. * * In addition, during scene transitions, scroll events are consumed by the * [SceneTransitionLayout] instead of the scrollable component. */ - EdgeNoOverscroll(canStartOnPostFling = false), + EdgeNoPreview(canStartOnPostFling = false), /** * Overscroll will only be used by the [SceneTransitionLayout] to move to the next scene if the * gesture begins at the edge of the scrollable component. If the gesture is partially consumed - * by the scrollable component, there will be an overscroll effect between scenes. + * by the scrollable component, there will be a preview of the next scene. * * In addition, during scene transitions, scroll events are consumed by the * [SceneTransitionLayout] instead of the scrollable component. */ - EdgeWithOverscroll(canStartOnPostFling = true), + EdgeWithPreview(canStartOnPostFling = true), /** * Any overscroll will be used by the [SceneTransitionLayout] to move to the next scene. @@ -67,7 +67,7 @@ enum class NestedScrollBehavior(val canStartOnPostFling: Boolean) { * In addition, during scene transitions, scroll events are consumed by the * [SceneTransitionLayout] instead of the scrollable component. */ - Always(canStartOnPostFling = true), + EdgeAlways(canStartOnPostFling = true), } internal fun Modifier.nestedScrollToScene( diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt index b00c88612269..212c9eb65138 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:Suppress("NOTHING_TO_INLINE") + package com.android.compose.animation.scene import android.util.Log @@ -26,7 +28,6 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round import com.android.compose.nestedscroll.PriorityNestedScrollConnection @@ -48,14 +49,13 @@ internal class SceneGestureHandler( layoutImpl.state.transitionState = value } - /** - * The transition controlled by this gesture handler. It will be set as the [transitionState] in - * the [SceneTransitionLayoutImpl] whenever this handler is driving the current transition. - * - * Note: the initialScene here does not matter, it's only used for initializing the transition - * and will be replaced when a drag event starts. - */ - internal val swipeTransition = SwipeTransition(initialScene = currentScene) + internal var swipeTransition: SwipeTransition = SwipeTransition(currentScene, currentScene, 1f) + private set + + private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) { + if (isDrivingTransition || force) transitionState = newTransition + swipeTransition = newTransition + } internal val currentScene: Scene get() = layoutImpl.scene(transitionState.currentScene) @@ -63,15 +63,6 @@ internal class SceneGestureHandler( internal val isDrivingTransition get() = transitionState == swipeTransition - internal var isAnimatingOffset - get() = swipeTransition.isAnimatingOffset - private set(value) { - swipeTransition.isAnimatingOffset = value - } - - internal val swipeTransitionToScene - get() = swipeTransition._toScene - /** * The velocity threshold at which the intent of the user is to swipe up or down. It is the same * as SwipeableV2Defaults.VelocityThreshold. @@ -86,11 +77,17 @@ internal class SceneGestureHandler( internal var gestureWithPriority: Any? = null - internal fun onDragStarted(pointersDown: Int, layoutSize: IntSize, startedPosition: Offset?) { + /** The [UserAction]s associated to the current swipe. */ + private var actionUpOrLeft: UserAction? = null + private var actionDownOrRight: UserAction? = null + private var actionUpOrLeftNoEdge: UserAction? = null + private var actionDownOrRightNoEdge: UserAction? = null + + internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) { if (isDrivingTransition) { // This [transition] was already driving the animation: simply take over it. // Stop animating and start from where the current offset. - swipeTransition.stopOffsetAnimation() + swipeTransition.cancelOffsetAnimation() return } @@ -106,37 +103,29 @@ internal class SceneGestureHandler( } val fromScene = currentScene + setCurrentActions(fromScene, startedPosition, pointersDown) - swipeTransition._currentScene = fromScene - swipeTransition._fromScene = fromScene - - // We don't know where we are transitioning to yet given that the drag just started, so set - // it to fromScene, which will effectively be treated the same as Idle(fromScene). - swipeTransition._toScene = fromScene + if (fromScene.upOrLeft() == null && fromScene.downOrRight() == null) { + return + } - swipeTransition.stopOffsetAnimation() - swipeTransition.dragOffset = 0f + val (targetScene, distance) = fromScene.findTargetSceneAndDistance(overSlop) - // Use the layout size in the swipe orientation for swipe distance. - // TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, - // we will also have to make sure that we correctly handle overscroll. - swipeTransition.absoluteDistance = - when (orientation) { - Orientation.Horizontal -> layoutSize.width - Orientation.Vertical -> layoutSize.height - }.toFloat() + updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true) + } + private fun setCurrentActions(fromScene: Scene, startedPosition: Offset?, pointersDown: Int) { val fromEdge = startedPosition?.let { position -> layoutImpl.edgeDetector.edge( - layoutSize, + fromScene.targetSize, position.round(), layoutImpl.density, orientation, ) } - swipeTransition.actionUpOrLeft = + val upOrLeft = Swipe( direction = when (orientation) { @@ -147,7 +136,7 @@ internal class SceneGestureHandler( fromEdge = fromEdge, ) - swipeTransition.actionDownOrRight = + val downOrRight = Swipe( direction = when (orientation) { @@ -159,108 +148,114 @@ internal class SceneGestureHandler( ) if (fromEdge == null) { - swipeTransition.actionUpOrLeftNoEdge = null - swipeTransition.actionDownOrRightNoEdge = null + actionUpOrLeft = null + actionDownOrRight = null + actionUpOrLeftNoEdge = upOrLeft + actionDownOrRightNoEdge = downOrRight } else { - swipeTransition.actionUpOrLeftNoEdge = - (swipeTransition.actionUpOrLeft as Swipe).copy(fromEdge = null) - swipeTransition.actionDownOrRightNoEdge = - (swipeTransition.actionDownOrRight as Swipe).copy(fromEdge = null) + actionUpOrLeft = upOrLeft + actionDownOrRight = downOrRight + actionUpOrLeftNoEdge = upOrLeft.copy(fromEdge = null) + actionDownOrRightNoEdge = downOrRight.copy(fromEdge = null) } + } - if (swipeTransition.absoluteDistance > 0f) { - transitionState = swipeTransition - } + /** + * Use the layout size in the swipe orientation for swipe distance. + * + * TODO(b/290184746): Also handle custom distances for transitions. With smaller distances, we + * will also have to make sure that we correctly handle overscroll. + */ + private fun Scene.getAbsoluteDistance(): Float { + return when (orientation) { + Orientation.Horizontal -> targetSize.width + Orientation.Vertical -> targetSize.height + }.toFloat() } internal fun onDrag(delta: Float) { - if (delta == 0f) return - + if (delta == 0f || !isDrivingTransition) return swipeTransition.dragOffset += delta - // First check transition.fromScene should be changed for the case where the user quickly - // swiped twice in a row to accelerate the transition and go from A => B then B => C really - // fast. - maybeHandleAcceleratedSwipe() - - val offset = swipeTransition.dragOffset - val fromScene = swipeTransition._fromScene + val (fromScene, acceleratedOffset) = + computeFromSceneConsideringAcceleratedSwipe(swipeTransition) + swipeTransition.dragOffset += acceleratedOffset // Compute the target scene depending on the current offset. - val target = fromScene.findTargetSceneAndDistance(offset) + val (targetScene, distance) = + fromScene.findTargetSceneAndDistance(swipeTransition.dragOffset) - if (swipeTransition._toScene.key != target.sceneKey) { - swipeTransition._toScene = layoutImpl.scenes.getValue(target.sceneKey) - } - - if (swipeTransition._distance != target.distance) { - swipeTransition._distance = target.distance + // TODO(b/290184746): support long scroll A => B => C? especially for non fullscreen scenes + if ( + fromScene.key != swipeTransition.fromScene || targetScene.key != swipeTransition.toScene + ) { + updateTransition( + SwipeTransition(fromScene, targetScene, distance).apply { + this.dragOffset = swipeTransition.dragOffset + } + ) } } /** * Change fromScene in the case where the user quickly swiped multiple times in the same * direction to accelerate the transition from A => B then B => C. + * + * @return the new fromScene and a dragOffset to be added in case the scene has changed + * + * TODO(b/290184746): the second drag needs to pass B to work. Add support for flinging twice + * before B has been reached */ - private fun maybeHandleAcceleratedSwipe() { + private inline fun computeFromSceneConsideringAcceleratedSwipe( + swipeTransition: SwipeTransition, + ): Pair<Scene, Float> { val toScene = swipeTransition._toScene val fromScene = swipeTransition._fromScene + val absoluteDistance = swipeTransition.distance.absoluteValue // If the swipe was not committed, don't do anything. if (fromScene == toScene || swipeTransition._currentScene != toScene) { - return + return Pair(fromScene, 0f) } // If the offset is past the distance then let's change fromScene so that the user can swipe // to the next screen or go back to the previous one. val offset = swipeTransition.dragOffset - val absoluteDistance = swipeTransition.absoluteDistance - if (offset <= -absoluteDistance && swipeTransition.upOrLeft(fromScene) == toScene.key) { - swipeTransition.dragOffset += absoluteDistance - swipeTransition._fromScene = toScene - } else if ( - offset >= absoluteDistance && swipeTransition.downOrRight(fromScene) == toScene.key - ) { - swipeTransition.dragOffset -= absoluteDistance - swipeTransition._fromScene = toScene + return if (offset <= -absoluteDistance && fromScene.upOrLeft() == toScene.key) { + Pair(toScene, absoluteDistance) + } else if (offset >= absoluteDistance && fromScene.downOrRight() == toScene.key) { + Pair(toScene, -absoluteDistance) + } else { + Pair(fromScene, 0f) } - - // Important note: toScene and distance will be updated right after this function is called, - // using fromScene and dragOffset. } - private class TargetScene( - val sceneKey: SceneKey, - val distance: Float, - ) - - private fun Scene.findTargetSceneAndDistance(directionOffset: Float): TargetScene { - val upOrLeft = swipeTransition.upOrLeft(this) - val downOrRight = swipeTransition.downOrRight(this) + // TODO(b/290184746): there are two bugs here: + // 1. if both upOrLeft and downOrRight become `null` during a transition this will crash + // 2. if one of them changes during a transition, the transition will jump cut to the new target + private inline fun Scene.findTargetSceneAndDistance( + directionOffset: Float + ): Pair<Scene, Float> { + val upOrLeft = upOrLeft() + val downOrRight = downOrRight() + val absoluteDistance = getAbsoluteDistance() // Compute the target scene depending on the current offset. - return when { - directionOffset < 0f && upOrLeft != null -> { - TargetScene( - sceneKey = upOrLeft, - distance = -swipeTransition.absoluteDistance, - ) - } - directionOffset > 0f && downOrRight != null -> { - TargetScene( - sceneKey = downOrRight, - distance = swipeTransition.absoluteDistance, - ) - } - else -> { - TargetScene( - sceneKey = key, - distance = 0f, - ) - } + return if ((directionOffset < 0f && upOrLeft != null) || downOrRight == null) { + Pair(layoutImpl.scene(upOrLeft!!), -absoluteDistance) + } else { + Pair(layoutImpl.scene(downOrRight), absoluteDistance) } } + private fun Scene.upOrLeft(): SceneKey? { + return userActions[actionUpOrLeft] ?: userActions[actionUpOrLeftNoEdge] + } + + private fun Scene.downOrRight(): SceneKey? { + return userActions[actionDownOrRight] ?: userActions[actionDownOrRightNoEdge] + } + internal fun onDragStopped(velocity: Float, canChangeScene: Boolean) { // The state was changed since the drag started; don't do anything. if (!isDrivingTransition) { @@ -291,11 +286,6 @@ internal class SceneGestureHandler( // velocity and offset of the transition, then we launch the animation. val toScene = swipeTransition._toScene - if (fromScene == toScene) { - // We were not animating. - transitionState = TransitionState.Idle(fromScene.key) - return - } // Compute the destination scene (and therefore offset) to settle in. val offset = swipeTransition.dragOffset @@ -322,12 +312,14 @@ internal class SceneGestureHandler( if (startFromIdlePosition) { // If there is a next scene, we start the overscroll animation. - val target = fromScene.findTargetSceneAndDistance(velocity) - val isValidTarget = target.distance != 0f && target.sceneKey != fromScene.key + val (targetScene, distance) = fromScene.findTargetSceneAndDistance(velocity) + val isValidTarget = distance != 0f && targetScene.key != fromScene.key if (isValidTarget) { - swipeTransition._toScene = layoutImpl.scene(target.sceneKey) - swipeTransition._distance = target.distance - + updateTransition( + SwipeTransition(fromScene, targetScene, distance).apply { + _currentScene = swipeTransition._currentScene + } + ) animateTo(targetScene = fromScene, targetOffset = 0f) } else { // We will not animate @@ -382,10 +374,10 @@ internal class SceneGestureHandler( ) { swipeTransition.startOffsetAnimation { coroutineScope.launch { - if (!isAnimatingOffset) { + if (!swipeTransition.isAnimatingOffset) { swipeTransition.offsetAnimatable.snapTo(swipeTransition.dragOffset) } - isAnimatingOffset = true + swipeTransition.isAnimatingOffset = true swipeTransition.offsetAnimatable.animateTo( targetOffset, @@ -397,7 +389,7 @@ internal class SceneGestureHandler( initialVelocity = initialVelocity, ) - isAnimatingOffset = false + swipeTransition.finishOffsetAnimation() // Now that the animation is done, the state should be idle. Note that if the state // was changed since this animation started, some external code changed it and we @@ -410,29 +402,26 @@ internal class SceneGestureHandler( } } - internal class SwipeTransition(initialScene: Scene) : TransitionState.Transition { - var _currentScene by mutableStateOf(initialScene) + internal class SwipeTransition( + val _fromScene: Scene, + val _toScene: Scene, + /** + * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is + * above or to the left of [toScene]. + */ + val distance: Float + ) : TransitionState.Transition { + var _currentScene by mutableStateOf(_fromScene) override val currentScene: SceneKey get() = _currentScene.key - var _fromScene by mutableStateOf(initialScene) - override val fromScene: SceneKey - get() = _fromScene.key + override val fromScene: SceneKey = _fromScene.key - var _toScene by mutableStateOf(initialScene) - override val toScene: SceneKey - get() = _toScene.key + override val toScene: SceneKey = _toScene.key override val progress: Float get() { val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset - if (distance == 0f) { - // This can happen only if fromScene == toScene. - error( - "Transition.progress should be called only when Transition.fromScene != " + - "Transition.toScene" - ) - } return offset / distance } @@ -459,46 +448,22 @@ internal class SceneGestureHandler( /** Ends any previous [offsetAnimationJob] and runs the new [job]. */ fun startOffsetAnimation(job: () -> Job) { - stopOffsetAnimation() + cancelOffsetAnimation() offsetAnimationJob = job() } - /** Stops any ongoing offset animation. */ - fun stopOffsetAnimation() { + /** Cancel any ongoing offset animation. */ + fun cancelOffsetAnimation() { offsetAnimationJob?.cancel() + finishOffsetAnimation() + } + fun finishOffsetAnimation() { if (isAnimatingOffset) { isAnimatingOffset = false dragOffset = offsetAnimatable.value } } - - /** The absolute distance between [fromScene] and [toScene]. */ - var absoluteDistance = 0f - - /** - * The signed distance between [fromScene] and [toScene]. It is negative if [fromScene] is - * above or to the left of [toScene]. - */ - var _distance by mutableFloatStateOf(0f) - val distance: Float - get() = _distance - - /** The [UserAction]s associated to this swipe. */ - var actionUpOrLeft: UserAction = Back - var actionDownOrRight: UserAction = Back - var actionUpOrLeftNoEdge: UserAction? = null - var actionDownOrRightNoEdge: UserAction? = null - - fun upOrLeft(scene: Scene): SceneKey? { - return scene.userActions[actionUpOrLeft] - ?: actionUpOrLeftNoEdge?.let { scene.userActions[it] } - } - - fun downOrRight(scene: Scene): SceneKey? { - return scene.userActions[actionDownOrRight] - ?: actionDownOrRightNoEdge?.let { scene.userActions[it] } - } } companion object { @@ -509,9 +474,9 @@ internal class SceneGestureHandler( private class SceneDraggableHandler( private val gestureHandler: SceneGestureHandler, ) : DraggableHandler { - override fun onDragStarted(layoutSize: IntSize, startedPosition: Offset, pointersDown: Int) { + override fun onDragStarted(startedPosition: Offset, overSlop: Float, pointersDown: Int) { gestureHandler.gestureWithPriority = this - gestureHandler.onDragStarted(pointersDown, layoutSize, startedPosition) + gestureHandler.onDragStarted(pointersDown, startedPosition, overSlop) } override fun onDelta(pixels: Float) { @@ -589,7 +554,7 @@ internal class SceneNestedScrollHandler( // The progress value can go beyond this range in the case of overscroll. val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f) if (shouldSnapToIdle) { - gestureHandler.swipeTransition.stopOffsetAnimation() + gestureHandler.swipeTransition.cancelOffsetAnimation() gestureHandler.transitionState = TransitionState.Idle(gestureHandler.swipeTransition.currentScene) } @@ -612,15 +577,15 @@ internal class SceneNestedScrollHandler( canChangeScene = false // unused: added for consistency false } - NestedScrollBehavior.EdgeNoOverscroll -> { + NestedScrollBehavior.EdgeNoPreview -> { canChangeScene = isZeroOffset isZeroOffset && hasNextScene(offsetAvailable) } - NestedScrollBehavior.EdgeWithOverscroll -> { + NestedScrollBehavior.EdgeWithPreview -> { canChangeScene = isZeroOffset hasNextScene(offsetAvailable) } - NestedScrollBehavior.Always -> { + NestedScrollBehavior.EdgeAlways -> { canChangeScene = true hasNextScene(offsetAvailable) } @@ -639,12 +604,12 @@ internal class SceneNestedScrollHandler( behavior.canStartOnPostFling && hasNextScene(velocityAvailable) }, canContinueScroll = { true }, - onStart = { + onStart = { offsetAvailable -> gestureHandler.gestureWithPriority = this gestureHandler.onDragStarted( pointersDown = 1, - layoutSize = gestureHandler.currentScene.targetSize, startedPosition = null, + overSlop = offsetAvailable, ) }, onScroll = { offsetAvailable -> diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt index 07add77eccd4..afa184b15901 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt @@ -132,8 +132,8 @@ interface SceneScope { */ fun Modifier.nestedScrollToScene( orientation: Orientation, - startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoOverscroll, - endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoOverscroll, + startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, + endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview, ): Modifier /** diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt index 2c78dee56bbc..116a66673d0a 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt @@ -46,7 +46,7 @@ internal fun Modifier.swipeToScene(gestureHandler: SceneGestureHandler): Modifie // user can't swipe in the other direction. startDragImmediately = gestureHandler.isDrivingTransition && - gestureHandler.isAnimatingOffset && + gestureHandler.swipeTransition.isAnimatingOffset && !canOppositeSwipe, onDragStarted = gestureHandler.draggable::onDragStarted, onDragDelta = gestureHandler.draggable::onDelta, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt index a5fd1bfb72e6..c49a2b8bbe32 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt @@ -38,7 +38,7 @@ class PriorityNestedScrollConnection( private val canStartPostScroll: (offsetAvailable: Offset, offsetBeforeStart: Offset) -> Boolean, private val canStartPostFling: (velocityAvailable: Velocity) -> Boolean, private val canContinueScroll: () -> Boolean, - private val onStart: () -> Unit, + private val onStart: (offsetAvailable: Offset) -> Unit, private val onScroll: (offsetAvailable: Offset) -> Offset, private val onStop: (velocityAvailable: Velocity) -> Velocity, ) : NestedScrollConnection { @@ -131,7 +131,7 @@ class PriorityNestedScrollConnection( // Note: onStop will be called if we cannot continue to scroll (step 3a), or the finger is // lifted (step 3b), or this object has been destroyed (step 3c). - onStart() + onStart(available) return onScroll(available) } @@ -156,7 +156,7 @@ fun PriorityNestedScrollConnection( canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean, canStartPostFling: (velocityAvailable: Float) -> Boolean, canContinueScroll: () -> Boolean, - onStart: () -> Unit, + onStart: (offsetAvailable: Float) -> Unit, onScroll: (offsetAvailable: Float) -> Float, onStop: (velocityAvailable: Float) -> Float, ) = @@ -172,7 +172,7 @@ fun PriorityNestedScrollConnection( canStartPostFling(velocityAvailable.toFloat()) }, canContinueScroll = canContinueScroll, - onStart = onStart, + onStart = { offsetAvailable -> onStart(offsetAvailable.toFloat()) }, onScroll = { offsetAvailable: Offset -> onScroll(offsetAvailable.toFloat()).toOffset() }, diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt index 49ef31b16d73..aa942e039856 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt @@ -29,10 +29,10 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.Velocity import androidx.test.ext.junit.runners.AndroidJUnit4 -import com.android.compose.animation.scene.NestedScrollBehavior.Always import com.android.compose.animation.scene.NestedScrollBehavior.DuringTransitionBetweenScenes -import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoOverscroll -import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithOverscroll +import com.android.compose.animation.scene.NestedScrollBehavior.EdgeAlways +import com.android.compose.animation.scene.NestedScrollBehavior.EdgeNoPreview +import com.android.compose.animation.scene.NestedScrollBehavior.EdgeWithPreview import com.android.compose.animation.scene.TestScenes.SceneA import com.android.compose.animation.scene.TestScenes.SceneB import com.android.compose.animation.scene.TestScenes.SceneC @@ -65,30 +65,53 @@ class SceneGestureHandlerTest { ) { Text("SceneA") } - scene(SceneB) { Text("SceneB") } - scene(SceneC) { Text("SceneC") } + scene( + key = SceneB, + userActions = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA), + ) { + Text("SceneB") + } + scene( + key = SceneC, + userActions = + mapOf( + Swipe.Up to SceneB, + Swipe(SwipeDirection.Up, fromEdge = Edge.Bottom) to SceneA + ), + ) { + Text("SceneC") + } } val transitionInterceptionThreshold = 0.05f + private val layoutImpl = + SceneTransitionLayoutImpl( + onChangeScene = { internalCurrentScene = it }, + builder = scenesBuilder, + transitions = EmptyTestTransitions, + state = layoutState, + density = Density(1f), + edgeDetector = DefaultEdgeDetector, + transitionInterceptionThreshold = transitionInterceptionThreshold, + coroutineScope = coroutineScope, + ) + .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) } + val sceneGestureHandler = SceneGestureHandler( - layoutImpl = - SceneTransitionLayoutImpl( - onChangeScene = { internalCurrentScene = it }, - builder = scenesBuilder, - transitions = EmptyTestTransitions, - state = layoutState, - density = Density(1f), - edgeDetector = DefaultEdgeDetector, - transitionInterceptionThreshold = transitionInterceptionThreshold, - coroutineScope = coroutineScope, - ) - .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }, + layoutImpl = layoutImpl, orientation = Orientation.Vertical, coroutineScope = coroutineScope, ) + val horizontalSceneGestureHandler = + SceneGestureHandler( + layoutImpl = layoutImpl, + orientation = Orientation.Horizontal, + coroutineScope = coroutineScope, + ) + val draggable = sceneGestureHandler.draggable fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) = @@ -101,11 +124,17 @@ class SceneGestureHandlerTest { val velocityThreshold = sceneGestureHandler.velocityThreshold - // 10% of the screen - val deltaInPixels10 = SCREEN_SIZE * 0.1f + fun down(fractionOfScreen: Float) = + if (fractionOfScreen < 0f) error("use up()") else SCREEN_SIZE * fractionOfScreen + + fun up(fractionOfScreen: Float) = + if (fractionOfScreen < 0f) error("use down()") else -down(fractionOfScreen) + + // Float tolerance for comparisons + val tolerance = 0.00001f // Offset y: 10% of the screen - val offsetY10 = Offset(x = 0f, y = deltaInPixels10) + val offsetY10 = Offset(x = 0f, y = down(0.1f)) val transitionState: TransitionState get() = layoutState.transitionState @@ -121,12 +150,39 @@ class SceneGestureHandlerTest { coroutineScope.testScheduler.runCurrent() } - fun assertScene(currentScene: SceneKey, isIdle: Boolean) { - val idleMsg = if (isIdle) "MUST" else "MUST NOT" - assertWithMessage("transitionState $idleMsg be Idle") - .that(transitionState is Idle) - .isEqualTo(isIdle) - assertThat(transitionState.currentScene).isEqualTo(currentScene) + fun assertIdle(currentScene: SceneKey) { + assertWithMessage("transitionState must be Idle").that(transitionState is Idle).isTrue() + assertWithMessage("currentScene does not match") + .that(transitionState.currentScene) + .isEqualTo(currentScene) + } + + fun assertTransition( + currentScene: SceneKey? = null, + fromScene: SceneKey? = null, + toScene: SceneKey? = null, + progress: Float? = null, + ) { + assertWithMessage("transitionState must be Transition") + .that(transitionState is Transition) + .isTrue() + if (currentScene != null) + assertWithMessage("currentScene does not match") + .that(transitionState.currentScene) + .isEqualTo(currentScene) + if (fromScene != null) + assertWithMessage("fromScene does not match") + .that((transitionState as? Transition)?.fromScene) + .isEqualTo(fromScene) + if (toScene != null) + assertWithMessage("toScene does not match") + .that((transitionState as? Transition)?.toScene) + .isEqualTo(toScene) + if (progress != null) + assertWithMessage("progress does not match") + .that((transitionState as? Transition)?.progress) + .isWithin(tolerance) + .of(progress) } } @@ -135,111 +191,262 @@ class SceneGestureHandlerTest { runMonotonicClockTest { TestGestureScope(coroutineScope = this).block() } } - private fun DraggableHandler.onDragStarted() = - onDragStarted(layoutSize = LAYOUT_SIZE, startedPosition = Offset.Zero) + private fun DraggableHandler.onDragStarted( + overSlop: Float = 0f, + startedPosition: Offset = Offset.Zero, + ) { + onDragStarted(startedPosition, overSlop) + // MultiPointerDraggable will always call onDelta with the initial overSlop right after + onDelta(overSlop) + } - @Test - fun testPreconditions() = runGestureTest { assertScene(currentScene = SceneA, isIdle = true) } + @Test fun testPreconditions() = runGestureTest { assertIdle(currentScene = SceneA) } @Test fun onDragStarted_shouldStartATransition() = runGestureTest { draggable.onDragStarted() - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) } @Test fun afterSceneTransitionIsStarted_interceptDragEvents() = runGestureTest { draggable.onDragStarted() - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) - draggable.onDelta(pixels = deltaInPixels10) + draggable.onDelta(pixels = down(0.1f)) assertThat(progress).isEqualTo(0.1f) - draggable.onDelta(pixels = deltaInPixels10) + draggable.onDelta(pixels = down(0.1f)) assertThat(progress).isEqualTo(0.2f) } @Test fun onDragStoppedAfterDrag_velocityLowerThanThreshold_remainSameScene() = runGestureTest { draggable.onDragStarted() - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) - draggable.onDelta(pixels = deltaInPixels10) - assertScene(currentScene = SceneA, isIdle = false) + draggable.onDelta(pixels = down(0.1f)) + assertTransition(currentScene = SceneA) draggable.onDragStopped( velocity = velocityThreshold - 0.01f, ) - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } @Test fun onDragStoppedAfterDrag_velocityAtLeastThreshold_goToNextScene() = runGestureTest { draggable.onDragStarted() - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) - draggable.onDelta(pixels = deltaInPixels10) - assertScene(currentScene = SceneA, isIdle = false) + draggable.onDelta(pixels = down(0.1f)) + assertTransition(currentScene = SceneA) - draggable.onDragStopped( - velocity = velocityThreshold, - ) - assertScene(currentScene = SceneC, isIdle = false) + draggable.onDragStopped(velocity = velocityThreshold) + + assertTransition(currentScene = SceneC) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneC, isIdle = true) + assertIdle(currentScene = SceneC) } @Test - fun onDragStoppedAfterStarted_returnImmediatelyToIdle() = runGestureTest { + fun onDragStoppedAfterStarted_returnToIdle() = runGestureTest { draggable.onDragStarted() - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) draggable.onDragStopped(velocity = 0f) - assertScene(currentScene = SceneA, isIdle = true) + advanceUntilIdle() + assertIdle(currentScene = SceneA) + } + + @Test + fun onDragReversedDirection_changeToScene() = runGestureTest { + // Drag A -> B with progress 0.6 + draggable.onDragStarted() + draggable.onDelta(up(0.6f)) + assertTransition( + currentScene = SceneA, + fromScene = SceneA, + toScene = SceneB, + progress = 0.6f + ) + + // Reverse direction such that A -> C now with 0.4 + draggable.onDelta(down(1f)) + assertTransition( + currentScene = SceneA, + fromScene = SceneA, + toScene = SceneC, + progress = 0.4f + ) + + // After the drag stopped scene C should be committed + draggable.onDragStopped(velocity = velocityThreshold) + assertTransition(currentScene = SceneC, fromScene = SceneA, toScene = SceneC) + + // wait for the stop animation + advanceUntilIdle() + assertIdle(currentScene = SceneC) + } + + @Test + fun onDragStartedWithoutActionsInBothDirections_stayIdle() = runGestureTest { + horizontalSceneGestureHandler.draggable.onDragStarted(up(0.3f)) + assertIdle(currentScene = SceneA) + horizontalSceneGestureHandler.draggable.onDragStarted(down(0.3f)) + assertIdle(currentScene = SceneA) + } + + @Test + fun onDragIntoNoAction_startTransitionToOppositeDirection() = runGestureTest { + navigateToSceneC() + + // We are on SceneC which has no action in Down direction + draggable.onDragStarted(down(0.1f)) + assertTransition( + currentScene = SceneC, + fromScene = SceneC, + toScene = SceneB, + progress = -0.1f + ) + + // Reverse drag direction, it will consume the previous drag + draggable.onDelta(up(0.1f)) + assertTransition( + currentScene = SceneC, + fromScene = SceneC, + toScene = SceneB, + progress = 0.0f + ) + + // Continue reverse drag direction, it should record progress to Scene B + draggable.onDelta(up(0.1f)) + assertTransition( + currentScene = SceneC, + fromScene = SceneC, + toScene = SceneB, + progress = 0.1f + ) + } + + @Test + fun onDragFromEdge_startTransitionToEdgeAction() = runGestureTest { + navigateToSceneC() + + // Start dragging from the bottom + draggable.onDragStarted(up(0.1f), Offset(SCREEN_SIZE * 0.5f, SCREEN_SIZE)) + assertTransition( + currentScene = SceneC, + fromScene = SceneC, + toScene = SceneA, + progress = 0.1f + ) + } + + @Test + fun onDragToExactlyZero_toSceneIsSet() = runGestureTest { + draggable.onDragStarted(down(0.3f)) + assertTransition( + currentScene = SceneA, + fromScene = SceneA, + toScene = SceneC, + progress = 0.3f + ) + draggable.onDelta(up(0.3f)) + assertTransition( + currentScene = SceneA, + fromScene = SceneA, + toScene = SceneC, + progress = 0.0f + ) + } + + private fun TestGestureScope.navigateToSceneC() { + assertIdle(currentScene = SceneA) + draggable.onDragStarted(down(1f)) + draggable.onDragStopped(0f) + advanceUntilIdle() + assertIdle(currentScene = SceneC) + } + + @Test + fun onAccelaratedScroll_scrollToThirdScene() = runGestureTest { + // Drag A -> B with progress 0.2 + draggable.onDragStarted() + draggable.onDelta(up(0.2f)) + assertTransition( + currentScene = SceneA, + fromScene = SceneA, + toScene = SceneB, + progress = 0.2f + ) + + // Start animation A -> B with progress 0.2 -> 1.0 + draggable.onDragStopped(velocity = -velocityThreshold) + assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB) + + // While at A -> B do a 100% screen drag (progress 1.2). This should go past B and change + // the transition to B -> C with progress 0.2 + draggable.onDragStarted() + draggable.onDelta(up(1f)) + assertTransition( + currentScene = SceneB, + fromScene = SceneB, + toScene = SceneC, + progress = 0.2f + ) + + // After the drag stopped scene C should be committed + draggable.onDragStopped(velocity = -velocityThreshold) + assertTransition(currentScene = SceneC, fromScene = SceneB, toScene = SceneC) + + // wait for the stop animation + advanceUntilIdle() + assertIdle(currentScene = SceneC) } @Test fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest { draggable.onDragStarted() - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) - draggable.onDelta(pixels = deltaInPixels10) - assertScene(currentScene = SceneA, isIdle = false) + draggable.onDelta(pixels = down(0.1f)) + assertTransition(currentScene = SceneA) draggable.onDragStopped( velocity = velocityThreshold, ) // The stop animation is not started yet - assertThat(sceneGestureHandler.isAnimatingOffset).isFalse() + assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse() runCurrent() - assertThat(sceneGestureHandler.isAnimatingOffset).isTrue() + assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isTrue() assertThat(sceneGestureHandler.isDrivingTransition).isTrue() - assertScene(currentScene = SceneC, isIdle = false) + assertTransition(currentScene = SceneC) // Start a new gesture while the offset is animating draggable.onDragStarted() - assertThat(sceneGestureHandler.isAnimatingOffset).isFalse() + assertThat(sceneGestureHandler.swipeTransition.isAnimatingOffset).isFalse() } @Test fun onInitialPreScroll_EdgeWithOverscroll_doNotChangeState() = runGestureTest { - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) nestedScroll.onPreScroll(available = offsetY10, source = NestedScrollSource.Drag) - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } @Test fun onPostScrollWithNothingAvailable_EdgeWithOverscroll_doNotChangeState() = runGestureTest { - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) val consumed = nestedScroll.onPostScroll( consumed = Offset.Zero, @@ -247,13 +454,13 @@ class SceneGestureHandlerTest { source = NestedScrollSource.Drag ) - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) assertThat(consumed).isEqualTo(Offset.Zero) } @Test fun onPostScrollWithSomethingAvailable_startSceneTransition() = runGestureTest { - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) val consumed = nestedScroll.onPostScroll( consumed = Offset.Zero, @@ -261,7 +468,7 @@ class SceneGestureHandlerTest { source = NestedScrollSource.Drag ) - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) assertThat(progress).isEqualTo(0.1f) assertThat(consumed).isEqualTo(offsetY10) } @@ -282,9 +489,9 @@ class SceneGestureHandlerTest { @Test fun afterSceneTransitionIsStarted_interceptPreScrollEvents() = runGestureTest { - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) nestedScroll.scroll(available = offsetY10) - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) assertThat(progress).isEqualTo(0.1f) @@ -303,14 +510,14 @@ class SceneGestureHandlerTest { nestedScroll.scroll(available = offsetY10) assertThat(progress).isEqualTo(0.3f) - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) } private suspend fun TestGestureScope.preScrollAfterSceneTransition( firstScroll: Float, secondScroll: Float ) { - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) // start scene transition nestedScroll.scroll(available = Offset(0f, SCREEN_SIZE * firstScroll)) @@ -321,9 +528,6 @@ class SceneGestureHandlerTest { nestedScroll.onPreScroll(Offset(0f, SCREEN_SIZE * secondScroll), NestedScrollSource.Drag) } - // Float tolerance for comparisons - private val tolerance = 0.00001f - @Test fun scrollAndFling_scrollLessThanInterceptable_goToIdleOnCurrentScene() = runGestureTest { val first = transitionInterceptionThreshold - tolerance @@ -331,7 +535,7 @@ class SceneGestureHandlerTest { preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) - assertScene(SceneA, isIdle = true) + assertIdle(SceneA) } @Test @@ -341,7 +545,7 @@ class SceneGestureHandlerTest { preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) - assertThat(progress).isWithin(tolerance).of(first + second) + assertTransition(progress = first + second) } @Test @@ -351,7 +555,7 @@ class SceneGestureHandlerTest { preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) - assertThat(progress).isWithin(tolerance).of(first + second) + assertTransition(progress = first + second) } @Test @@ -361,21 +565,21 @@ class SceneGestureHandlerTest { preScrollAfterSceneTransition(firstScroll = first, secondScroll = second) - assertScene(SceneC, isIdle = true) + assertIdle(SceneC) } @Test fun onPreFling_velocityLowerThanThreshold_remainSameScene() = runGestureTest { - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) nestedScroll.scroll(available = offsetY10) - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) nestedScroll.onPreFling(available = Velocity.Zero) - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } private suspend fun TestGestureScope.flingAfterScroll( @@ -384,7 +588,7 @@ class SceneGestureHandlerTest { ) { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = use) nestedScroll.scroll(available = offsetY10) - assertScene(currentScene = SceneA, isIdle = idleAfterScroll) + if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA) nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) } @@ -393,40 +597,40 @@ class SceneGestureHandlerTest { fun flingAfterScroll_DuringTransitionBetweenScenes_doNothing() = runGestureTest { flingAfterScroll(use = DuringTransitionBetweenScenes, idleAfterScroll = true) - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } @Test fun flingAfterScroll_EdgeNoOverscroll_goToNextScene() = runGestureTest { - flingAfterScroll(use = EdgeNoOverscroll, idleAfterScroll = false) + flingAfterScroll(use = EdgeNoPreview, idleAfterScroll = false) - assertScene(currentScene = SceneC, isIdle = false) + assertTransition(currentScene = SceneC) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneC, isIdle = true) + assertIdle(currentScene = SceneC) } @Test fun flingAfterScroll_EdgeWithOverscroll_goToNextScene() = runGestureTest { - flingAfterScroll(use = EdgeWithOverscroll, idleAfterScroll = false) + flingAfterScroll(use = EdgeWithPreview, idleAfterScroll = false) - assertScene(currentScene = SceneC, isIdle = false) + assertTransition(currentScene = SceneC) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneC, isIdle = true) + assertIdle(currentScene = SceneC) } @Test fun flingAfterScroll_Always_goToNextScene() = runGestureTest { - flingAfterScroll(use = Always, idleAfterScroll = false) + flingAfterScroll(use = EdgeAlways, idleAfterScroll = false) - assertScene(currentScene = SceneC, isIdle = false) + assertTransition(currentScene = SceneC) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneC, isIdle = true) + assertIdle(currentScene = SceneC) } /** we started the scroll in the scene, then fling with the velocityThreshold */ @@ -440,7 +644,7 @@ class SceneGestureHandlerTest { // scroll offsetY10 is all available for parents nestedScroll.scroll(available = offsetY10) - assertScene(currentScene = SceneA, isIdle = idleAfterScroll) + if (idleAfterScroll) assertIdle(SceneA) else assertTransition(SceneA) nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) } @@ -449,64 +653,64 @@ class SceneGestureHandlerTest { fun flingAfterScrollStartedInScene_DuringTransitionBetweenScenes_doNothing() = runGestureTest { flingAfterScrollStartedInScene(use = DuringTransitionBetweenScenes, idleAfterScroll = true) - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } @Test fun flingAfterScrollStartedInScene_EdgeNoOverscroll_doNothing() = runGestureTest { - flingAfterScrollStartedInScene(use = EdgeNoOverscroll, idleAfterScroll = true) + flingAfterScrollStartedInScene(use = EdgeNoPreview, idleAfterScroll = true) - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } @Test fun flingAfterScrollStartedInScene_EdgeWithOverscroll_doOverscrollAnimation() = runGestureTest { - flingAfterScrollStartedInScene(use = EdgeWithOverscroll, idleAfterScroll = false) + flingAfterScrollStartedInScene(use = EdgeWithPreview, idleAfterScroll = false) - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } @Test fun flingAfterScrollStartedInScene_Always_goToNextScene() = runGestureTest { - flingAfterScrollStartedInScene(use = Always, idleAfterScroll = false) + flingAfterScrollStartedInScene(use = EdgeAlways, idleAfterScroll = false) - assertScene(currentScene = SceneC, isIdle = false) + assertTransition(currentScene = SceneC) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneC, isIdle = true) + assertIdle(currentScene = SceneC) } @Test fun beforeDraggableStart_drag_shouldBeIgnored() = runGestureTest { - draggable.onDelta(deltaInPixels10) - assertScene(currentScene = SceneA, isIdle = true) + draggable.onDelta(down(0.1f)) + assertIdle(currentScene = SceneA) } @Test fun beforeDraggableStart_stop_shouldBeIgnored() = runGestureTest { draggable.onDragStopped(velocityThreshold) - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } @Test fun beforeNestedScrollStart_stop_shouldBeIgnored() = runGestureTest { - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithOverscroll) + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeWithPreview) nestedScroll.onPreFling(Velocity(0f, velocityThreshold)) - assertScene(currentScene = SceneA, isIdle = true) + assertIdle(currentScene = SceneA) } @Test fun startNestedScrollWhileDragging() = runGestureTest { - val nestedScroll = nestedScrollConnection(nestedScrollBehavior = Always) + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) draggable.onDragStarted() - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) - draggable.onDelta(deltaInPixels10) + draggable.onDelta(down(0.1f)) assertThat(progress).isEqualTo(0.1f) // now we can intercept the scroll events @@ -515,7 +719,7 @@ class SceneGestureHandlerTest { // this should be ignored, we are scrolling now! draggable.onDragStopped(velocityThreshold) - assertScene(currentScene = SceneA, isIdle = false) + assertTransition(currentScene = SceneA) nestedScroll.scroll(available = offsetY10) assertThat(progress).isEqualTo(0.3f) @@ -524,10 +728,10 @@ class SceneGestureHandlerTest { assertThat(progress).isEqualTo(0.4f) nestedScroll.onPreFling(available = Velocity(0f, velocityThreshold)) - assertScene(currentScene = SceneC, isIdle = false) + assertTransition(currentScene = SceneC) // wait for the stop animation advanceUntilIdle() - assertScene(currentScene = SceneC, isIdle = true) + assertIdle(currentScene = SceneC) } } 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 d968c1bb54bb..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,14 +21,12 @@ 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 -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceTimeBy import kotlinx.coroutines.test.runCurrent @@ -78,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 @@ -131,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 @@ -187,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) @@ -203,7 +203,8 @@ class AuthenticationInteractorTest : SysuiTestCase() { ) ) .isEqualTo(AuthenticationResult.SKIPPED) - assertThat(throttling).isNull() + assertThat(lockout).isNull() + assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0) } @Test @@ -264,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) @@ -272,7 +273,7 @@ class AuthenticationInteractorTest : SysuiTestCase() { utils.authenticationRepository.apply { setAuthenticationMethod(AuthenticationMethodModel.Pin) setAutoConfirmFeatureEnabled(true) - setThrottleDuration(42) + setLockoutDuration(42) } val authResult = @@ -315,69 +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, - remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS, + 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, - remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS, + 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: - val throttleTimeoutSec = - FakeAuthenticationRepository.THROTTLE_DURATION_MS.milliseconds.inWholeSeconds - .toInt() - repeat(throttleTimeoutSec - 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, - remainingMs = - ((throttleTimeoutSec - (time + 1)).seconds.inWholeMilliseconds) - .toInt(), + 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/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt index cbb772f49c93..0ab596c82d6f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt @@ -21,6 +21,8 @@ import android.testing.TestableLooper import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityModel +import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF +import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepositoryImpl @@ -56,10 +58,12 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.mock +import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -539,4 +543,77 @@ class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : .onDozeAmountChanged(eq(0f), eq(0f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE)) job.cancel() } + + @Test + fun cancelledLockscreenToAod_dozeAmountNotUpdatedToZero() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForLockscreenAodTransitions(this) + // WHEN lockscreen to aod transition is cancelled + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.LOCKSCREEN, + to = KeyguardState.AOD, + value = 1f, + transitionState = TransitionState.CANCELED + ) + ) + runCurrent() + + // THEN doze amount is NOT updated to zero + verify(mView, never()).onDozeAmountChanged(eq(0f), eq(0f), anyInt()) + job.cancel() + } + + @Test + fun dreamingToAod_dozeAmountChanged() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForDreamingToAodTransitions(this) + // WHEN dreaming to aod transition in progress + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.DREAMING, + to = KeyguardState.AOD, + value = .3f, + transitionState = TransitionState.RUNNING + ) + ) + runCurrent() + + // THEN doze amount is updated to + verify(mView).onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATE_APPEAR_ON_SCREEN_OFF)) + job.cancel() + } + + @Test + fun alternateBouncerToAod_dozeAmountChanged() = + testScope.runTest { + // GIVEN view is attached + mController.onViewAttached() + Mockito.reset(mView) + + val job = mController.listenForAlternateBouncerToAodTransitions(this) + // WHEN alternate bouncer to aod transition in progress + transitionRepository.sendTransitionStep( + TransitionStep( + from = KeyguardState.ALTERNATE_BOUNCER, + to = KeyguardState.AOD, + value = .3f, + transitionState = TransitionState.RUNNING + ) + ) + runCurrent() + + // THEN doze amount is updated to + verify(mView) + .onDozeAmountChanged(eq(.3f), eq(.3f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)) + job.cancel() + } } 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 04f6cd30fc32..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,15 +21,14 @@ 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 import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat -import kotlin.math.ceil import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceTimeBy @@ -247,47 +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, - remainingMs = FakeAuthenticationRepository.THROTTLE_DURATION_MS, + 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?.remainingMs?.let { remainingMs -> - val seconds = ceil(remainingMs / 1000f).toInt() + lockout?.remainingSeconds?.let { seconds -> repeat(seconds) { time -> advanceTimeBy(1000) val remainingTimeSec = seconds - time - 1 @@ -297,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 75d6a007b4aa..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 @@ -26,6 +26,7 @@ import com.android.systemui.flags.Flags import com.android.systemui.scene.SceneTestUtils import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.flatMapLatest @@ -134,17 +135,19 @@ 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?.remainingMs?.let { remainingMs -> advanceTimeBy(remainingMs.toLong()) } + lockout?.remainingSeconds?.let { remainingSeconds -> + advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds) + } assertThat(message?.isUpdateAnimated).isTrue() } @@ -157,35 +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?.remainingMs?.let { milliseconds -> advanceTimeBy(milliseconds.toLong()) } + 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 64f294600be2..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 remainingTimeMs = 30_000 - authenticationRepository.setThrottleDuration(remainingTimeMs) - authenticationRepository.throttling.value = - AuthenticationThrottlingModel( + val remainingTimeSeconds = 30 + authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000) + authenticationRepository.lockout.value = + AuthenticationLockoutModel( failedAttemptCount = failedAttemptCount, - remainingMs = remainingTimeMs, + 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/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt index 97ac8c62d69d..d3049d9080f3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt @@ -6,6 +6,7 @@ import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.scene.SceneTestUtils import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController @@ -38,6 +39,7 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { private val testUtils = SceneTestUtils(this) private val testScope = testUtils.testScope private val userRepository = FakeUserRepository() + private val keyguardRepository = FakeKeyguardRepository() private lateinit var underTest: DeviceEntryRepository @@ -55,6 +57,7 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { lockPatternUtils = lockPatternUtils, keyguardBypassController = keyguardBypassController, keyguardStateController = keyguardStateController, + keyguardRepository = keyguardRepository, ) testScope.runCurrent() } 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..34f703bc0ca7 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, @@ -339,6 +344,31 @@ class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() { } @Test + fun quickAffordanceAlwaysVisible_notVisible_restrictedByPolicyManager() = + testScope.runTest { + whenever(devicePolicyManager.getKeyguardDisabledFeatures(null, userTracker.userId)) + .thenReturn(DevicePolicyManager.KEYGUARD_DISABLE_SHORTCUTS_ALL) + + repository.setKeyguardShowing(false) + repository.setIsDozing(true) + homeControls.setState( + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = ICON, + activationState = ActivationState.Active, + ) + ) + + val collectedValue by + collectLastValue( + underTest.quickAffordanceAlwaysVisible( + KeyguardQuickAffordancePosition.BOTTOM_START + ) + ) + + assertThat(collectedValue).isInstanceOf(KeyguardQuickAffordanceModel.Hidden::class.java) + } + + @Test fun quickAffordanceAlwaysVisible_evenWhenLockScreenNotShowingAndDozing() = testScope.runTest { repository.setKeyguardShowing(false) 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/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt index 8c896a6a1709..1e2784a622b1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt @@ -25,11 +25,10 @@ import android.service.quicksettings.Tile import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE +import com.android.systemui.Flags.FLAG_QS_NEW_TILES import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.dump.nano.SystemUIProtoDump -import com.android.systemui.flags.FakeFeatureFlags -import com.android.systemui.flags.Flags import com.android.systemui.plugins.qs.QSTile import com.android.systemui.plugins.qs.QSTile.BooleanState import com.android.systemui.qs.FakeQSFactory @@ -81,8 +80,7 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { private val tileFactory = FakeQSFactory(::tileCreator) private val customTileAddedRepository: CustomTileAddedRepository = FakeCustomTileAddedRepository() - private val featureFlags = FakeFeatureFlags() - private val pipelineFlags = QSPipelineFlagsRepository(featureFlags) + private val pipelineFlags = QSPipelineFlagsRepository() private val tileLifecycleManagerFactory = TLMFactory() @Mock private lateinit var customTileStatePersister: CustomTileStatePersister @@ -100,14 +98,12 @@ class CurrentTilesInteractorImplTest : SysuiTestCase() { private lateinit var underTest: CurrentTilesInteractorImpl - @OptIn(ExperimentalCoroutinesApi::class) @Before fun setup() { MockitoAnnotations.initMocks(this) mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE) - // TODO(b/299909337): Add test checking the new factory is used when the flag is on - featureFlags.set(Flags.QS_PIPELINE_NEW_TILES, true) + mSetFlagsRule.enableFlags(FLAG_QS_NEW_TILES) userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1)) 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/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt new file mode 100644 index 000000000000..4b9625107745 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegateTest.kt @@ -0,0 +1,101 @@ +/* + * 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.impl.saver.domain + +import android.content.SharedPreferences +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.internal.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.leaks.FakeDataSaverController +import kotlin.coroutines.EmptyCoroutineContext +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.mock +import org.mockito.Mockito.verify + +/** Test [DataSaverDialogDelegate]. */ +@SmallTest +@RunWith(AndroidJUnit4::class) +class DataSaverDialogDelegateTest : SysuiTestCase() { + + private val dataSaverController = FakeDataSaverController(LeakCheck()) + + private lateinit var sysuiDialogFactory: SystemUIDialog.Factory + private lateinit var sysuiDialog: SystemUIDialog + private lateinit var dataSaverDialogDelegate: DataSaverDialogDelegate + + @Before + fun setup() { + sysuiDialog = mock<SystemUIDialog>() + sysuiDialogFactory = mock<SystemUIDialog.Factory>() + + dataSaverDialogDelegate = + DataSaverDialogDelegate( + sysuiDialogFactory, + context, + EmptyCoroutineContext, + dataSaverController, + mock<SharedPreferences>() + ) + + whenever(sysuiDialogFactory.create(eq(dataSaverDialogDelegate), eq(context))) + .thenReturn(sysuiDialog) + } + @Test + fun delegateSetsDialogTitleCorrectly() { + val expectedResId = R.string.data_saver_enable_title + + dataSaverDialogDelegate.onCreate(sysuiDialog, null) + + verify(sysuiDialog).setTitle(eq(expectedResId)) + } + + @Test + fun delegateSetsDialogMessageCorrectly() { + val expectedResId = R.string.data_saver_description + + dataSaverDialogDelegate.onCreate(sysuiDialog, null) + + verify(sysuiDialog).setMessage(expectedResId) + } + + @Test + fun delegateSetsDialogPositiveButtonCorrectly() { + val expectedResId = R.string.data_saver_enable_button + + dataSaverDialogDelegate.onCreate(sysuiDialog, null) + + verify(sysuiDialog).setPositiveButton(eq(expectedResId), any()) + } + + @Test + fun delegateSetsDialogCancelButtonCorrectly() { + val expectedResId = R.string.cancel + + dataSaverDialogDelegate.onCreate(sysuiDialog, null) + + verify(sysuiDialog).setNeutralButton(eq(expectedResId), eq(null)) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt new file mode 100644 index 000000000000..d1824129590b --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt @@ -0,0 +1,95 @@ +/* + * 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.impl.saver.domain + +import android.widget.Switch +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.qs.tiles.impl.saver.qsDataSaverTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import org.junit.Test +import org.junit.runner.RunWith + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DataSaverTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig + + // Using lazy (versus =) to make sure we override the right context -- see b/311612168 + private val mapper by lazy { DataSaverTileMapper(context.orCreateTestableResources.resources) } + + @Test + fun activeStateMatchesEnabledModel() { + val inputModel = DataSaverTileModel(true) + + val outputState = mapper.map(dataSaverTileConfig, inputModel) + + val expectedState = + createDataSaverTileState( + QSTileState.ActivationState.ACTIVE, + R.drawable.qs_data_saver_icon_on + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun inactiveStateMatchesDisabledModel() { + val inputModel = DataSaverTileModel(false) + + val outputState = mapper.map(dataSaverTileConfig, inputModel) + + val expectedState = + createDataSaverTileState( + QSTileState.ActivationState.INACTIVE, + R.drawable.qs_data_saver_icon_off + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createDataSaverTileState( + activationState: QSTileState.ActivationState, + iconRes: Int + ): QSTileState { + val label = context.getString(R.string.data_saver) + val secondaryLabel = + if (activationState == QSTileState.ActivationState.ACTIVE) + context.resources.getStringArray(R.array.tile_states_saver)[2] + else if (activationState == QSTileState.ActivationState.INACTIVE) + context.resources.getStringArray(R.array.tile_states_saver)[1] + else context.resources.getStringArray(R.array.tile_states_saver)[0] + + return QSTileState( + { Icon.Resource(iconRes, null) }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + label, + null, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt new file mode 100644 index 000000000000..819bd03437f4 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractorTest.kt @@ -0,0 +1,75 @@ +/* + * 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.impl.saver.domain.interactor + +import android.os.UserHandle +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +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.saver.domain.model.DataSaverTileModel +import com.android.systemui.utils.leaks.FakeDataSaverController +import com.google.common.truth.Truth +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class DataSaverTileDataInteractorTest : SysuiTestCase() { + private val controller: FakeDataSaverController = FakeDataSaverController(LeakCheck()) + private val underTest: DataSaverTileDataInteractor = DataSaverTileDataInteractor(controller) + + @Test + fun isAvailableRegardlessOfController() = runTest { + controller.setDataSaverEnabled(false) + + runCurrent() + val availability by collectLastValue(underTest.availability(TEST_USER)) + + Truth.assertThat(availability).isTrue() + } + + @Test + fun dataMatchesController() = runTest { + controller.setDataSaverEnabled(false) + val flowValues: List<DataSaverTileModel> by + collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest))) + + runCurrent() + controller.setDataSaverEnabled(true) + runCurrent() + controller.setDataSaverEnabled(false) + runCurrent() + + Truth.assertThat(flowValues.size).isEqualTo(3) + Truth.assertThat(flowValues.map { it.isEnabled }) + .containsExactly(false, true, false) + .inOrder() + } + + private companion object { + val TEST_USER = UserHandle.of(1)!! + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..7091cb3b259c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractorTest.kt @@ -0,0 +1,183 @@ +/* + * 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.impl.saver.domain.interactor + +import android.content.Context +import android.content.SharedPreferences +import android.provider.Settings +import android.testing.LeakCheck +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.intentInputs +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.settings.UserFileManager +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.eq +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.android.systemui.utils.leaks.FakeDataSaverController +import com.google.common.truth.Truth.assertThat +import kotlin.coroutines.EmptyCoroutineContext +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class DataSaverTileUserActionInteractorTest : SysuiTestCase() { + private val qsTileIntentUserActionHandler = FakeQSTileIntentUserInputHandler() + private val dataSaverController = FakeDataSaverController(LeakCheck()) + + private lateinit var userFileManager: UserFileManager + private lateinit var sharedPreferences: SharedPreferences + private lateinit var dialogFactory: SystemUIDialog.Factory + private lateinit var underTest: DataSaverTileUserActionInteractor + + @Before + fun setup() { + userFileManager = mock<UserFileManager>() + sharedPreferences = mock<SharedPreferences>() + dialogFactory = mock<SystemUIDialog.Factory>() + whenever( + userFileManager.getSharedPreferences( + eq(DataSaverTileUserActionInteractor.PREFS), + eq(Context.MODE_PRIVATE), + eq(context.userId) + ) + ) + .thenReturn(sharedPreferences) + + underTest = + DataSaverTileUserActionInteractor( + context, + EmptyCoroutineContext, + EmptyCoroutineContext, + dataSaverController, + qsTileIntentUserActionHandler, + mock<DialogLaunchAnimator>(), + dialogFactory, + userFileManager, + ) + } + + /** Since the dialog was shown before, we expect the click to enable the controller. */ + @Test + fun handleClickToEnableDialogShownBefore() = runTest { + whenever( + sharedPreferences.getBoolean( + eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN), + any() + ) + ) + .thenReturn(true) + val stateBeforeClick = false + + underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick))) + + assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!stateBeforeClick) + } + + /** + * The first time the tile is clicked to turn on we expect (1) the enabled state to not change + * and (2) the dialog to be shown instead. + */ + @Test + fun handleClickToEnableDialogNotShownBefore() = runTest { + whenever( + sharedPreferences.getBoolean( + eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN), + any() + ) + ) + .thenReturn(false) + val mockDialog = mock<SystemUIDialog>() + whenever(dialogFactory.create(any(), any())).thenReturn(mockDialog) + val stateBeforeClick = false + + val input = QSTileInputTestKtx.click(DataSaverTileModel(stateBeforeClick)) + underTest.handleInput(input) + + assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(stateBeforeClick) + verify(mockDialog).show() + } + + /** Disabling should flip the state, even if the dialog was not shown before. */ + @Test + fun handleClickToDisableDialogNotShownBefore() = runTest { + whenever( + sharedPreferences.getBoolean( + eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN), + any() + ) + ) + .thenReturn(false) + val enabledBeforeClick = true + + underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick))) + + assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick) + } + + @Test + fun handleClickToDisableDialogShownBefore() = runTest { + whenever( + sharedPreferences.getBoolean( + eq(DataSaverTileUserActionInteractor.DIALOG_SHOWN), + any() + ) + ) + .thenReturn(true) + val enabledBeforeClick = true + + underTest.handleInput(QSTileInputTestKtx.click(DataSaverTileModel(enabledBeforeClick))) + + assertThat(dataSaverController.isDataSaverEnabled).isEqualTo(!enabledBeforeClick) + } + + @Test + fun handleLongClickWhenEnabled() = runTest { + val enabledState = true + + underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState))) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } + + @Test + fun handleLongClickWhenDisabled() = runTest { + val enabledState = false + + underTest.handleInput(QSTileInputTestKtx.longClick(DataSaverTileModel(enabledState))) + + assertThat(qsTileIntentUserActionHandler.handledInputs).hasSize(1) + val intentInput = qsTileIntentUserActionHandler.intentInputs.last() + val actualIntentAction = intentInput.intent.action + val expectedIntentAction = Settings.ACTION_DATA_SAVER_SETTINGS + assertThat(actualIntentAction).isEqualTo(expectedIntentAction) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index 3cb97e369a67..61d55f0ae667 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.scene.domain.startable import android.os.PowerManager +import android.platform.test.annotations.EnableFlags import android.view.Display import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest @@ -45,7 +46,6 @@ import kotlinx.coroutines.flow.map 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.Mockito.clearInvocations @@ -55,6 +55,7 @@ import org.mockito.Mockito.verify @SmallTest @RunWith(AndroidJUnit4::class) +@EnableFlags(AconfigFlags.FLAG_SCENE_CONTAINER) class SceneContainerStartableTest : SysuiTestCase() { private val utils = SceneTestUtils(this) @@ -93,11 +94,6 @@ class SceneContainerStartableTest : SysuiTestCase() { authenticationInteractor = authenticationInteractor, ) - @Before - fun setUp() { - mSetFlagsRule.enableFlags(AconfigFlags.FLAG_SCENE_CONTAINER) - } - @Test fun hydrateVisibility() = testScope.runTest { 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/flags/Flag.kt b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt index f1a40077f3f9..e27a328a847a 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt +++ b/packages/SystemUI/shared/src/com/android/systemui/flags/Flag.kt @@ -119,9 +119,8 @@ data class UnreleasedFlag constructor( data class ReleasedFlag constructor( override val name: String, override val namespace: String, - override val teamfood: Boolean = false, override val overridden: Boolean = false -) : BooleanFlag(name, namespace, true, teamfood, overridden) +) : BooleanFlag(name, namespace, true, teamfood = false, overridden) /** * A Flag that reads its default values from a resource overlay instead of code. @@ -132,8 +131,9 @@ data class ResourceBooleanFlag constructor( override val name: String, override val namespace: String, @BoolRes override val resourceId: Int, +) : ResourceFlag<Boolean> { override val teamfood: Boolean = false -) : ResourceFlag<Boolean> +} /** * A Flag that can reads its overrides from System Properties. @@ -147,7 +147,6 @@ data class SysPropBooleanFlag constructor( override val namespace: String, override val default: Boolean = false, ) : SysPropFlag<Boolean> { - // TODO(b/268520433): Teamfood not supported for sysprop flags yet. override val teamfood: Boolean = false } 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-debug/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt index aef83710c17b..f9fe67ac2076 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsFactory.kt @@ -42,7 +42,7 @@ object FlagsFactory { name: String, namespace: String = "systemui", ): ReleasedFlag { - val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false) + val flag = ReleasedFlag(name = name, namespace = namespace) checkForDupesAndAdd(flag) return flag } @@ -57,7 +57,6 @@ object FlagsFactory { name = name, namespace = namespace, resourceId = resourceId, - teamfood = false, ) checkForDupesAndAdd(flag) return flag diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt index f4b429659d8a..aedf0ce21c24 100644 --- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt +++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsFactory.kt @@ -42,7 +42,7 @@ object FlagsFactory { name: String, namespace: String = "systemui", ): ReleasedFlag { - val flag = ReleasedFlag(name = name, namespace = namespace, teamfood = false) + val flag = ReleasedFlag(name = name, namespace = namespace) flagMap[name] = flag return flag } @@ -57,7 +57,6 @@ object FlagsFactory { name = name, namespace = namespace, resourceId = resourceId, - teamfood = false, ) flagMap[name] = flag return flag 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/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java index 37bd9b287ebd..9c61a8a3cd8b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java @@ -1277,6 +1277,17 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab private final FaceAuthenticationListener mFaceAuthenticationListener = new FaceAuthenticationListener() { + public void onAuthenticatedChanged(boolean isAuthenticated) { + if (!isAuthenticated) { + for (int i = 0; i < mCallbacks.size(); i++) { + KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); + if (cb != null) { + cb.onFacesCleared(); + } + } + } + } + @Override public void onAuthEnrollmentStateChanged(boolean enrolled) { notifyAboutEnrollmentChange(TYPE_FACE); @@ -1961,7 +1972,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab protected void handleStartedGoingToSleep(int arg1) { Assert.isMainThread(); - clearBiometricRecognized(); + clearFingerprintRecognized(); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { @@ -3010,7 +3021,7 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab void handleUserSwitching(int userId, Runnable resultCallback) { mLogger.logUserSwitching(userId, "from UserTracker"); Assert.isMainThread(); - clearBiometricRecognized(); + clearFingerprintRecognized(); boolean trustUsuallyManaged = mTrustManager.isTrustUsuallyManaged(userId); mLogger.logTrustUsuallyManagedUpdated(userId, mUserTrustIsUsuallyManaged.get(userId), trustUsuallyManaged, "userSwitching"); @@ -3560,25 +3571,30 @@ public class KeyguardUpdateMonitor implements TrustManager.TrustListener, Dumpab return mServiceStates.get(subId); } - public void clearBiometricRecognized() { - clearBiometricRecognized(UserHandle.USER_NULL); + /** + * Resets the fingerprint authenticated state to false. + */ + public void clearFingerprintRecognized() { + clearFingerprintRecognized(UserHandle.USER_NULL); } - public void clearBiometricRecognizedWhenKeyguardDone(int unlockedUser) { - clearBiometricRecognized(unlockedUser); + /** + * Resets the fingerprint authenticated state to false. + */ + public void clearFingerprintRecognizedWhenKeyguardDone(int unlockedUser) { + clearFingerprintRecognized(unlockedUser); } - private void clearBiometricRecognized(int unlockedUser) { + private void clearFingerprintRecognized(int unlockedUser) { Assert.isMainThread(); mUserFingerprintAuthenticated.clear(); mTrustManager.clearAllBiometricRecognized(FINGERPRINT, unlockedUser); - mTrustManager.clearAllBiometricRecognized(FACE, unlockedUser); - mLogger.d("clearBiometricRecognized"); + mLogger.d("clearFingerprintRecognized"); for (int i = 0; i < mCallbacks.size(); i++) { KeyguardUpdateMonitorCallback cb = mCallbacks.get(i).get(); if (cb != null) { - cb.onBiometricsCleared(); + cb.onFingerprintsCleared(); } } } diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java index 02dd3312c587..9d216dcede2b 100644 --- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java +++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitorCallback.java @@ -291,9 +291,14 @@ public class KeyguardUpdateMonitorCallback { public void onLogoutEnabledChanged() { } /** - * Called when authenticated biometrics are cleared. + * Called when authenticated fingerprint biometrics are cleared. */ - public void onBiometricsCleared() { } + public void onFingerprintsCleared() { } + + /** + * Called when authenticated face biometrics have cleared. + */ + public void onFacesCleared() { } /** * Called when the secondary lock screen requirement changes. diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java index bf445177f3f8..c3f64803758b 100644 --- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java +++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java @@ -110,6 +110,10 @@ public class SystemUIApplication extends Application implements View.setTracedRequestLayoutClassClass( SystemProperties.get("persist.debug.trace_request_layout_class", null)); + if (Flags.enableLayoutTracing()) { + View.setTraceLayoutSteps(true); + } + if (Process.myUserHandle().equals(UserHandle.SYSTEM)) { IntentFilter bootCompletedFilter = new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java index c03e403b754b..a98990af00c7 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationSettings.java @@ -59,8 +59,8 @@ import android.widget.TextView; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.systemui.res.R; import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView; +import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; import java.lang.annotation.Retention; @@ -671,17 +671,17 @@ class WindowMagnificationSettings implements MagnificationGestureDetector.OnGest } private Rect getDraggableWindowBounds() { - final int layoutMargin = mContext.getResources().getDimensionPixelSize( - R.dimen.magnification_switch_button_margin); final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics(); final Insets windowInsets = windowMetrics.getWindowInsets().getInsetsIgnoringVisibility( WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout()); + // re-measure the settings panel view so that we can get the correct view size to inset + int unspecificSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + mSettingView.measure(unspecificSpec, unspecificSpec); + final Rect boundRect = new Rect(windowMetrics.getBounds()); boundRect.offsetTo(0, 0); - boundRect.inset(0, 0, mParams.width, mParams.height); + boundRect.inset(0, 0, mSettingView.getMeasuredWidth(), mSettingView.getMeasuredHeight()); boundRect.inset(windowInsets); - boundRect.inset(layoutMargin, layoutMargin); - return boundRect; } 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 1ba0220bdae7..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,15 +20,16 @@ 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 import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.time.SystemClock import javax.inject.Inject +import kotlin.math.ceil import kotlin.math.max import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher @@ -84,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, @@ -139,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 { @@ -188,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. @@ -215,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) { @@ -244,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(), - remainingMs = remainingMs.toInt(), + 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 d0d398e31859..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,20 @@ 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 milliseconds, before another authentication attempt can be done. - * If not throttling this will be `0`. + * Remaining amount of time, in seconds, before another authentication attempt can be done. If + * 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. */ - val remainingMs: Int = 0, + val remainingSeconds: Int = 0, ) 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/battery/BatteryMeterView.java b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java index 24cd9b589639..b1a153aa86aa 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java +++ b/packages/SystemUI/src/com/android/systemui/battery/BatteryMeterView.java @@ -66,8 +66,6 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { public static final int MODE_ON = 1; public static final int MODE_OFF = 2; public static final int MODE_ESTIMATE = 3; - @VisibleForTesting - public static final long LAYOUT_TRANSITION_DURATION = 200; private final AccessorizedBatteryDrawable mDrawable; private final ImageView mBatteryIconView; @@ -136,7 +134,7 @@ public class BatteryMeterView extends LinearLayout implements DarkReceiver { private void setupLayoutTransition() { LayoutTransition transition = new LayoutTransition(); - transition.setDuration(LAYOUT_TRANSITION_DURATION); + transition.setDuration(200); // Animates appearing/disappearing of the battery percentage text using fade-in/fade-out // and disables all other animation types diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt index a2ac66f6d831..63fe26a37e46 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt @@ -197,11 +197,42 @@ open class UdfpsKeyguardViewControllerLegacy( listenForGoneToAodTransition(this) listenForLockscreenAodTransitions(this) listenForAodToOccludedTransitions(this) + listenForAlternateBouncerToAodTransitions(this) + listenForDreamingToAodTransitions(this) } } } @VisibleForTesting + suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job { + return scope.launch { + transitionInteractor.transition(KeyguardState.DREAMING, KeyguardState.AOD).collect { + transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + ANIMATE_APPEAR_ON_SCREEN_OFF, + ) + } + } + } + + @VisibleForTesting + suspend fun listenForAlternateBouncerToAodTransitions(scope: CoroutineScope): Job { + return scope.launch { + transitionInteractor + .transition(KeyguardState.ALTERNATE_BOUNCER, KeyguardState.AOD) + .collect { transitionStep -> + view.onDozeAmountChanged( + transitionStep.value, + transitionStep.value, + UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, + ) + } + } + } + + @VisibleForTesting suspend fun listenForAodToOccludedTransitions(scope: CoroutineScope): Job { return scope.launch { transitionInteractor.transition(KeyguardState.AOD, KeyguardState.OCCLUDED).collect { @@ -246,7 +277,10 @@ open class UdfpsKeyguardViewControllerLegacy( suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job { return scope.launch { transitionInteractor.dozeAmountTransition.collect { transitionStep -> - if (transitionStep.transitionState == TransitionState.CANCELED) { + if ( + transitionStep.from == KeyguardState.AOD && + transitionStep.transitionState == TransitionState.CANCELED + ) { if ( transitionInteractor.startedKeyguardTransitionStep.first().to != KeyguardState.AOD 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 1122877929a0..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 @@ -32,7 +32,6 @@ import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject -import kotlin.time.Duration.Companion.milliseconds import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.async import kotlinx.coroutines.flow.MutableSharedFlow @@ -61,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 @@ -103,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() } @@ -214,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())) @@ -251,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.remainingMs.milliseconds.inWholeSeconds, + lockoutModel.remainingSeconds, ) message != null -> message else -> "" diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt index 5385442092b9..7f97718cb623 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayout.kt @@ -21,13 +21,13 @@ import androidx.annotation.VisibleForTesting /** Enumerates all known adaptive layout configurations. */ enum class BouncerSceneLayout { /** The default UI with the bouncer laid out normally. */ - STANDARD, + STANDARD_BOUNCER, /** The bouncer is displayed vertically stacked with the user switcher. */ - STACKED, + BELOW_USER_SWITCHER, /** The bouncer is displayed side-by-side with the user switcher or an empty space. */ - SIDE_BY_SIDE, + BESIDE_USER_SWITCHER, /** The bouncer is split in two with both sides shown side-by-side. */ - SPLIT, + SPLIT_BOUNCER, } /** Enumerates the supported window size classes. */ @@ -48,19 +48,19 @@ fun calculateLayoutInternal( isSideBySideSupported: Boolean, ): BouncerSceneLayout { return when (height) { - SizeClass.COMPACT -> BouncerSceneLayout.SPLIT + SizeClass.COMPACT -> BouncerSceneLayout.SPLIT_BOUNCER SizeClass.MEDIUM -> when (width) { - SizeClass.COMPACT -> BouncerSceneLayout.STANDARD - SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD - SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.MEDIUM -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER } SizeClass.EXPANDED -> when (width) { - SizeClass.COMPACT -> BouncerSceneLayout.STANDARD - SizeClass.MEDIUM -> BouncerSceneLayout.STACKED - SizeClass.EXPANDED -> BouncerSceneLayout.SIDE_BY_SIDE + SizeClass.COMPACT -> BouncerSceneLayout.STANDARD_BOUNCER + SizeClass.MEDIUM -> BouncerSceneLayout.BELOW_USER_SWITCHER + SizeClass.EXPANDED -> BouncerSceneLayout.BESIDE_USER_SWITCHER } - }.takeIf { it != BouncerSceneLayout.SIDE_BY_SIDE || isSideBySideSupported } - ?: BouncerSceneLayout.STANDARD + }.takeIf { it != BouncerSceneLayout.BESIDE_USER_SWITCHER || isSideBySideSupported } + ?: BouncerSceneLayout.STANDARD_BOUNCER } 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 58fa85781ca1..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 @@ -36,7 +36,6 @@ import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel import com.android.systemui.user.ui.viewmodel.UserViewModel import dagger.Module import dagger.Provides -import kotlin.math.ceil import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob @@ -106,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?> = @@ -129,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, @@ -150,7 +149,7 @@ class BouncerViewModel( initialValue = toMessageViewModel( message = bouncerInteractor.message.value, - isThrottled = bouncerInteractor.throttling.value != null, + isLockedOut = bouncerInteractor.lockout.value != null, ), ) @@ -198,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, - ceil(throttling.remainingMs / 1000f).toInt(), + 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 { @@ -232,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/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index cd764c0cc1a4..b91541813d62 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -1,6 +1,5 @@ package com.android.systemui.deviceentry -import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import dagger.Module @@ -10,7 +9,6 @@ import dagger.multibindings.Multibinds includes = [ DeviceEntryRepositoryModule::class, - DeviceEntryHapticsRepositoryModule::class, ], ) abstract class DeviceEntryModule { diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt deleted file mode 100644 index 1458404446e6..000000000000 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt +++ /dev/null @@ -1,72 +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.systemui.deviceentry.data.repository - -import com.android.systemui.dagger.SysUISingleton -import dagger.Binds -import dagger.Module -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow - -/** Interface for classes that can access device-entry haptics application state. */ -interface DeviceEntryHapticsRepository { - /** - * Whether a successful biometric haptic has been requested. Has not yet been handled if true. - */ - val successHapticRequest: Flow<Boolean> - - /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */ - val errorHapticRequest: Flow<Boolean> - - fun requestSuccessHaptic() - fun handleSuccessHaptic() - fun requestErrorHaptic() - fun handleErrorHaptic() -} - -/** Encapsulates application state for device entry haptics. */ -@SysUISingleton -class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository { - private val _successHapticRequest = MutableStateFlow(false) - override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow() - - private val _errorHapticRequest = MutableStateFlow(false) - override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow() - - override fun requestSuccessHaptic() { - _successHapticRequest.value = true - } - - override fun handleSuccessHaptic() { - _successHapticRequest.value = false - } - - override fun requestErrorHaptic() { - _errorHapticRequest.value = true - } - - override fun handleErrorHaptic() { - _errorHapticRequest.value = false - } -} - -@Module -interface DeviceEntryHapticsRepositoryModule { - @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository -} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt index f27bbe6c7624..08e8c2d8271f 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -7,26 +7,36 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.keyguard.data.repository.KeyguardRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockModel +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.statusbar.phone.KeyguardBypassController import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.data.repository.UserRepository +import com.android.systemui.util.kotlin.sample import dagger.Binds import dagger.Module import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext /** Interface for classes that can access device-entry-related application state. */ interface DeviceEntryRepository { + /** Whether the device is immediately entering the device after a biometric unlock. */ + val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> + /** * Whether the device is unlocked. * @@ -73,7 +83,14 @@ constructor( private val lockPatternUtils: LockPatternUtils, private val keyguardBypassController: KeyguardBypassController, keyguardStateController: KeyguardStateController, + keyguardRepository: KeyguardRepository, ) : DeviceEntryRepository { + override val enteringDeviceFromBiometricUnlock = + keyguardRepository.biometricUnlockState + .filter { BiometricUnlockModel.dismissesKeyguard(it) } + .sample( + keyguardRepository.biometricUnlockSource.filterNotNull(), + ) private val _isUnlocked = MutableStateFlow(false) diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt new file mode 100644 index 000000000000..1a6bd0427db5 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt @@ -0,0 +1,70 @@ +/* + * 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.deviceentry.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.deviceentry.shared.DeviceEntryBiometricMode +import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository +import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus +import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.emptyFlow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map + +/** Business logic for device entry biometric states that may differ based on the biometric mode. */ +@ExperimentalCoroutinesApi +@SysUISingleton +class DeviceEntryBiometricAuthInteractor +@Inject +constructor( + biometricSettingsRepository: BiometricSettingsRepository, + deviceEntryFaceAuthInteractor: DeviceEntryFaceAuthInteractor, +) { + private val biometricMode: Flow<DeviceEntryBiometricMode> = + combine( + biometricSettingsRepository.isFingerprintEnrolledAndEnabled, + biometricSettingsRepository.isFaceAuthEnrolledAndEnabled, + ) { fingerprintEnrolled, faceEnrolled -> + if (fingerprintEnrolled && faceEnrolled) { + DeviceEntryBiometricMode.CO_EXPERIENCE + } else if (fingerprintEnrolled) { + DeviceEntryBiometricMode.FINGERPRINT_ONLY + } else if (faceEnrolled) { + DeviceEntryBiometricMode.FACE_ONLY + } else { + DeviceEntryBiometricMode.NONE + } + } + private val faceOnly: Flow<Boolean> = + biometricMode.map { it == DeviceEntryBiometricMode.FACE_ONLY } + + /** + * Triggered if face is the only biometric that can be used for device entry and a face failure + * occurs. + */ + val faceOnlyFaceFailure: Flow<FailedFaceAuthenticationStatus> = + faceOnly.flatMapLatest { faceOnly -> + if (faceOnly) { + deviceEntryFaceAuthInteractor.faceFailure + } else { + emptyFlow() + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt new file mode 100644 index 000000000000..70716c6c91fe --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt @@ -0,0 +1,34 @@ +/* + * 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.deviceentry.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterIsInstance + +@SysUISingleton +class DeviceEntryFaceAuthInteractor +@Inject +constructor( + repository: DeviceEntryFaceAuthRepository, +) { + val faceFailure: Flow<FailedFaceAuthenticationStatus> = + repository.authenticationStatus.filterIsInstance<FailedFaceAuthenticationStatus>() +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt new file mode 100644 index 000000000000..efa1c0a07490 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt @@ -0,0 +1,34 @@ +/* + * 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.deviceentry.domain.interactor + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.filterIsInstance + +@SysUISingleton +class DeviceEntryFingerprintAuthInteractor +@Inject +constructor( + repository: DeviceEntryFingerprintAuthRepository, +) { + val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> = + repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>() +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt index 53d6f737af8d..649a9715ffea 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -19,7 +19,6 @@ import com.android.keyguard.logging.BiometricUnlockLogger import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.power.domain.interactor.PowerInteractor @@ -34,11 +33,12 @@ import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onStart /** * Business logic for device entry haptic events. Determines whether the haptic should play. In - * particular, there are extra guards for whether device entry error and successes hatpics should + * particular, there are extra guards for whether device entry error and successes haptics should * play when the physical fingerprint sensor is located on the power button. */ @ExperimentalCoroutinesApi @@ -46,7 +46,9 @@ import kotlinx.coroutines.flow.onStart class DeviceEntryHapticsInteractor @Inject constructor( - private val repository: DeviceEntryHapticsRepository, + deviceEntryInteractor: DeviceEntryInteractor, + deviceEntryFingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, + deviceEntryBiometricAuthInteractor: DeviceEntryBiometricAuthInteractor, fingerprintPropertyRepository: FingerprintPropertyRepository, biometricSettingsRepository: BiometricSettingsRepository, keyEventInteractor: KeyEventInteractor, @@ -77,9 +79,8 @@ constructor( emit(recentPowerButtonPressThresholdMs * -1L - 1L) } - val playSuccessHaptic: Flow<Boolean> = - repository.successHapticRequest - .filter { it } + val playSuccessHaptic: Flow<Unit> = + deviceEntryInteractor.enteringDeviceFromBiometricUnlock .sample( combine( powerButtonSideFpsEnrolled, @@ -88,7 +89,7 @@ constructor( ::Triple ) ) - .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> + .filter { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) -> val sideFpsAllowsHaptic = !powerButtonDown && systemClock.uptimeMillis() - lastPowerButtonWakeup > @@ -96,38 +97,28 @@ constructor( val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic if (!allowHaptic) { logger.d("Skip success haptic. Recent power button press or button is down.") - handleSuccessHaptic() // immediately handle, don't vibrate } allowHaptic } - val playErrorHaptic: Flow<Boolean> = - repository.errorHapticRequest - .filter { it } + .map {} // map to Unit + + private val playErrorHapticForBiometricFailure: Flow<Unit> = + merge( + deviceEntryFingerprintAuthInteractor.fingerprintFailure, + deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure, + ) + .map {} // map to Unit + val playErrorHaptic: Flow<Unit> = + playErrorHapticForBiometricFailure .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair)) - .map { (sideFpsEnrolled, powerButtonDown) -> + .filter { (sideFpsEnrolled, powerButtonDown) -> val allowHaptic = !sideFpsEnrolled || !powerButtonDown if (!allowHaptic) { logger.d("Skip error haptic. Power button is down.") - handleErrorHaptic() // immediately handle, don't vibrate } allowHaptic } - - fun vibrateSuccess() { - repository.requestSuccessHaptic() - } - - fun vibrateError() { - repository.requestErrorHaptic() - } - - fun handleSuccessHaptic() { - repository.handleSuccessHaptic() - } - - fun handleErrorHaptic() { - repository.handleErrorHaptic() - } + .map {} // map to Unit private val recentPowerButtonPressThresholdMs = 400L } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 4cddb9ccffdb..47be8ab0c0a2 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository import com.android.systemui.keyguard.data.repository.TrustRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.SceneKey @@ -30,6 +31,7 @@ import com.android.systemui.scene.shared.model.SceneModel import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.collectLatest @@ -60,6 +62,9 @@ constructor( trustRepository: TrustRepository, flags: SceneContainerFlags, ) { + val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> = + repository.enteringDeviceFromBiometricUnlock + /** * Whether the device is unlocked. * diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt new file mode 100644 index 000000000000..6d885b349527 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryBiometricMode.kt @@ -0,0 +1,29 @@ +/* + * 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.deviceentry.shared + +/** Models the biometrics that can be used to enter the device. */ +enum class DeviceEntryBiometricMode { + /** No biometrics can be used to enter the device from the lockscreen. */ + NONE, + /** Only face can be used to enter the device from the lockscreen. */ + FACE_ONLY, + /** Only fingerprint can be used to enter the device from the lockscreen. */ + FINGERPRINT_ONLY, + /** Both face and fingerprint can be used to enter the device from the lockscreen. */ + CO_EXPERIENCE, +} diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java index 9c13a8c82b11..3fac865ffb1d 100644 --- a/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java +++ b/packages/SystemUI/src/com/android/systemui/doze/DozeAuthRemover.java @@ -43,7 +43,7 @@ public class DozeAuthRemover implements DozeMachine.Part { if (newState == DozeMachine.State.DOZE || newState == DozeMachine.State.DOZE_AOD) { int currentUser = mSelectedUserInteractor.getSelectedUserId(); if (mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(currentUser)) { - mKeyguardUpdateMonitor.clearBiometricRecognized(); + mKeyguardUpdateMonitor.clearFingerprintRecognized(); } } } diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java index 87c12b4a5a59..72b08910ef32 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java +++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsClassicDebug.java @@ -25,7 +25,6 @@ import static com.android.systemui.flags.FlagManager.EXTRA_NAME; import static com.android.systemui.flags.FlagManager.EXTRA_VALUE; import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS; import static com.android.systemui.shared.Flags.exampleSharedFlag; - import static java.util.Objects.requireNonNull; import android.content.BroadcastReceiver; @@ -508,9 +507,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { enabled = isEnabled((ResourceBooleanFlag) f); overridden = readBooleanFlagOverride(f.getName()) != null; } else if (f instanceof SysPropBooleanFlag) { - // TODO(b/223379190): Teamfood not supported for sysprop flags yet. enabled = isEnabled((SysPropBooleanFlag) f); - teamfood = false; overridden = !mSystemProperties.get(f.getName()).isEmpty(); } else { // TODO: add support for other flag types. @@ -519,7 +516,7 @@ public class FeatureFlagsClassicDebug implements FeatureFlagsClassic { } if (enabled) { - return new ReleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden); + return new ReleasedFlag(f.getName(), f.getNamespace(), overridden); } else { return new UnreleasedFlag(f.getName(), f.getNamespace(), teamfood, overridden); } 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/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index e8ceabf90af7..5a763b11d6a4 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -278,10 +278,6 @@ object Flags { "qs_user_detail_shortcut" ) - // TODO(b/296357483): Tracking Bug - @JvmField - val QS_PIPELINE_NEW_TILES = unreleasedFlag("qs_pipeline_new_tiles") - // TODO(b/254512383): Tracking Bug @JvmField val FULL_SCREEN_USER_SWITCHER = diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java index 30090874a480..b7260f2b2b94 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java +++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java @@ -2611,14 +2611,14 @@ public class KeyguardViewMediator implements CoreStartable, Dumpable, } if (mGoingToSleep) { - mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser); + mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser); Log.i(TAG, "Device is going to sleep, aborting keyguardDone"); return; } setPendingLock(false); // user may have authenticated during the screen off animation handleHide(); - mUpdateMonitor.clearBiometricRecognizedWhenKeyguardDone(currentUser); + mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser); Trace.endSection(); } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt index e47c44863980..eceaf6c3c4fd 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt @@ -326,7 +326,7 @@ constructor( it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS }, ) - .flowOn(backgroundDispatcher) + .flowOn(mainDispatcher) // should revoke auth ASAP in the main thread .onEach { anyOfThemIsTrue -> if (anyOfThemIsTrue) { clearPendingAuthRequest("Resetting auth status") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt index 0b6b971f2314..7fdcf2f09bc1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt @@ -48,7 +48,7 @@ constructor( override fun start() { listenForDreamingToOccluded() listenForDreamingToGone() - listenForDreamingToDozing() + listenForDreamingToAodOrDozing() listenForTransitionToCamera(scope, keyguardInteractor) } @@ -94,7 +94,7 @@ constructor( } } - private fun listenForDreamingToDozing() { + private fun listenForDreamingToAodOrDozing() { scope.launch { combine( keyguardInteractor.dozeTransitionModel, @@ -102,11 +102,12 @@ constructor( ::Pair ) .collect { (dozeTransitionModel, keyguardState) -> - if ( - dozeTransitionModel.to == DozeStateModel.DOZE && - keyguardState == KeyguardState.DREAMING - ) { - startTransitionTo(KeyguardState.DOZING) + if (keyguardState == KeyguardState.DREAMING) { + if (dozeTransitionModel.to == DozeStateModel.DOZE) { + startTransitionTo(KeyguardState.DOZING) + } else if (dozeTransitionModel.to == DozeStateModel.DOZE_AOD) { + startTransitionTo(KeyguardState.AOD) + } } } } 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/KeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt index 5ed70b526f1b..046916aeb277 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractor.kt @@ -78,6 +78,9 @@ interface KeyguardFaceAuthInteractor { * flows. */ interface FaceAuthenticationListener { + /** Receive face isAuthenticated updates */ + fun onAuthenticatedChanged(isAuthenticated: Boolean) + /** Receive face authentication status updates */ fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) 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..7882a9758105 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 @@ -117,10 +120,14 @@ constructor( * This is useful for experiences like the lock screen preview mode, where the affordances must * always be visible. */ - fun quickAffordanceAlwaysVisible( + suspend fun quickAffordanceAlwaysVisible( position: KeyguardQuickAffordancePosition, ): Flow<KeyguardQuickAffordanceModel> { - return quickAffordanceInternal(position) + return if (isFeatureDisabledByDevicePolicy()) { + flowOf(KeyguardQuickAffordanceModel.Hidden) + } else { + quickAffordanceInternal(position) + } } /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt index 532df4afebf7..fb20000471a2 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt @@ -16,8 +16,10 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.trust.TrustManager import android.content.Context import android.hardware.biometrics.BiometricFaceConstants +import android.hardware.biometrics.BiometricSourceType import com.android.keyguard.FaceAuthUiEvent import com.android.keyguard.FaceWakeUpTriggersConfig import com.android.keyguard.KeyguardUpdateMonitor @@ -83,6 +85,7 @@ constructor( private val faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig, private val powerInteractor: PowerInteractor, private val biometricSettingsRepository: BiometricSettingsRepository, + private val trustManager: TrustManager, ) : CoreStartable, KeyguardFaceAuthInteractor { private val listeners: MutableList<FaceAuthenticationListener> = mutableListOf() @@ -291,6 +294,20 @@ constructor( .onEach { running -> listeners.forEach { it.onRunningStateChanged(running) } } .flowOn(mainDispatcher) .launchIn(applicationScope) + repository.isAuthenticated + .sample(userRepository.selectedUserInfo, ::Pair) + .onEach { (isAuthenticated, userInfo) -> + if (!isAuthenticated) { + faceAuthenticationLogger.clearFaceRecognized() + trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id) + } + } + .flowOn(backgroundDispatcher) + .onEach { (isAuthenticated, _) -> + listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) } + } + .flowOn(mainDispatcher) + .launchIn(applicationScope) biometricSettingsRepository.isFaceAuthEnrolledAndEnabled .onEach { enrolledAndEnabled -> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt index 8fe6309fc005..2ae5ce1ad545 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt @@ -51,9 +51,21 @@ enum class BiometricUnlockModel { companion object { private val wakeAndUnlockModes = setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING) + private val dismissesKeyguardModes = + setOf( + WAKE_AND_UNLOCK, + WAKE_AND_UNLOCK_PULSING, + UNLOCK_COLLAPSING, + WAKE_AND_UNLOCK_FROM_DREAM, + DISMISS_BOUNCER + ) fun isWakeAndUnlock(model: BiometricUnlockModel): Boolean { return wakeAndUnlockModes.contains(model) } + + fun dismissesKeyguard(model: BiometricUnlockModel): Boolean { + return dismissesKeyguardModes.contains(model) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt index 3c143fe1a68a..cc385a8eea85 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/FingerprintAuthenticationModels.kt @@ -49,7 +49,7 @@ data class AcquiredFingerprintAuthenticationStatus(val acquiredInfo: Int) : } /** Fingerprint authentication failed message. */ -object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus() +data object FailFingerprintAuthenticationStatus : FingerprintAuthenticationStatus() /** Fingerprint authentication error message */ data class ErrorFingerprintAuthenticationStatus( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt index 4b4a19ecd770..dcf4284438bf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/DeviceEntryIconViewBinder.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.binder import android.annotation.SuppressLint import android.content.res.ColorStateList +import android.view.HapticFeedbackConstants import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle @@ -30,6 +31,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryForegroundViewModel import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.plugins.FalsingManager +import com.android.systemui.statusbar.VibratorHelper import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -51,6 +53,7 @@ object DeviceEntryIconViewBinder { fgViewModel: DeviceEntryForegroundViewModel, bgViewModel: DeviceEntryBackgroundViewModel, falsingManager: FalsingManager, + vibratorHelper: VibratorHelper, ) { DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() val longPressHandlingView = view.longPressHandlingView @@ -62,6 +65,10 @@ object DeviceEntryIconViewBinder { if (falsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { return } + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.CONFIRM, + ) viewModel.onLongPress() } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt index e603ead463f2..01a1ca3eeb93 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt @@ -68,7 +68,6 @@ import javax.inject.Provider import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -253,27 +252,21 @@ object KeyguardRootViewBinder { if (deviceEntryHapticsInteractor != null && vibratorHelper != null) { launch { - deviceEntryHapticsInteractor.playSuccessHaptic - .filter { it } - .collect { - vibratorHelper.performHapticFeedback( - view, - HapticFeedbackConstants.CONFIRM, - ) - deviceEntryHapticsInteractor.handleSuccessHaptic() - } + deviceEntryHapticsInteractor.playSuccessHaptic.collect { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.CONFIRM, + ) + } } launch { - deviceEntryHapticsInteractor.playErrorHaptic - .filter { it } - .collect { - vibratorHelper.performHapticFeedback( - view, - HapticFeedbackConstants.REJECT, - ) - deviceEntryHapticsInteractor.handleErrorHaptic() - } + deviceEntryHapticsInteractor.playErrorHaptic.collect { + vibratorHelper.performHapticFeedback( + view, + HapticFeedbackConstants.REJECT, + ) + } } } } 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/DefaultDeviceEntrySection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt index 77ab9f416910..a693ec9317d0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySection.kt @@ -49,6 +49,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.gesture.TapGestureDetector import dagger.Lazy import javax.inject.Inject @@ -76,6 +77,7 @@ constructor( @Application private val scope: CoroutineScope, private val swipeUpAnywhereGestureHandler: Lazy<SwipeUpAnywhereGestureHandler>, private val tapGestureDetector: Lazy<TapGestureDetector>, + private val vibratorHelper: Lazy<VibratorHelper>, ) : KeyguardSection() { private val deviceEntryIconViewId = R.id.device_entry_icon_view private val alternateBouncerViewId = R.id.alternate_bouncer @@ -114,6 +116,7 @@ constructor( deviceEntryForegroundViewModel.get(), deviceEntryBackgroundViewModel.get(), falsingManager.get(), + vibratorHelper.get(), ) } constraintLayout.findViewById<FrameLayout?>(alternateBouncerViewId)?.let { 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/DeviceEntryIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt index bd6aae8f2dcb..f95713bf1802 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModel.kt @@ -19,7 +19,6 @@ package com.android.systemui.keyguard.ui.viewmodel import android.animation.FloatEvaluator import android.animation.IntEvaluator import com.android.keyguard.KeyguardViewController -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.BurnInInteractor @@ -56,7 +55,6 @@ constructor( val shadeDependentFlows: ShadeDependentFlows, private val sceneContainerFlags: SceneContainerFlags, private val keyguardViewController: Lazy<KeyguardViewController>, - private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor, private val deviceEntryInteractor: DeviceEntryInteractor, ) { private val intEvaluator = IntEvaluator() @@ -182,8 +180,6 @@ constructor( } fun onLongPress() { - deviceEntryHapticsInteractor.vibrateSuccess() - // TODO (b/309804148): play auth ripple via an interactor if (sceneContainerFlags.isEnabled()) { 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/log/FaceAuthenticationLogger.kt b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt index 8c5690b312ec..3c2facbc967a 100644 --- a/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/log/FaceAuthenticationLogger.kt @@ -132,6 +132,10 @@ constructor( logBuffer.log(TAG, DEBUG, "Face authentication failed") } + fun clearFaceRecognized() { + logBuffer.log(TAG, DEBUG, "Clear face recognized") + } + fun authenticationError( errorCode: Int, errString: CharSequence?, diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java index dc55179f53f8..d8bb3e65392f 100644 --- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java +++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java @@ -188,7 +188,7 @@ public class LogModule { LogBufferFactory factory, QSPipelineFlagsRepository flags ) { - if (flags.getPipelineTilesEnabled()) { + if (flags.getTilesEnabled()) { // we use return factory.create("QSLog", 450 /* maxSize */, false /* systrace */); } else { diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java index 1fab58e18ad2..828d6ed6ab89 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java @@ -57,6 +57,8 @@ import com.android.systemui.tuner.TunerService; import com.android.systemui.tuner.TunerService.Tunable; import com.android.systemui.util.settings.SecureSettings; +import dagger.Lazy; + import org.jetbrains.annotations.NotNull; import java.io.PrintWriter; @@ -73,8 +75,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; import javax.inject.Provider; -import dagger.Lazy; - /** Platform implementation of the quick settings tile host * * This class keeps track of the set of current tiles and is the in memory source of truth @@ -151,7 +151,7 @@ public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, P mShadeController = shadeController; - if (featureFlags.getPipelineTilesEnabled()) { + if (featureFlags.getTilesEnabled()) { mQsFactories.add(newQsTileFactoryProvider.get()); } mQsFactories.add(defaultFactory); diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt index 4bda7307c667..5d28c8c0b69a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt @@ -336,7 +336,7 @@ constructor( private suspend fun createTile(spec: TileSpec): QSTile? { val tile = withContext(mainDispatcher) { - if (featureFlags.pipelineTilesEnabled) { + if (featureFlags.tilesEnabled) { newQSTileFactory.get().createTile(spec.spec) } else { null diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt index 5c7420cb3c1b..935d07229ae5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt @@ -2,24 +2,18 @@ package com.android.systemui.qs.pipeline.shared import com.android.systemui.Flags as AconfigFlags import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags import com.android.systemui.flags.RefactorFlagUtils import javax.inject.Inject /** Encapsulate the different QS pipeline flags and their dependencies */ @SysUISingleton -class QSPipelineFlagsRepository -@Inject -constructor( - private val featureFlags: FeatureFlagsClassic, -) { +class QSPipelineFlagsRepository @Inject constructor() { + val pipelineEnabled: Boolean get() = AconfigFlags.qsNewPipeline() - /** @see Flags.QS_PIPELINE_NEW_TILES */ - val pipelineTilesEnabled: Boolean - get() = featureFlags.isEnabled(Flags.QS_PIPELINE_NEW_TILES) + val tilesEnabled: Boolean + get() = AconfigFlags.qsNewTiles() companion object Utils { fun assertInLegacyMode() = @@ -27,5 +21,11 @@ constructor( AconfigFlags.qsNewPipeline(), AconfigFlags.FLAG_QS_NEW_PIPELINE ) + + fun assertNewTilesInLegacyMode() = + RefactorFlagUtils.assertInLegacyMode( + AconfigFlags.qsNewTiles(), + AconfigFlags.FLAG_QS_NEW_TILES + ) } } 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/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt index 27007bbf7aee..52e49f9d2653 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt @@ -19,6 +19,7 @@ package com.android.systemui.qs.tiles.di import com.android.systemui.dagger.SysUISingleton import com.android.systemui.plugins.qs.QSFactory import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory import com.android.systemui.qs.tiles.impl.custom.di.CustomTileComponent @@ -44,6 +45,7 @@ constructor( ) : QSFactory { init { + QSPipelineFlagsRepository.assertNewTilesInLegacyMode() for (viewModelTileSpec in tileMap.keys) { require(qsTileConfigProvider.hasConfig(viewModelTileSpec)) { "No config for $viewModelTileSpec" diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt new file mode 100644 index 000000000000..fc42ba495a51 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverDialogDelegate.kt @@ -0,0 +1,60 @@ +/* + * 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.impl.saver.domain + +import android.content.Context +import android.content.DialogInterface +import android.content.SharedPreferences +import android.os.Bundle +import com.android.internal.R +import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.policy.DataSaverController +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +class DataSaverDialogDelegate( + private val sysuiDialogFactory: SystemUIDialog.Factory, + private val context: Context, + private val backgroundContext: CoroutineContext, + private val dataSaverController: DataSaverController, + private val sharedPreferences: SharedPreferences, +) : SystemUIDialog.Delegate { + override fun createDialog(): SystemUIDialog { + return sysuiDialogFactory.create(this, context) + } + + override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) { + with(dialog) { + setTitle(R.string.data_saver_enable_title) + setMessage(R.string.data_saver_description) + setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ -> + CoroutineScope(backgroundContext).launch { + dataSaverController.setDataSaverEnabled(true) + } + + sharedPreferences + .edit() + .putBoolean(DataSaverTileUserActionInteractor.DIALOG_SHOWN, true) + .apply() + } + setNeutralButton(R.string.cancel, null) + setShowForAllUsers(true) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt new file mode 100644 index 000000000000..25b09131522b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt @@ -0,0 +1,49 @@ +/* + * 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.impl.saver.domain + +import android.content.res.Resources +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import javax.inject.Inject + +/** Maps [DataSaverTileModel] to [QSTileState]. */ +class DataSaverTileMapper @Inject constructor(@Main private val resources: Resources) : + QSTileDataToStateMapper<DataSaverTileModel> { + override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState = + QSTileState.build(resources, config.uiConfig) { + with(data) { + if (isEnabled) { + activationState = QSTileState.ActivationState.ACTIVE + icon = { Icon.Resource(R.drawable.qs_data_saver_icon_on, null) } + secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2] + } else { + activationState = QSTileState.ActivationState.INACTIVE + icon = { Icon.Resource(R.drawable.qs_data_saver_icon_off, null) } + secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1] + } + contentDescription = label + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt new file mode 100644 index 000000000000..91e049b68c06 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileDataInteractor.kt @@ -0,0 +1,52 @@ +/* + * 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.impl.saver.domain.interactor + +import android.os.UserHandle +import com.android.systemui.common.coroutine.ConflatedCallbackFlow +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.statusbar.policy.DataSaverController +import javax.inject.Inject +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf + +/** Observes data saver state changes providing the [DataSaverTileModel]. */ +class DataSaverTileDataInteractor +@Inject +constructor( + private val dataSaverController: DataSaverController, +) : QSTileDataInteractor<DataSaverTileModel> { + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<DataSaverTileModel> = + ConflatedCallbackFlow.conflatedCallbackFlow { + val initialValue = dataSaverController.isDataSaverEnabled + trySend(DataSaverTileModel(initialValue)) + + val callback = DataSaverController.Listener { trySend(DataSaverTileModel(it)) } + + dataSaverController.addCallback(callback) + awaitClose { dataSaverController.removeCallback(callback) } + } + + override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt new file mode 100644 index 000000000000..af74409630ca --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/interactor/DataSaverTileUserActionInteractor.kt @@ -0,0 +1,111 @@ +/* + * 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.impl.saver.domain.interactor + +import android.content.Context +import android.content.Intent +import android.provider.Settings +import com.android.internal.jank.InteractionJankMonitor +import com.android.systemui.animation.DialogCuj +import com.android.systemui.animation.DialogLaunchAnimator +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dagger.qualifiers.Main +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.interactor.QSTileInput +import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverDialogDelegate +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.settings.UserFileManager +import com.android.systemui.statusbar.phone.SystemUIDialog +import com.android.systemui.statusbar.policy.DataSaverController +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext + +/** Handles data saver tile clicks. */ +class DataSaverTileUserActionInteractor +@Inject +constructor( + @Application private val context: Context, + @Main private val coroutineContext: CoroutineContext, + @Background private val backgroundContext: CoroutineContext, + private val dataSaverController: DataSaverController, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val dialogLaunchAnimator: DialogLaunchAnimator, + private val systemUIDialogFactory: SystemUIDialog.Factory, + userFileManager: UserFileManager, +) : QSTileUserActionInteractor<DataSaverTileModel> { + companion object { + private const val INTERACTION_JANK_TAG = "start_data_saver" + const val PREFS = "data_saver" + const val DIALOG_SHOWN = "data_saver_dialog_shown" + } + + val sharedPreferences = + userFileManager.getSharedPreferences(PREFS, Context.MODE_PRIVATE, context.userId) + + override suspend fun handleInput(input: QSTileInput<DataSaverTileModel>): Unit = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + val wasEnabled: Boolean = data.isEnabled + if (wasEnabled || sharedPreferences.getBoolean(DIALOG_SHOWN, false)) { + withContext(backgroundContext) { + dataSaverController.setDataSaverEnabled(!wasEnabled) + } + return@with + } + // Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator + // must be created and shown on the main thread, so we post it to the UI + // handler + withContext(coroutineContext) { + val dialogContext = action.view?.context ?: context + val dialogDelegate = + DataSaverDialogDelegate( + systemUIDialogFactory, + dialogContext, + backgroundContext, + dataSaverController, + sharedPreferences + ) + val dialog = systemUIDialogFactory.create(dialogDelegate, dialogContext) + + if (action.view != null) { + dialogLaunchAnimator.showFromView( + dialog, + action.view!!, + DialogCuj( + InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, + INTERACTION_JANK_TAG + ) + ) + } else { + dialog.show() + } + } + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.view, + Intent(Settings.ACTION_DATA_SAVER_SETTINGS) + ) + } + } + } +} diff --git a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt index be1f081d5b8f..040c7bf55236 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlayExtraWideBack/res/values-sw600dp/config.xml +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/model/DataSaverTileModel.kt @@ -1,13 +1,11 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/** - * Copyright (c) 2023, The Android Open Source Project +/* + * 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 + * 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, @@ -15,8 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ ---> -<resources> - <!-- If true, attach the navigation bar to the app during app transition --> - <bool name="config_attachNavBarToAppDuringTransition">false</bool> -</resources> + +package com.android.systemui.qs.tiles.impl.saver.domain.model + +/** + * data saver tile model. + * + * @param isEnabled is true when the data saver is enabled; + */ +@JvmInline value class DataSaverTileModel(val isEnabled: Boolean) 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/settings/brightness/BrightnessSliderView.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java index c88549224183..c43d20cdf52f 100644 --- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java +++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessSliderView.java @@ -35,6 +35,8 @@ import com.android.settingslib.RestrictedLockUtils; import com.android.systemui.Gefingerpoken; import com.android.systemui.res.R; +import java.util.Collections; + /** * {@code FrameLayout} used to show and manipulate a {@link ToggleSeekBar}. * @@ -48,6 +50,7 @@ public class BrightnessSliderView extends FrameLayout { @Nullable private Drawable mProgressDrawable; private float mScale = 1f; + private final Rect mSystemGestureExclusionRect = new Rect(); public BrightnessSliderView(Context context) { this(context, null); @@ -176,6 +179,11 @@ public class BrightnessSliderView extends FrameLayout { protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); applySliderScale(); + int horizontalMargin = + getResources().getDimensionPixelSize(R.dimen.notification_side_paddings); + mSystemGestureExclusionRect.set(-horizontalMargin, 0, right - left + horizontalMargin, + bottom - top); + setSystemGestureExclusionRects(Collections.singletonList(mSystemGestureExclusionRect)); } /** diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java index e9779cd02760..5fbb60d76fbb 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java @@ -59,6 +59,7 @@ import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; import com.android.systemui.res.R; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.ui.view.WindowRootViewComponent; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.domain.interactor.ShadeInteractor; @@ -116,6 +117,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW private final AuthController mAuthController; private final Lazy<SelectedUserInteractor> mUserInteractor; private final Lazy<ShadeInteractor> mShadeInteractorLazy; + private final SceneContainerFlags mSceneContainerFlags; private ViewGroup mWindowRootView; private LayoutParams mLp; private boolean mHasTopUi; @@ -162,7 +164,8 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW Lazy<ShadeInteractor> shadeInteractorLazy, ShadeWindowLogger logger, Lazy<SelectedUserInteractor> userInteractor, - UserTracker userTracker) { + UserTracker userTracker, + SceneContainerFlags sceneContainerFlags) { mContext = context; mWindowRootViewComponentFactory = windowRootViewComponentFactory; mWindowManager = windowManager; @@ -180,6 +183,7 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW dumpManager.registerDumpable(this); mAuthController = authController; mUserInteractor = userInteractor; + mSceneContainerFlags = sceneContainerFlags; mLastKeyguardRotationAllowed = mKeyguardStateController.isKeyguardScreenRotationAllowed(); mLockScreenDisplayTimeout = context.getResources() .getInteger(R.integer.config_lockScreenDisplayTimeout); @@ -287,6 +291,15 @@ public class NotificationShadeWindowControllerImpl implements NotificationShadeW mLp.privateFlags |= PRIVATE_FLAG_BEHAVIOR_CONTROLLED; mLp.insetsFlags.behavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; + if (mSceneContainerFlags.isEnabled()) { + // This prevents the appearance and disappearance of the software keyboard (also known + // as the "IME") from scrolling/panning the window to make room for the keyboard. + // + // The scene container logic does its own adjustment and animation when the IME appears + // or disappears. + mLp.softInputMode = LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + } + mWindowManager.addView(mWindowRootView, mLp); mLpChanged.copyFrom(mLp); diff --git a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java index dd194eaade9b..8397caae438f 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/QuickSettingsController.java @@ -794,13 +794,6 @@ public class QuickSettingsController implements Dumpable { /** update Qs height state */ public void setExpansionHeight(float height) { - // TODO(b/277909752): remove below log when bug is fixed - if (mSplitShadeEnabled && mShadeExpandedFraction == 1.0f && height == 0 - && mBarState == SHADE) { - Log.wtf(TAG, - "setting QS height to 0 in split shade while shade is open(ing). " - + "Value of isExpandImmediate() = " + isExpandImmediate()); - } int maxHeight = getMaxExpansionHeight(); height = Math.min(Math.max( height, getMinExpansionHeight()), maxHeight); 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/connectivity/ConnectivityModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt index a3adea0b86d9..642eaccc3c99 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/connectivity/ConnectivityModule.kt @@ -35,6 +35,10 @@ import com.android.systemui.qs.tiles.impl.airplane.domain.AirplaneModeMapper import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileDataInteractor import com.android.systemui.qs.tiles.impl.airplane.domain.interactor.AirplaneModeTileUserActionInteractor import com.android.systemui.qs.tiles.impl.airplane.domain.model.AirplaneModeTileModel +import com.android.systemui.qs.tiles.impl.saver.domain.DataSaverTileMapper +import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileDataInteractor +import com.android.systemui.qs.tiles.impl.saver.domain.interactor.DataSaverTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.saver.domain.model.DataSaverTileModel import com.android.systemui.qs.tiles.viewmodel.QSTileConfig import com.android.systemui.qs.tiles.viewmodel.QSTilePolicy import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig @@ -85,6 +89,7 @@ interface ConnectivityModule { companion object { const val AIRPLANE_MODE_TILE_SPEC = "airplane" + const val DATA_SAVER_TILE_SPEC = "saver" /** Inject InternetTile or InternetTileNewImpl into tileMap in QSModule */ @Provides @@ -132,5 +137,36 @@ interface ConnectivityModule { stateInteractor, mapper, ) + + @Provides + @IntoMap + @StringKey(DATA_SAVER_TILE_SPEC) + fun provideDataSaverTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(DATA_SAVER_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_data_saver_icon_off, + labelRes = R.string.data_saver, + ), + instanceId = uiEventLogger.getNewInstanceId(), + ) + + /** Inject DataSaverTile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(DATA_SAVER_TILE_SPEC) + fun provideDataSaverTileViewModel( + factory: QSTileViewModelFactory.Static<DataSaverTileModel>, + mapper: DataSaverTileMapper, + stateInteractor: DataSaverTileDataInteractor, + userActionInteractor: DataSaverTileUserActionInteractor + ): QSTileViewModel = + factory.create( + TileSpec.create(DATA_SAVER_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) } } 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/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java index 4e77801af515..97fc35a062f4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java @@ -47,7 +47,6 @@ import com.android.systemui.Dumpable; import com.android.systemui.biometrics.AuthController; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -180,8 +179,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp private final SystemClock mSystemClock; private final boolean mOrderUnlockAndWake; private final Lazy<SelectedUserInteractor> mSelectedUserInteractor; - private final DeviceEntryHapticsInteractor mHapticsInteractor; - private long mLastFpFailureUptimeMillis; private int mNumConsecutiveFpFailures; @@ -288,7 +285,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp ScreenOffAnimationController screenOffAnimationController, VibratorHelper vibrator, SystemClock systemClock, - DeviceEntryHapticsInteractor hapticsInteractor, Lazy<SelectedUserInteractor> selectedUserInteractor, BiometricUnlockInteractor biometricUnlockInteractor ) { @@ -320,7 +316,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp mSystemClock = systemClock; mOrderUnlockAndWake = resources.getBoolean( com.android.internal.R.bool.config_orderUnlockAndWake); - mHapticsInteractor = hapticsInteractor; mSelectedUserInteractor = selectedUserInteractor; dumpManager.registerDumpable(this); @@ -442,7 +437,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp if (mode == MODE_WAKE_AND_UNLOCK || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) { - mHapticsInteractor.vibrateSuccess(); onBiometricUnlockedWithKeyguardDismissal(biometricSourceType); } startWakeAndUnlock(mode); @@ -726,14 +720,6 @@ public class BiometricUnlockController extends KeyguardUpdateMonitorCallback imp } } - // Suppress all face auth errors if fingerprint can be used to authenticate - if ((biometricSourceType == BiometricSourceType.FACE - && !mUpdateMonitor.isUnlockWithFingerprintPossible( - mSelectedUserInteractor.get().getSelectedUserId())) - || (biometricSourceType == BiometricSourceType.FINGERPRINT)) { - mHapticsInteractor.vibrateError(); - } - cleanup(); } 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/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java index 80c68023b4c8..756c440eca89 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java @@ -484,7 +484,12 @@ public class KeyguardStateControllerImpl implements KeyguardStateController, Dum } @Override - public void onBiometricsCleared() { + public void onFingerprintsCleared() { + update(false /* alwaysUpdate */); + } + + @Override + public void onFacesCleared() { update(false /* alwaysUpdate */); } 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/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt index 968981197b83..50515daedc51 100644 --- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt +++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt @@ -68,6 +68,7 @@ class UnfoldTransitionModule { @Provides @UnfoldBgProgressFlag + @Singleton fun unfoldBgProgressFlag() = Flags.unfoldAnimationBackgroundProgress() /** A globally available FoldStateListener that allows one to query the fold state. */ 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/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java index 95e21cf6138a..92b06ba3f714 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java @@ -20,6 +20,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_CAPAB import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_ALL; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; +import static android.view.WindowInsets.Type.systemBars; import static com.google.common.truth.Truth.assertThat; @@ -42,6 +43,7 @@ import android.annotation.IdRes; import android.content.Context; import android.content.pm.ActivityInfo; import android.database.ContentObserver; +import android.graphics.Insets; import android.graphics.Rect; import android.os.UserHandle; import android.provider.Settings; @@ -49,6 +51,7 @@ import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; import android.widget.Button; @@ -59,10 +62,10 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import com.android.internal.graphics.SfVsyncFrameCallbackProvider; -import com.android.systemui.res.R; import com.android.systemui.SysuiTestCase; import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView; import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener; +import com.android.systemui.res.R; import com.android.systemui.util.settings.SecureSettings; import org.junit.After; @@ -314,6 +317,42 @@ public class WindowMagnificationSettingsTest extends SysuiTestCase { } @Test + public void onWindowBoundsChanged_updateDraggableWindowBounds() { + setupMagnificationCapabilityAndMode( + /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL, + /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW); + mWindowMagnificationSettings.showSettingPanel(); + + // get the measured panel view frame size + final int panelWidth = mSettingView.getMeasuredWidth(); + final int panelHeight = mSettingView.getMeasuredHeight(); + + final Rect testWindowBounds = new Rect(10, 20, 1010, 2020); + final WindowInsets testWindowInsets = new WindowInsets.Builder() + .setInsetsIgnoringVisibility(systemBars(), Insets.of(100, 200, 100, 200)) + .build(); + mWindowManager.setWindowBounds(testWindowBounds); + mWindowManager.setWindowInsets(testWindowInsets); + + InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { + mWindowMagnificationSettings.onConfigurationChanged(ActivityInfo.CONFIG_SCREEN_SIZE); + }); + + // the draggable window bounds left/top should be only related to the insets, + // and the bounds right/bottom should consider the panel frame size + // inset left (100) = 100 + int expectedLeft = 100; + // inset top (200) = 200 + int expectedTop = 200; + // window width (1010 - 10) - inset right (100) - panel width + int expectedRight = 900 - panelWidth; + // window height (2020 - 20) - inset bottom (200) - panel height + int expectedBottom = 1800 - panelHeight; + Rect expectedBounds = new Rect(expectedLeft, expectedTop, expectedRight, expectedBottom); + assertThat(mWindowMagnificationSettings.mDraggableWindowBounds).isEqualTo(expectedBounds); + } + + @Test public void onScreenSizeChanged_resetPositionToRightBottomCorner() { setupMagnificationCapabilityAndMode( /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_ALL, diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java index 2a1cfd199f0c..215f93d1e163 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationControllerTest.java @@ -17,7 +17,6 @@ package com.android.systemui.accessibility.floatingmenu; import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Mockito.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; @@ -26,9 +25,7 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; import android.graphics.PointF; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.View; @@ -74,10 +71,6 @@ public class MenuAnimationControllerTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Mock private AccessibilityManager mAccessibilityManager; @@ -233,7 +226,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK) + @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK) public void tuck_animates() { mMenuAnimationController.cancelAnimations(); mMenuAnimationController.moveToEdgeAndHide(); @@ -242,7 +235,7 @@ public class MenuAnimationControllerTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK) + @EnableFlags(Flags.FLAG_FLOATING_MENU_ANIMATED_TUCK) public void untuck_animates() { mMenuAnimationController.cancelAnimations(); mMenuAnimationController.moveOutEdgeAndShow(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java index 0f1364d951e7..be6f3ff8d3f9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -21,11 +21,8 @@ import static android.view.View.VISIBLE; import static android.view.WindowInsets.Type.displayCutout; import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.systemBars; - import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -41,10 +38,8 @@ import android.graphics.PointF; import android.graphics.Rect; import android.os.Build; import android.os.UserHandle; -import android.platform.test.annotations.RequiresFlagsDisabled; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -103,10 +98,6 @@ public class MenuViewLayerTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Mock private IAccessibilityFloatingMenu mFloatingMenu; @@ -230,7 +221,7 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) + @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme_old() { mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); final PointF beforePosition = mMenuView.getMenuPosition(); @@ -243,7 +234,7 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) + @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() { mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 100)); final PointF beforePosition = mMenuView.getMenuPosition(); @@ -259,7 +250,7 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test - @RequiresFlagsDisabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) + @DisableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition_old() { mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200)); final PointF beforePosition = mMenuView.getMenuPosition(); @@ -271,7 +262,7 @@ public class MenuViewLayerTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) + @EnableFlags(Flags.FLAG_FLOATING_MENU_IME_DISPLACEMENT_ANIMATION) public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() { mMenuAnimationController.moveAndPersistPosition(new PointF(0, IME_TOP + 200)); final PointF beforePosition = mMenuView.getMenuPosition(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java index 8f0a97ca06ba..8da6cf98d76f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -17,9 +17,7 @@ package com.android.systemui.accessibility.floatingmenu; import static android.app.UiModeManager.MODE_NIGHT_YES; - import static com.google.common.truth.Truth.assertThat; - import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -27,9 +25,7 @@ import static org.mockito.Mockito.verify; import android.app.UiModeManager; import android.graphics.Rect; import android.graphics.drawable.GradientDrawable; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.view.WindowManager; @@ -66,10 +62,6 @@ public class MenuViewTest extends SysuiTestCase { @Rule public MockitoRule mockito = MockitoJUnit.rule(); - @Rule - public final CheckFlagsRule mCheckFlagsRule = - DeviceFlagsValueProvider.createCheckFlagsRule(); - @Mock private AccessibilityManager mAccessibilityManager; @@ -147,7 +139,7 @@ public class MenuViewTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION) + @EnableFlags(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION) public void onEdgeChanged_startsRadiiAnimation() { final RadiiAnimator radiiAnimator = getRadiiAnimator(); mMenuView.onEdgeChanged(); @@ -155,7 +147,7 @@ public class MenuViewTest extends SysuiTestCase { } @Test - @RequiresFlagsEnabled(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION) + @EnableFlags(Flags.FLAG_FLOATING_MENU_RADII_ANIMATION) public void onDraggingStart_startsRadiiAnimation() { final RadiiAnimator radiiAnimator = getRadiiAnimator(); mMenuView.onDraggingStart(); 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/bouncer/domain/interactor/BouncerMessageInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt index 094616f0682c..aa0d7b621793 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/BouncerMessageInteractorTest.kt @@ -35,7 +35,7 @@ import com.android.systemui.bouncer.shared.model.BouncerMessageModel import com.android.systemui.bouncer.ui.BouncerView import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.flags.FakeFeatureFlags +import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.DismissCallbackRegistry @@ -62,7 +62,6 @@ import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.mock import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -108,17 +107,18 @@ class BouncerMessageInteractorTest : SysuiTestCase() { suspend fun TestScope.init() { userRepository.setSelectedUserInfo(PRIMARY_USER) - val featureFlags = FakeFeatureFlags().apply { set(Flags.REVAMPED_BOUNCER_MESSAGES, true) } + val featureFlags = + FakeFeatureFlagsClassic().apply { set(Flags.REVAMPED_BOUNCER_MESSAGES, true) } primaryBouncerInteractor = PrimaryBouncerInteractor( bouncerRepository, - Mockito.mock(BouncerView::class.java), - Mockito.mock(Handler::class.java), - Mockito.mock(KeyguardStateController::class.java), - Mockito.mock(KeyguardSecurityModel::class.java), - Mockito.mock(PrimaryBouncerCallbackInteractor::class.java), - Mockito.mock(FalsingCollector::class.java), - Mockito.mock(DismissCallbackRegistry::class.java), + mock(BouncerView::class.java), + mock(Handler::class.java), + mock(KeyguardStateController::class.java), + mock(KeyguardSecurityModel::class.java), + mock(PrimaryBouncerCallbackInteractor::class.java), + mock(FalsingCollector::class.java), + mock(DismissCallbackRegistry::class.java), context, keyguardUpdateMonitor, fakeTrustRepository, diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt index 395d7129bee3..ca9582240b93 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/helper/BouncerSceneLayoutTest.kt @@ -18,10 +18,10 @@ package com.android.systemui.bouncer.ui.helper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SIDE_BY_SIDE -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STACKED -import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BELOW_USER_SWITCHER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.BESIDE_USER_SWITCHER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.SPLIT_BOUNCER +import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout.STANDARD_BOUNCER import com.google.common.truth.Truth.assertThat import java.util.Locale import org.junit.Test @@ -84,33 +84,33 @@ class BouncerSceneLayoutTest : SysuiTestCase() { listOf( Phone to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), Tablet to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = STACKED, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = BELOW_USER_SWITCHER, ), Folded to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), Unfolded to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = STANDARD, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = STANDARD_BOUNCER, ), TallerFolded to Expected( - whenNaturallyHeld = STANDARD, - whenUnnaturallyHeld = SPLIT, + whenNaturallyHeld = STANDARD_BOUNCER, + whenUnnaturallyHeld = SPLIT_BOUNCER, ), TallerUnfolded to Expected( - whenNaturallyHeld = SIDE_BY_SIDE, - whenUnnaturallyHeld = SIDE_BY_SIDE, + whenNaturallyHeld = BESIDE_USER_SWITCHER, + whenUnnaturallyHeld = BESIDE_USER_SWITCHER, ), ) .flatMap { (device, expected) -> @@ -124,13 +124,13 @@ class BouncerSceneLayoutTest : SysuiTestCase() { ) ) - if (expected.whenNaturallyHeld == SIDE_BY_SIDE) { + if (expected.whenNaturallyHeld == BESIDE_USER_SWITCHER) { add( TestCase( device = device, held = device.naturallyHeld, isSideBySideSupported = false, - expected = STANDARD, + expected = STANDARD_BOUNCER, ) ) } @@ -144,13 +144,13 @@ class BouncerSceneLayoutTest : SysuiTestCase() { ) ) - if (expected.whenUnnaturallyHeld == SIDE_BY_SIDE) { + if (expected.whenUnnaturallyHeld == BESIDE_USER_SWITCHER) { add( TestCase( device = device, held = device.naturallyHeld.flip(), isSideBySideSupported = false, - expected = STANDARD, + expected = STANDARD_BOUNCER, ) ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt index 9b8e581d1ba4..59bcf01e0eef 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt @@ -18,157 +18,165 @@ package com.android.systemui.deviceentry.data.repository import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.keyguard.logging.BiometricUnlockLogger import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository +import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.biometrics.shared.model.FingerprintSensorType import com.android.systemui.biometrics.shared.model.SensorStrength -import com.android.systemui.classifier.FalsingCollector import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor -import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository -import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor -import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.power.data.repository.FakePowerRepository -import com.android.systemui.power.domain.interactor.PowerInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor +import com.android.systemui.keyevent.data.repository.fakeKeyEventRepository +import com.android.systemui.keyguard.data.repository.biometricSettingsRepository +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource +import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.FailedFaceAuthenticationStatus +import com.android.systemui.kosmos.testScope +import com.android.systemui.power.data.repository.powerRepository import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState -import com.android.systemui.statusbar.phone.ScreenOffAnimationController -import com.android.systemui.util.time.FakeSystemClock +import com.android.systemui.testKosmos +import com.android.systemui.util.time.fakeSystemClock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi 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.Mockito.mock @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class DeviceEntryHapticsInteractorTest : SysuiTestCase() { - - private lateinit var repository: DeviceEntryHapticsRepository - private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository - private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository - private lateinit var keyEventRepository: FakeKeyEventRepository - private lateinit var powerRepository: FakePowerRepository - private lateinit var systemClock: FakeSystemClock - private lateinit var underTest: DeviceEntryHapticsInteractor - - @Before - fun setUp() { - repository = DeviceEntryHapticsRepositoryImpl() - fingerprintPropertyRepository = FakeFingerprintPropertyRepository() - biometricSettingsRepository = FakeBiometricSettingsRepository() - keyEventRepository = FakeKeyEventRepository() - powerRepository = FakePowerRepository() - systemClock = FakeSystemClock() - underTest = - DeviceEntryHapticsInteractor( - repository = repository, - fingerprintPropertyRepository = fingerprintPropertyRepository, - biometricSettingsRepository = biometricSettingsRepository, - keyEventInteractor = KeyEventInteractor(keyEventRepository), - powerInteractor = - PowerInteractor( - powerRepository, - mock(FalsingCollector::class.java), - mock(ScreenOffAnimationController::class.java), - mock(StatusBarStateController::class.java), - ), - systemClock = systemClock, - logger = mock(BiometricUnlockLogger::class.java), - ) - } + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val underTest = kosmos.deviceEntryHapticsInteractor @Test - fun nonPowerButtonFPS_vibrateSuccess() = runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) - setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) - underTest.vibrateSuccess() - assertThat(playSuccessHaptic).isTrue() - } + fun nonPowerButtonFPS_vibrateSuccess() = + testScope.runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + runCurrent() + enterDeviceFromBiometricUnlock() + assertThat(playSuccessHaptic).isNotNull() + } @Test - fun powerButtonFPS_vibrateSuccess() = runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() - keyEventRepository.setPowerButtonDown(false) - - // It's been 10 seconds since the last power button wakeup - setAwakeFromPowerButton() - runCurrent() - systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) - - underTest.vibrateSuccess() - assertThat(playSuccessHaptic).isTrue() - } + fun powerButtonFPS_vibrateSuccess() = + testScope.runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + kosmos.fakeKeyEventRepository.setPowerButtonDown(false) + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000) + + enterDeviceFromBiometricUnlock() + assertThat(playSuccessHaptic).isNotNull() + } @Test - fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() - keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN - - // It's been 10 seconds since the last power button wakeup - setAwakeFromPowerButton() - runCurrent() - systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000) + fun powerButtonFPS_powerDown_doNotVibrateSuccess() = + testScope.runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + kosmos.fakeKeyEventRepository.setPowerButtonDown(true) // power button is currently DOWN + + // It's been 10 seconds since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 10000) + + enterDeviceFromBiometricUnlock() + assertThat(playSuccessHaptic).isNull() + } - underTest.vibrateSuccess() - assertThat(playSuccessHaptic).isFalse() - } + @Test + fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = + testScope.runTest { + val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + kosmos.fakeKeyEventRepository.setPowerButtonDown(false) + + // It's only been 50ms since the last power button wakeup + setAwakeFromPowerButton() + runCurrent() + kosmos.fakeSystemClock.setUptimeMillis(kosmos.fakeSystemClock.uptimeMillis() + 50) + + enterDeviceFromBiometricUnlock() + assertThat(playSuccessHaptic).isNull() + } @Test - fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest { - val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() - keyEventRepository.setPowerButtonDown(false) + fun nonPowerButtonFPS_vibrateError() = + testScope.runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + runCurrent() + fingerprintFailure() + assertThat(playErrorHaptic).isNotNull() + } - // It's only been 50ms since the last power button wakeup - setAwakeFromPowerButton() - runCurrent() - systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50) + @Test + fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() = + testScope.runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) + coExEnrolledAndEnabled() + runCurrent() + faceFailure() + assertThat(playErrorHaptic).isNull() + } - underTest.vibrateSuccess() - assertThat(playSuccessHaptic).isFalse() - } + @Test + fun powerButtonFPS_vibrateError() = + testScope.runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + runCurrent() + fingerprintFailure() + assertThat(playErrorHaptic).isNotNull() + } @Test - fun nonPowerButtonFPS_vibrateError() = runTest { - val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) - setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC) - underTest.vibrateError() - assertThat(playErrorHaptic).isTrue() + fun powerButtonFPS_powerDown_doNotVibrateError() = + testScope.runTest { + val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) + setPowerButtonFingerprintProperty() + setFingerprintEnrolled() + kosmos.fakeKeyEventRepository.setPowerButtonDown(true) + runCurrent() + fingerprintFailure() + assertThat(playErrorHaptic).isNull() + } + + private suspend fun enterDeviceFromBiometricUnlock() { + kosmos.fakeDeviceEntryRepository.enteringDeviceFromBiometricUnlock( + BiometricUnlockSource.FINGERPRINT_SENSOR + ) } - @Test - fun powerButtonFPS_vibrateError() = runTest { - val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() - underTest.vibrateError() - assertThat(playErrorHaptic).isTrue() + private fun fingerprintFailure() { + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + FailFingerprintAuthenticationStatus + ) } - @Test - fun powerButtonFPS_powerDown_doNotVibrateError() = runTest { - val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) - setPowerButtonFingerprintProperty() - setFingerprintEnrolled() - keyEventRepository.setPowerButtonDown(true) - underTest.vibrateError() - assertThat(playErrorHaptic).isFalse() + private fun faceFailure() { + kosmos.fakeDeviceEntryFaceAuthRepository.setAuthenticationStatus( + FailedFaceAuthenticationStatus() + ) } private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) { - fingerprintPropertyRepository.setProperties( + kosmos.fingerprintPropertyRepository.setProperties( sensorId = 0, strength = SensorStrength.STRONG, sensorType = fingerprintSensorType, @@ -181,15 +189,20 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { } private fun setFingerprintEnrolled() { - biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) + kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) } private fun setAwakeFromPowerButton() { - powerRepository.updateWakefulness( + kosmos.powerRepository.updateWakefulness( WakefulnessState.AWAKE, WakeSleepReason.POWER_BUTTON, WakeSleepReason.POWER_BUTTON, powerButtonLaunchGestureTriggered = false, ) } + + private fun coExEnrolledAndEnabled() { + setFingerprintEnrolled() + kosmos.biometricSettingsRepository.setIsFaceAuthEnrolledAndEnabled(true) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt index 42b0f5097cad..37c740968261 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt @@ -18,6 +18,7 @@ package com.android.systemui.display.domain.interactor import android.companion.virtual.VirtualDeviceManager import android.companion.virtual.flags.Flags.FLAG_INTERACTIVE_SCREEN_MIRROR +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper import android.view.Display @@ -75,7 +76,6 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() { @Before fun setup() { - mSetFlagsRule.disableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR) whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt())).thenReturn(false) fakeKeyguardRepository.setKeyguardShowing(false) } @@ -160,9 +160,9 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR) fun displayState_virtualDeviceOwnedMirrorVirtualDisplay_connected() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR) whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt())) .thenReturn(true) val value by lastValue() @@ -183,9 +183,9 @@ class ConnectedDisplayInteractorTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR) fun virtualDeviceOwnedMirrorVirtualDisplay_emitsConnectedDisplayAddition() = testScope.runTest { - mSetFlagsRule.enableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR) whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt())) .thenReturn(true) var count = 0 diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt index a903d257d05f..523127e08a20 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsClassicDebugTest.kt @@ -21,6 +21,8 @@ import android.content.Intent import android.content.pm.PackageManager.NameNotFoundException import android.content.res.Resources import android.content.res.Resources.NotFoundException +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.test.suitebuilder.annotation.SmallTest import com.android.systemui.Flags.FLAG_SYSUI_TEAMFOOD import com.android.systemui.SysuiTestCase @@ -68,15 +70,14 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { private val serverFlagReader = ServerFlagReaderFake() private val teamfoodableFlagA = UnreleasedFlag(name = "a", namespace = "test", teamfood = true) - private val teamfoodableFlagB = ReleasedFlag(name = "b", namespace = "test", teamfood = true) + private val releasedFlagB = ReleasedFlag(name = "b", namespace = "test") @Before fun setup() { MockitoAnnotations.initMocks(this) - mSetFlagsRule.disableFlags(FLAG_SYSUI_TEAMFOOD) flagMap.put(teamfoodableFlagA.name, teamfoodableFlagA) - flagMap.put(teamfoodableFlagB.name, teamfoodableFlagB) + flagMap.put(releasedFlagB.name, releasedFlagB) mFeatureFlagsClassicDebug = FeatureFlagsClassicDebug( flagManager, @@ -99,7 +100,6 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { @Test fun readBooleanFlag() { - // Remember that the TEAMFOOD flag is id#1 and has special behavior. whenever(flagManager.readFlagValue<Boolean>(eq("3"), any())).thenReturn(true) whenever(flagManager.readFlagValue<Boolean>(eq("4"), any())).thenReturn(false) @@ -122,9 +122,10 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SYSUI_TEAMFOOD) fun teamFoodFlag_False() { assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isFalse() - assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue() + assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isTrue() // Regular boolean flags should still test the same. // Only our teamfoodableFlag should change. @@ -132,10 +133,10 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SYSUI_TEAMFOOD) fun teamFoodFlag_True() { - mSetFlagsRule.enableFlags(FLAG_SYSUI_TEAMFOOD) assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue() - assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isTrue() + assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isTrue() // Regular boolean flags should still test the same. // Only our teamfoodableFlag should change. @@ -143,14 +144,14 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { } @Test + @EnableFlags(FLAG_SYSUI_TEAMFOOD) fun teamFoodFlag_Overridden() { whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagA.name), any())) .thenReturn(true) - whenever(flagManager.readFlagValue<Boolean>(eq(teamfoodableFlagB.name), any())) + whenever(flagManager.readFlagValue<Boolean>(eq(releasedFlagB.name), any())) .thenReturn(false) - mSetFlagsRule.enableFlags(FLAG_SYSUI_TEAMFOOD) assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA)).isTrue() - assertThat(mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagB)).isFalse() + assertThat(mFeatureFlagsClassicDebug.isEnabled(releasedFlagB)).isFalse() // Regular boolean flags should still test the same. // Only our teamfoodableFlag should change. @@ -400,6 +401,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SYSUI_TEAMFOOD) fun serverSide_OverrideUncached_NoRestart() { // No one has read the flag, so it's not in the cache. serverFlagReader.setFlagValue( @@ -411,6 +413,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SYSUI_TEAMFOOD) fun serverSide_Override_Restarts() { // Read it to put it in the cache. mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA) @@ -423,6 +426,7 @@ class FeatureFlagsClassicDebugTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SYSUI_TEAMFOOD) fun serverSide_RedundantOverride_NoRestart() { // Read it to put it in the cache. mFeatureFlagsClassicDebug.isEnabled(teamfoodableFlagA) 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/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java index d246f0e49e1c..ae5f625b1c8d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java @@ -97,6 +97,7 @@ import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransition import com.android.systemui.log.SessionTracker; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.scene.FakeWindowRootViewComponent; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.ui.view.WindowRootView; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.NotificationShadeWindowControllerImpl; @@ -214,6 +215,7 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { private @Mock CoroutineDispatcher mDispatcher; private @Mock DreamingToLockscreenTransitionViewModel mDreamingToLockscreenTransitionViewModel; private @Mock SystemPropertiesHelper mSystemPropertiesHelper; + private @Mock SceneContainerFlags mSceneContainerFlags; private FakeFeatureFlags mFeatureFlags; private final int mDefaultUserId = 100; @@ -258,7 +260,8 @@ public class KeyguardViewMediatorTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker); + mUserTracker, + mSceneContainerFlags); mFeatureFlags = new FakeFeatureFlags(); mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false); mFeatureFlags.set(Flags.REFACTOR_GETCURRENTUSER, true); diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt index de12b8f91d20..4ab8e28bc232 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt @@ -17,8 +17,10 @@ package com.android.systemui.keyguard.domain.interactor +import android.app.trust.TrustManager import android.content.pm.UserInfo import android.hardware.biometrics.BiometricFaceConstants +import android.hardware.biometrics.BiometricSourceType import android.os.Handler import android.os.PowerManager import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -62,6 +64,7 @@ import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.domain.interactor.SelectedUserInteractor +import com.android.systemui.util.mockito.eq import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat @@ -74,8 +77,11 @@ import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.anyInt import org.mockito.Mock import org.mockito.Mockito.mock +import org.mockito.Mockito.never +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @OptIn(ExperimentalCoroutinesApi::class) @@ -99,7 +105,8 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var faceWakeUpTriggersConfig: FaceWakeUpTriggersConfig - @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor + @Mock private lateinit var trustManager: TrustManager @Before fun setup() { @@ -146,7 +153,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { keyguardUpdateMonitor, FakeTrustRepository(), testScope.backgroundScope, - mSelectedUserInteractor, + selectedUserInteractor, underTest, ) }, @@ -169,6 +176,7 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { faceWakeUpTriggersConfig, powerInteractor, fakeBiometricSettingsRepository, + trustManager, ) } @@ -498,6 +506,22 @@ class KeyguardFaceAuthInteractorTest : SysuiTestCase() { assertThat(faceAuthRepository.isLockedOut.value).isTrue() } + @Test + fun whenIsAuthenticatedFalse_clearFaceBiometrics() = + testScope.runTest { + underTest.start() + + faceAuthRepository.isAuthenticated.value = true + runCurrent() + verify(trustManager, never()) + .clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt()) + + faceAuthRepository.isAuthenticated.value = false + runCurrent() + + verify(trustManager).clearAllBiometricRecognized(eq(BiometricSourceType.FACE), anyInt()) + } + companion object { private const val primaryUserId = 1 private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY) 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..b8a8bdf06954 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 @@ -1138,7 +1137,7 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { runCurrent() // WHEN primary bouncer shows - bouncerRepository.setPrimaryShow(true) // beverlyt + bouncerRepository.setPrimaryShow(true) runCurrent() val info = @@ -1233,6 +1232,36 @@ class KeyguardTransitionScenariosTest : SysuiTestCase() { } @Test + fun dreamingToAod() = + testScope.runTest { + // GIVEN a prior transition has run to DREAMING + keyguardRepository.setDreaming(true) + runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.DREAMING) + runCurrent() + + // WHEN the device starts DOZE_AOD + keyguardRepository.setDozeTransitionModel( + DozeTransitionModel( + from = DozeStateModel.INITIALIZED, + to = DozeStateModel.DOZE_AOD, + ) + ) + runCurrent() + + val info = + withArgCaptor<TransitionInfo> { + verify(transitionRepository).startTransition(capture()) + } + // THEN a transition to AOD should occur + assertThat(info.ownerName).isEqualTo("FromDreamingTransitionInteractor") + assertThat(info.from).isEqualTo(KeyguardState.DREAMING) + assertThat(info.to).isEqualTo(KeyguardState.AOD) + assertThat(info.animator).isNotNull() + + coroutineContext.cancelChildren() + } + + @Test fun lockscreenToOccluded() = testScope.runTest { // GIVEN a prior transition has run to LOCKSCREEN @@ -1329,12 +1358,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 +1375,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/view/layout/sections/DefaultDeviceEntrySectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt index 67fba42aac5b..22569e27d02a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultDeviceEntrySectionTest.kt @@ -39,6 +39,7 @@ import com.android.systemui.plugins.FalsingManager import com.android.systemui.res.R import com.android.systemui.shade.NotificationPanelView import com.android.systemui.statusbar.NotificationShadeWindowController +import com.android.systemui.statusbar.VibratorHelper import com.android.systemui.statusbar.gesture.TapGestureDetector import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -91,6 +92,7 @@ class DefaultDeviceEntrySectionTest : SysuiTestCase() { TestScope().backgroundScope, { mock(SwipeUpAnywhereGestureHandler::class.java) }, { mock(TapGestureDetector::class.java) }, + { mock(VibratorHelper::class.java) }, ) } 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/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java index 5245b224fa5b..5e2423a8b373 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java @@ -18,6 +18,7 @@ package com.android.systemui.qs; import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE; +import static com.android.systemui.Flags.FLAG_QS_NEW_TILES; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; @@ -53,7 +54,6 @@ import com.android.systemui.SysuiTestCase; import com.android.systemui.classifier.FalsingManagerFake; import com.android.systemui.dump.nano.SystemUIProtoDump; import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.flags.Flags; import com.android.systemui.plugins.ActivityStarter; import com.android.systemui.plugins.PluginManager; import com.android.systemui.plugins.qs.QSFactory; @@ -79,6 +79,8 @@ import com.android.systemui.util.settings.FakeSettings; import com.android.systemui.util.settings.SecureSettings; import com.android.systemui.util.time.FakeSystemClock; +import dagger.Lazy; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -93,8 +95,6 @@ import java.util.concurrent.Executor; import javax.inject.Provider; -import dagger.Lazy; - @RunWith(AndroidTestingRunner.class) @SmallTest public class QSTileHostTest extends SysuiTestCase { @@ -147,9 +147,8 @@ public class QSTileHostTest extends SysuiTestCase { mFeatureFlags = new FakeFeatureFlags(); mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE); - // TODO(b/299909337): Add test checking the new factory is used when the flag is on - mFeatureFlags.set(Flags.QS_PIPELINE_NEW_TILES, false); - mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(mFeatureFlags); + mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES); + mQSPipelineFlagsRepository = new QSPipelineFlagsRepository(); mMainExecutor = new FakeExecutor(new FakeSystemClock()); @@ -704,7 +703,7 @@ public class QSTileHostTest extends SysuiTestCase { TileLifecycleManager.Factory tileLifecycleManagerFactory, UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) { super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager, - tunerService, autoTiles, shadeController, qsLogger, + tunerService, autoTiles, shadeController, qsLogger, userTracker, secureSettings, customTileStatePersister, tileLifecycleManagerFactory, userFileManager, featureFlags); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt index 2e637084e6dc..970cd17a731a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt @@ -4,7 +4,6 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.Flags import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FakeFeatureFlagsClassic import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith @@ -13,9 +12,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class QSPipelineFlagsRepositoryTest : SysuiTestCase() { - private val fakeFeatureFlagsClassic = FakeFeatureFlagsClassic() - - private val underTest = QSPipelineFlagsRepository(fakeFeatureFlagsClassic) + private val underTest = QSPipelineFlagsRepository() @Test fun pipelineFlagDisabled() { @@ -30,4 +27,18 @@ class QSPipelineFlagsRepositoryTest : SysuiTestCase() { assertThat(underTest.pipelineEnabled).isTrue() } + + @Test + fun tilesFlagDisabled() { + mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_TILES) + + assertThat(underTest.tilesEnabled).isFalse() + } + + @Test + fun tilesFlagEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_TILES) + + assertThat(underTest.tilesEnabled).isTrue() + } } 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/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java index 39739e7bb93d..5ffbe65d2c50 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java @@ -75,6 +75,7 @@ import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.UserTracker; import com.android.systemui.shade.data.repository.FakeShadeRepository; @@ -138,6 +139,7 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { @Mock private ShadeWindowLogger mShadeWindowLogger; @Mock private SelectedUserInteractor mSelectedUserInteractor; @Mock private UserTracker mUserTracker; + @Mock private SceneContainerFlags mSceneContainerFlags; @Captor private ArgumentCaptor<WindowManager.LayoutParams> mLayoutParameters; @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListener; @@ -274,7 +276,8 @@ public class NotificationShadeWindowControllerImplTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker) { + mUserTracker, + mSceneContainerFlags) { @Override protected boolean isDebuggable() { return false; 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/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java index 8bc5e70d09f6..34c7b09ba86a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java @@ -55,8 +55,10 @@ import android.net.Uri; import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.provider.Settings; -import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.util.SparseArray; @@ -96,12 +98,26 @@ import org.mockito.MockitoAnnotations; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.concurrent.Executor; +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(ParameterizedAndroidJunit4.class) @TestableLooper.RunWithLooper public class NotificationLockscreenUserManagerTest extends SysuiTestCase { + + @Parameters(name = "{0}") + public static List<FlagsParameterization> getParams() { + return FlagsParameterization.allCombinationsOf(FLAG_ALLOW_PRIVATE_PROFILE); + } + + public NotificationLockscreenUserManagerTest(FlagsParameterization flags) { + mSetFlagsRule.setFlagsParameterization(flags); + } + private static final int TEST_PROFILE_USERHANDLE = 12; @Mock private NotificationPresenter mPresenter; @@ -762,8 +778,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE) public void testProfileAvailabilityIntent() { - mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); mLockscreenUserManager.mCurrentProfiles.clear(); assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); @@ -773,8 +789,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + @EnableFlags(FLAG_ALLOW_PRIVATE_PROFILE) public void testProfileUnAvailabilityIntent() { - mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE); mLockscreenUserManager.mCurrentProfiles.clear(); assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); mLockscreenUserManager.mCurrentProfiles.append(0, mock(UserInfo.class)); @@ -784,8 +800,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE) public void testManagedProfileAvailabilityIntent() { - mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE); mLockscreenUserManager.mCurrentProfiles.clear(); mLockscreenUserManager.mCurrentManagedProfiles.clear(); assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); @@ -798,8 +814,8 @@ public class NotificationLockscreenUserManagerTest extends SysuiTestCase { } @Test + @DisableFlags(FLAG_ALLOW_PRIVATE_PROFILE) public void testManagedProfileUnAvailabilityIntent() { - mSetFlagsRule.disableFlags(FLAG_ALLOW_PRIVATE_PROFILE); mLockscreenUserManager.mCurrentProfiles.clear(); mLockscreenUserManager.mCurrentManagedProfiles.clear(); assertEquals(0, mLockscreenUserManager.mCurrentProfiles.size()); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt index fa5fad06b671..255cf6fbed63 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt @@ -15,11 +15,12 @@ */ package com.android.systemui.statusbar.notification.collection.coordinator +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import android.testing.TestableLooper.RunWithLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.setFlagValue import com.android.systemui.statusbar.notification.collection.NotifPipeline import com.android.systemui.statusbar.notification.collection.NotificationEntry import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -40,10 +41,9 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito.reset import org.mockito.Mockito.verify -import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations.initMocks +import org.mockito.Mockito.`when` as whenever @SmallTest @RunWith(AndroidTestingRunner::class) @@ -66,12 +66,6 @@ class StackCoordinatorTest : SysuiTestCase() { fun setUp() { initMocks(this) entry = NotificationEntryBuilder().setSection(section).build() - setUpWithFlags() - } - - private fun setUpWithFlags(vararg flags: Pair<String, Boolean>) { - flags.forEach { (name, value) -> mSetFlagsRule.setFlagValue(name, value) } - reset(pipeline) coordinator = StackCoordinator( groupExpansionManagerImpl, @@ -86,15 +80,15 @@ class StackCoordinatorTest : SysuiTestCase() { } @Test + @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME) fun testUpdateNotificationIcons() { - setUpWithFlags(NotificationIconContainerRefactor.FLAG_NAME to false) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry))) } @Test + @EnableFlags(NotificationIconContainerRefactor.FLAG_NAME) fun testSetRenderedListOnInteractor() { - setUpWithFlags(NotificationIconContainerRefactor.FLAG_NAME to true) afterRenderListListener.onAfterRenderList(listOf(entry), stackController) verify(renderListInteractor).setRenderedList(eq(listOf(entry))) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java index 22c5bae93489..57dac3ac19a6 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/view/FooterViewTest.java @@ -16,12 +16,11 @@ package com.android.systemui.statusbar.notification.footer.ui.view; +import static com.android.systemui.log.LogAssertKt.assertLogsWtf; import static com.google.common.truth.Truth.assertThat; - import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; - import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -31,33 +30,47 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.content.Context; -import android.testing.AndroidTestingRunner; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.FlagsParameterization; import android.view.LayoutInflater; import android.view.View; import android.widget.TextView; import androidx.test.filters.SmallTest; -import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.res.R; +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import java.util.List; + +import platform.test.runner.parameterized.ParameterizedAndroidJunit4; +import platform.test.runner.parameterized.Parameters; + @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(ParameterizedAndroidJunit4.class) public class FooterViewTest extends SysuiTestCase { + @Parameters(name = "{0}") + public static List<FlagsParameterization> getFlags() { + return FlagsParameterization.allCombinationsOf(FooterViewRefactor.FLAG_NAME); + } + + public FooterViewTest(FlagsParameterization flags) { + mSetFlagsRule.setFlagsParameterization(flags); + } + FooterView mView; Context mSpyContext = spy(mContext); @Before public void setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR); - mView = (FooterView) LayoutInflater.from(mSpyContext).inflate( R.layout.status_bar_notification_footer, null, false); mView.setAnimationDuration(0); @@ -114,6 +127,7 @@ public class FooterViewTest extends SysuiTestCase { } @Test + @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetClearAllButtonText_resourceOnlyFetchedOnce() { int resId = R.string.clear_all_notifications_text; mView.setClearAllButtonText(resId); @@ -132,6 +146,16 @@ public class FooterViewTest extends SysuiTestCase { } @Test + @DisableFlags(FooterViewRefactor.FLAG_NAME) + public void testSetClearAllButtonText_expectsFlagEnabled() { + clearInvocations(mSpyContext); + int resId = R.string.clear_all_notifications_text; + assertLogsWtf(()-> mView.setClearAllButtonText(resId)); + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test + @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetClearAllButtonDescription_resourceOnlyFetchedOnce() { int resId = R.string.accessibility_clear_all; mView.setClearAllButtonDescription(resId); @@ -150,6 +174,16 @@ public class FooterViewTest extends SysuiTestCase { } @Test + @DisableFlags(FooterViewRefactor.FLAG_NAME) + public void testSetClearAllButtonDescription_expectsFlagEnabled() { + clearInvocations(mSpyContext); + int resId = R.string.accessibility_clear_all; + assertLogsWtf(()-> mView.setClearAllButtonDescription(resId)); + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test + @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetMessageString_resourceOnlyFetchedOnce() { int resId = R.string.unlock_to_see_notif_text; mView.setMessageString(resId); @@ -168,6 +202,16 @@ public class FooterViewTest extends SysuiTestCase { } @Test + @DisableFlags(FooterViewRefactor.FLAG_NAME) + public void testSetMessageString_expectsFlagEnabled() { + clearInvocations(mSpyContext); + int resId = R.string.unlock_to_see_notif_text; + assertLogsWtf(()-> mView.setMessageString(resId)); + verify(mSpyContext, never()).getString(anyInt()); + } + + @Test + @EnableFlags(FooterViewRefactor.FLAG_NAME) public void testSetMessageIcon_resourceOnlyFetchedOnce() { int resId = R.drawable.ic_friction_lock_closed; mView.setMessageIcon(resId); @@ -183,6 +227,15 @@ public class FooterViewTest extends SysuiTestCase { } @Test + @DisableFlags(FooterViewRefactor.FLAG_NAME) + public void testSetMessageIcon_expectsFlagEnabled() { + clearInvocations(mSpyContext); + int resId = R.drawable.ic_friction_lock_closed; + assertLogsWtf(()-> mView.setMessageIcon(resId)); + verify(mSpyContext, never()).getDrawable(anyInt()); + } + + @Test public void testSetFooterLabelVisible() { mView.setFooterLabelVisible(true); assertThat(mView.findViewById(R.id.manage_text).getVisibility()).isEqualTo(View.GONE); diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt index 0ba820f0972a..8ab13f5aa720 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/footer/ui/viewmodel/FooterViewModelTest.kt @@ -16,9 +16,9 @@ package com.android.systemui.statusbar.notification.footer.ui.viewmodel +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase @@ -38,6 +38,7 @@ import com.android.systemui.runTest import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule import com.android.systemui.statusbar.phone.DozeParameters import com.android.systemui.user.domain.interactor.HeadlessSystemUserModeModule @@ -55,6 +56,7 @@ import org.mockito.MockitoAnnotations @RunWith(AndroidTestingRunner::class) @SmallTest +@EnableFlags(FooterViewRefactor.FLAG_NAME) class FooterViewModelTest : SysuiTestCase() { private lateinit var footerViewModel: FooterViewModel @@ -106,8 +108,6 @@ class FooterViewModelTest : SysuiTestCase() { fun setUp() { MockitoAnnotations.initMocks(this) - mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) - // The underTest in the component is Optional, because that matches the provider we // currently have for the footer view model. footerViewModel = testComponent.underTest.get() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt index 7361f6baa7b6..7ed33126a54f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/NotificationInterruptStateProviderWrapperTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.interruption +import android.platform.test.annotations.DisableFlags import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder @@ -34,11 +35,8 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) +@DisableFlags(VisualInterruptionRefactor.FLAG_NAME) class NotificationInterruptStateProviderWrapperTest : VisualInterruptionDecisionProviderTestBase() { - init { - mSetFlagsRule.disableFlags(VisualInterruptionRefactor.FLAG_NAME) - } - override val provider by lazy { NotificationInterruptStateProviderWrapper( NotificationInterruptStateProviderImpl( diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt index d2c046c67fb0..da68d9c743ce 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/interruption/VisualInterruptionDecisionProviderImplTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.statusbar.notification.interruption +import android.platform.test.annotations.EnableFlags import android.testing.AndroidTestingRunner import androidx.test.filters.SmallTest import com.android.systemui.statusbar.notification.collection.NotificationEntry @@ -27,11 +28,8 @@ import org.junit.runner.RunWith @SmallTest @RunWith(AndroidTestingRunner::class) +@EnableFlags(VisualInterruptionRefactor.FLAG_NAME) class VisualInterruptionDecisionProviderImplTest : VisualInterruptionDecisionProviderTestBase() { - init { - mSetFlagsRule.enableFlags(VisualInterruptionRefactor.FLAG_NAME) - } - override val provider by lazy { VisualInterruptionDecisionProviderImpl( ambientDisplayConfiguration, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index f00abc9c3f63..21774aa2f8d7 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -19,10 +19,10 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.app.NotificationManager.Policy +import android.platform.test.annotations.EnableFlags import android.provider.Settings import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase @@ -41,6 +41,7 @@ import com.android.systemui.runTest import com.android.systemui.shade.data.repository.FakeShadeRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.setActiveNotifs +import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor import com.android.systemui.statusbar.notification.footer.ui.viewmodel.FooterViewModelModule import com.android.systemui.statusbar.notification.row.ui.viewmodel.ActivatableNotificationViewModelModule import com.android.systemui.statusbar.policy.FakeConfigurationController @@ -59,6 +60,7 @@ import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidJUnit4::class) +@EnableFlags(FooterViewRefactor.FLAG_NAME) class NotificationListViewModelTest : SysuiTestCase() { @SysUISingleton @@ -104,8 +106,6 @@ class NotificationListViewModelTest : SysuiTestCase() { @Before fun setUp() { MockitoAnnotations.initMocks(this) - - mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_FOOTER_VIEW_REFACTOR) } @Test 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/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java index 051a4c1c05cd..6f65eb426e64 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java @@ -47,7 +47,6 @@ import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.logging.BiometricUnlockLogger; import com.android.systemui.SysuiTestCase; import com.android.systemui.biometrics.AuthController; -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.KeyguardViewMediator; import com.android.systemui.keyguard.WakefulnessLifecycle; @@ -122,8 +121,6 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { @Mock private ViewRootImpl mViewRootImpl; @Mock - private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor; - @Mock private SelectedUserInteractor mSelectedUserInteractor; @Mock private BiometricUnlockInteractor mBiometricUnlockInteractor; @@ -160,7 +157,6 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { mAuthController, mStatusBarStateController, mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper, mSystemClock, - mDeviceEntryHapticsInteractor, () -> mSelectedUserInteractor, mBiometricUnlockInteractor ); @@ -466,26 +462,6 @@ public class BiometricsUnlockControllerTest extends SysuiTestCase { } @Test - public void onFingerprintSuccess_requestSuccessHaptic() { - // WHEN biometric fingerprint succeeds - givenFingerprintModeUnlockCollapsing(); - mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT, - true); - - // THEN always vibrate the device - verify(mDeviceEntryHapticsInteractor).vibrateSuccess(); - } - - @Test - public void onFingerprintFail_requestErrorHaptic() { - // WHEN biometric fingerprint fails - mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT); - - // THEN always vibrate the device - verify(mDeviceEntryHapticsInteractor).vibrateError(); - } - - @Test public void onFingerprintDetect_showBouncer() { // WHEN fingerprint detect occurs mBiometricUnlockController.onBiometricDetected(UserHandle.USER_CURRENT, 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/LegacyNotificationIconAreaControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java index c24d9adf1dbd..b3708bad6917 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LegacyNotificationIconAreaControllerImplTest.java @@ -20,6 +20,7 @@ import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.platform.test.annotations.DisableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; @@ -49,6 +50,7 @@ import java.util.Optional; @SmallTest @RunWith(AndroidTestingRunner.class) @TestableLooper.RunWithLooper +@DisableFlags(NotificationIconContainerRefactor.FLAG_NAME) public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase { @Mock @@ -83,7 +85,6 @@ public class LegacyNotificationIconAreaControllerImplTest extends SysuiTestCase @Before public void setup() { MockitoAnnotations.initMocks(this); - mSetFlagsRule.disableFlags(NotificationIconContainerRefactor.FLAG_NAME); mController = new LegacyNotificationIconAreaControllerImpl( mContext, mStatusBarStateController, diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java index bbdc9ced57ee..1dafcc48f19f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java @@ -15,12 +15,13 @@ package com.android.systemui.statusbar.phone; import static android.view.Display.DEFAULT_DISPLAY; - import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.BUBBLE; import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PEEK; import static com.android.systemui.statusbar.notification.interruption.VisualInterruptionType.PULSE; - import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -30,14 +31,14 @@ import static org.mockito.Mockito.when; import android.app.Notification; import android.app.PendingIntent; import android.app.StatusBarManager; -import android.platform.test.flag.junit.SetFlagsRule; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import androidx.test.filters.SmallTest; -import com.android.systemui.Flags; import com.android.systemui.InitController; import com.android.systemui.SysuiTestCase; import com.android.systemui.plugins.ActivityStarter; @@ -64,6 +65,7 @@ import com.android.systemui.statusbar.notification.interruption.NotificationInte import com.android.systemui.statusbar.notification.interruption.VisualInterruptionCondition; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionFilter; +import com.android.systemui.statusbar.notification.interruption.VisualInterruptionRefactor; import com.android.systemui.statusbar.notification.interruption.VisualInterruptionType; import com.android.systemui.statusbar.notification.row.NotificationGutsManager; import com.android.systemui.statusbar.notification.stack.NotificationListContainer; @@ -73,7 +75,6 @@ import com.android.systemui.statusbar.policy.HeadsUpManager; import com.android.systemui.statusbar.policy.KeyguardStateController; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -100,10 +101,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { private final KeyguardStateController mKeyguardStateController = mock(KeyguardStateController.class); - @Rule - public final SetFlagsRule mSetFlagsRule = new SetFlagsRule( - SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT); - @Before public void setup() { mCommandQueue = new CommandQueue(mContext, new FakeDisplayTracker(mContext)); @@ -114,29 +111,46 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mDependency.injectMockDependency(NotificationShadeWindowController.class); when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(true); + + createPresenter(); + if (VisualInterruptionRefactor.isEnabled()) { + verifyAndCaptureSuppressors(); + } else { + verifyAndCaptureLegacySuppressor(); + } } @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testInit_refactorDisabled() { - ensureRefactorDisabledState(); + assertFalse(VisualInterruptionRefactor.isEnabled()); + assertNull(mAlertsDisabledCondition); + assertNull(mVrModeCondition); + assertNull(mNeedsRedactionFilter); + assertNull(mPanelsDisabledCondition); + assertNotNull(mInterruptSuppressor); } @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testInit_refactorEnabled() { - ensureRefactorEnabledState(); + assertTrue(VisualInterruptionRefactor.isEnabled()); + assertNotNull(mAlertsDisabledCondition); + assertNotNull(mVrModeCondition); + assertNotNull(mNeedsRedactionFilter); + assertNotNull(mPanelsDisabledCondition); + assertNull(mInterruptSuppressor); } @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testNoSuppressHeadsUp_default_refactorDisabled() { - ensureRefactorDisabledState(); - assertFalse(mInterruptSuppressor.suppressAwakeHeadsUp(createNotificationEntry())); } @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testNoSuppressHeadsUp_default_refactorEnabled() { - ensureRefactorEnabledState(); - assertFalse(mAlertsDisabledCondition.shouldSuppress()); assertFalse(mVrModeCondition.shouldSuppress()); assertFalse(mNeedsRedactionFilter.shouldSuppress(createNotificationEntry())); @@ -144,9 +158,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testSuppressHeadsUp_disabledStatusBar_refactorDisabled() { - ensureRefactorDisabledState(); - mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0, false /* animate */); TestableLooper.get(this).processAllMessages(); @@ -156,9 +169,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testSuppressHeadsUp_disabledStatusBar_refactorEnabled() { - ensureRefactorEnabledState(); - mCommandQueue.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_EXPAND, 0, false /* animate */); TestableLooper.get(this).processAllMessages(); @@ -168,9 +180,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testSuppressHeadsUp_disabledNotificationShade_refactorDisabled() { - ensureRefactorDisabledState(); - mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false /* animate */); TestableLooper.get(this).processAllMessages(); @@ -180,9 +191,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testSuppressHeadsUp_disabledNotificationShade_refactorEnabled() { - ensureRefactorEnabledState(); - mCommandQueue.disable(DEFAULT_DISPLAY, 0, StatusBarManager.DISABLE2_NOTIFICATION_SHADE, false /* animate */); TestableLooper.get(this).processAllMessages(); @@ -192,9 +202,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testPanelsDisabledConditionSuppressesPeek() { - ensureRefactorEnabledState(); - final Set<VisualInterruptionType> types = mPanelsDisabledCondition.getTypes(); assertTrue(types.contains(PEEK)); assertFalse(types.contains(PULSE)); @@ -202,9 +211,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorDisabled() { - ensureRefactorDisabledState(); - when(mKeyguardStateController.isShowing()).thenReturn(true); when(mKeyguardStateController.isOccluded()).thenReturn(false); @@ -212,9 +220,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testNoSuppressHeadsUp_FSI_nonOccludedKeyguard_refactorEnabled() { - ensureRefactorEnabledState(); - when(mKeyguardStateController.isShowing()).thenReturn(true); when(mKeyguardStateController.isOccluded()).thenReturn(false); @@ -227,9 +234,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testSuppressInterruptions_vrMode_refactorDisabled() { - ensureRefactorDisabledState(); - mStatusBarNotificationPresenter.mVrMode = true; assertTrue("Vr mode should suppress interruptions", @@ -237,9 +243,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testSuppressInterruptions_vrMode_refactorEnabled() { - ensureRefactorEnabledState(); - mStatusBarNotificationPresenter.mVrMode = true; assertTrue("Vr mode should suppress interruptions", mVrModeCondition.shouldSuppress()); @@ -251,9 +256,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @DisableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testSuppressInterruptions_statusBarAlertsDisabled_refactorDisabled() { - ensureRefactorDisabledState(); - when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false); assertTrue("When alerts aren't enabled, interruptions are suppressed", @@ -261,9 +265,8 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { } @Test + @EnableFlags(VisualInterruptionRefactor.FLAG_NAME) public void testSuppressInterruptions_statusBarAlertsDisabled_refactorEnabled() { - ensureRefactorEnabledState(); - when(mNotificationAlertsInteractor.areNotificationAlertsEnabled()).thenReturn(false); assertTrue("When alerts aren't enabled, interruptions are suppressed", @@ -349,18 +352,6 @@ public class StatusBarNotificationPresenterTest extends SysuiTestCase { mInterruptSuppressor = suppressorCaptor.getValue(); } - private void ensureRefactorDisabledState() { - mSetFlagsRule.disableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR); - createPresenter(); - verifyAndCaptureLegacySuppressor(); - } - - private void ensureRefactorEnabledState() { - mSetFlagsRule.enableFlags(Flags.FLAG_VISUAL_INTERRUPTIONS_REFACTOR); - createPresenter(); - verifyAndCaptureSuppressors(); - } - private NotificationEntry createNotificationEntry() { return new NotificationEntryBuilder() .setPkg("a") 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/CollapsedStatusBarViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt index 09dc1e537e9f..89842d6274e9 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/shared/ui/viewmodel/CollapsedStatusBarViewModelImplTest.kt @@ -16,9 +16,10 @@ package com.android.systemui.statusbar.pipeline.shared.ui.viewmodel +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags import androidx.test.filters.SmallTest import com.android.systemui.CoroutineTestScopeModule -import com.android.systemui.Flags import com.android.systemui.SysUITestComponent import com.android.systemui.SysUITestModule import com.android.systemui.SysuiTestCase @@ -29,6 +30,7 @@ import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepos import com.android.systemui.keyguard.shared.model.KeyguardState import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.keyguard.shared.model.TransitionStep +import com.android.systemui.log.assertLogsWtf import com.android.systemui.runTest import com.android.systemui.statusbar.data.model.StatusBarMode import com.android.systemui.statusbar.data.repository.FakeStatusBarModeRepository @@ -37,14 +39,15 @@ import com.android.systemui.statusbar.notification.data.model.activeNotification import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.statusbar.notification.shared.NotificationsLiveDataStoreRefactor import com.google.common.truth.Truth.assertThat import dagger.BindsInstance import dagger.Component import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.UnconfinedTestDispatcher -import org.junit.Before import org.junit.Test @SmallTest @@ -79,11 +82,6 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { testScope = CoroutineTestScopeModule(TestScope(UnconfinedTestDispatcher())), ) - @Before - fun setUp() { - mSetFlagsRule.enableFlags(Flags.FLAG_NOTIFICATIONS_LIVE_DATA_STORE_REFACTOR) - } - @Test fun isTransitioningFromLockscreenToOccluded_started_isTrue() = testComponent.runTest { @@ -347,6 +345,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) fun areNotificationsLightsOut_lowProfileWithNotifications_true() = testComponent.runTest { statusBarModeRepository.defaultDisplay.statusBarMode.value = @@ -360,6 +359,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) fun areNotificationsLightsOut_lowProfileWithoutNotifications_false() = testComponent.runTest { statusBarModeRepository.defaultDisplay.statusBarMode.value = @@ -373,6 +373,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) fun areNotificationsLightsOut_defaultStatusBarModeWithoutNotifications_false() = testComponent.runTest { statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT @@ -385,6 +386,7 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { } @Test + @EnableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) fun areNotificationsLightsOut_defaultStatusBarModeWithNotifications_false() = testComponent.runTest { statusBarModeRepository.defaultDisplay.statusBarMode.value = StatusBarMode.TRANSPARENT @@ -396,6 +398,16 @@ class CollapsedStatusBarViewModelImplTest : SysuiTestCase() { assertThat(actual).isFalse() } + @Test + @DisableFlags(NotificationsLiveDataStoreRefactor.FLAG_NAME) + fun areNotificationsLightsOut_requiresFlagEnabled() = + testComponent.runTest { + assertLogsWtf { + val flow = underTest.areNotificationsLightsOut(DISPLAY_ID) + assertThat(flow).isEqualTo(emptyFlow<Boolean>()) + } + } + private fun activeNotificationsStore(notifications: List<ActiveNotificationModel>) = ActiveNotificationsStore.Builder() .apply { notifications.forEach(::addIndividualNotif) } 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/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java index 01dad381efa0..479309c18c92 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardStateControllerTest.java @@ -153,6 +153,36 @@ public class KeyguardStateControllerTest extends SysuiTestCase { } @Test + public void testCanSkipLockScreen_updateCalledOnFacesCleared() { + verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture()); + + // Cannot skip after there's a password/pin/pattern + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); + ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */); + assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse(); + + // Unless user is authenticated + when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true); + mUpdateCallbackCaptor.getValue().onFacesCleared(); + assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue(); + } + + @Test + public void testCanSkipLockScreen_updateCalledOnFingerprintssCleared() { + verify(mKeyguardUpdateMonitor).registerCallback(mUpdateCallbackCaptor.capture()); + + // Cannot skip after there's a password/pin/pattern + when(mLockPatternUtils.isSecure(anyInt())).thenReturn(true); + ((KeyguardStateControllerImpl) mKeyguardStateController).update(false /* alwaysUpdate */); + assertThat(mKeyguardStateController.canDismissLockScreen()).isFalse(); + + // Unless user is authenticated + when(mKeyguardUpdateMonitor.getUserCanSkipBouncer(anyInt())).thenReturn(true); + mUpdateCallbackCaptor.getValue().onFingerprintsCleared(); + assertThat(mKeyguardStateController.canDismissLockScreen()).isTrue(); + } + + @Test public void testIsUnlocked() { // Is unlocked whenever the keyguard is not showing assertThat(mKeyguardStateController.isShowing()).isFalse(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java index 8585d46fa8a5..5b9b390eea2d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -119,6 +119,7 @@ import com.android.systemui.scene.SceneTestUtils; import com.android.systemui.scene.data.repository.SceneContainerRepository; import com.android.systemui.scene.domain.interactor.SceneInteractor; import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags; +import com.android.systemui.scene.shared.flag.SceneContainerFlags; import com.android.systemui.scene.shared.logger.SceneLogger; import com.android.systemui.settings.FakeDisplayTracker; import com.android.systemui.settings.UserTracker; @@ -343,6 +344,8 @@ public class BubblesTest extends SysuiTestCase { private Icon mAppBubbleIcon; @Mock private Display mDefaultDisplay; + @Mock + private SceneContainerFlags mSceneContainerFlags; private final SceneTestUtils mUtils = new SceneTestUtils(this); private final TestScope mTestScope = mUtils.getTestScope(); @@ -503,7 +506,8 @@ public class BubblesTest extends SysuiTestCase { () -> mShadeInteractor, mShadeWindowLogger, () -> mSelectedUserInteractor, - mUserTracker + mUserTracker, + mSceneContainerFlags ); mNotificationShadeWindowController.fetchWindowRootView(); mNotificationShadeWindowController.attach(); diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/android/content/pm/LauncherAppsKosmos.kt index 8bb07d9487cd..94fc1fce7f6f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/android/content/pm/LauncherAppsKosmos.kt @@ -14,10 +14,9 @@ * limitations under the License. */ -package com.android.systemui.deviceentry.data.repository +package android.content.pm import com.android.systemui.kosmos.Kosmos +import com.android.systemui.util.mockito.mock -var Kosmos.deviceEntryHapticsRepository: DeviceEntryHapticsRepository by - Kosmos.Fixture { fakeDeviceEntryHapticsRepository } -val Kosmos.fakeDeviceEntryHapticsRepository by Kosmos.Fixture { FakeDeviceEntryHapticsRepository() } +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 4fdea9713ab0..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,8 +175,9 @@ class FakeAuthenticationRepository( AuthenticationPatternCoordinate(0, 1), AuthenticationPatternCoordinate(0, 2), ) - const val MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING = 5 - const val THROTTLE_DURATION_MS = 30000 + 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/deviceentry/data/FakeDeviceEntryDataLayerModule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt index d97cb56787cc..8ff04a63802a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/FakeDeviceEntryDataLayerModule.kt @@ -16,7 +16,6 @@ package com.android.systemui.deviceentry.data import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepositoryModule -import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryHapticsRepositoryModule import com.android.systemui.deviceentry.data.repository.FakeDeviceEntryRepositoryModule import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepositoryModule import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFaceAuthRepositoryModule @@ -29,7 +28,6 @@ import dagger.Module [ FakeBiometricSettingsRepositoryModule::class, FakeDeviceEntryRepositoryModule::class, - FakeDeviceEntryHapticsRepositoryModule::class, FakeDeviceEntryFaceAuthRepositoryModule::class, FakeDeviceEntryFingerprintAuthRepositoryModule::class, FakeFingerprintPropertyRepositoryModule::class, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt deleted file mode 100644 index b29b67d7c220..000000000000 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryHapticsRepository.kt +++ /dev/null @@ -1,55 +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.systemui.deviceentry.data.repository - -import com.android.systemui.dagger.SysUISingleton -import dagger.Binds -import dagger.Module -import javax.inject.Inject -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow - -/** Fake implementation of [DeviceEntryHapticsRepository] */ -@SysUISingleton -class FakeDeviceEntryHapticsRepository @Inject constructor() : DeviceEntryHapticsRepository { - private var _successHapticRequest: MutableStateFlow<Boolean> = MutableStateFlow(false) - override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow() - - private var _errorHapticRequest: MutableStateFlow<Boolean> = MutableStateFlow(false) - override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow() - - override fun requestSuccessHaptic() { - _successHapticRequest.value = true - } - - override fun handleSuccessHaptic() { - _successHapticRequest.value = false - } - - override fun requestErrorHaptic() { - _errorHapticRequest.value = true - } - - override fun handleErrorHaptic() { - _errorHapticRequest.value = false - } -} - -@Module -interface FakeDeviceEntryHapticsRepositoryModule { - @Binds fun bindFake(fake: FakeDeviceEntryHapticsRepository): DeviceEntryHapticsRepository -} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt index ba70d46fd954..6436a382eb7f 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt @@ -16,16 +16,24 @@ package com.android.systemui.deviceentry.data.repository import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import dagger.Binds import dagger.Module import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow /** Fake implementation of [DeviceEntryRepository] */ @SysUISingleton class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { + private val _enteringDeviceFromBiometricUnlock: MutableSharedFlow<BiometricUnlockSource> = + MutableSharedFlow() + override val enteringDeviceFromBiometricUnlock: Flow<BiometricUnlockSource> = + _enteringDeviceFromBiometricUnlock.asSharedFlow() private var isLockscreenEnabled = true @@ -54,6 +62,10 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { fun setBypassEnabled(isBypassEnabled: Boolean) { _isBypassEnabled.value = isBypassEnabled } + + suspend fun enteringDeviceFromBiometricUnlock(sourceType: BiometricUnlockSource) { + _enteringDeviceFromBiometricUnlock.emit(sourceType) + } } @Module diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt new file mode 100644 index 000000000000..1bd105620813 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractorKosmos.kt @@ -0,0 +1,31 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.keyguard.data.repository.biometricSettingsRepository +import com.android.systemui.kosmos.Kosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.deviceEntryBiometricAuthInteractor by + Kosmos.Fixture { + DeviceEntryBiometricAuthInteractor( + biometricSettingsRepository = biometricSettingsRepository, + deviceEntryFaceAuthInteractor = deviceEntryFaceAuthInteractor, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt new file mode 100644 index 000000000000..d2dff7834b40 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractorKosmos.kt @@ -0,0 +1,30 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository +import com.android.systemui.kosmos.Kosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.deviceEntryFaceAuthInteractor by + Kosmos.Fixture { + DeviceEntryFaceAuthInteractor( + repository = deviceEntryFaceAuthRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt new file mode 100644 index 000000000000..66c6f86c452d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractorKosmos.kt @@ -0,0 +1,30 @@ +/* + * 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. + */ + +@file:OptIn(ExperimentalCoroutinesApi::class) + +package com.android.systemui.deviceentry.domain.interactor + +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository +import com.android.systemui.kosmos.Kosmos +import kotlinx.coroutines.ExperimentalCoroutinesApi + +val Kosmos.deviceEntryFingerprintAuthInteractor by + Kosmos.Fixture { + DeviceEntryFingerprintAuthInteractor( + repository = deviceEntryFingerprintAuthRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt index de6cacb00faa..6bf527df4026 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorKosmos.kt @@ -20,7 +20,6 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.keyguard.logging.biometricUnlockLogger import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryHapticsRepository import com.android.systemui.keyevent.domain.interactor.keyEventInteractor import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.kosmos.Kosmos @@ -31,7 +30,9 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi val Kosmos.deviceEntryHapticsInteractor by Kosmos.Fixture { DeviceEntryHapticsInteractor( - repository = fakeDeviceEntryHapticsRepository, + deviceEntryInteractor = deviceEntryInteractor, + deviceEntryFingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, + deviceEntryBiometricAuthInteractor = deviceEntryBiometricAuthInteractor, fingerprintPropertyRepository = fingerprintPropertyRepository, biometricSettingsRepository = biometricSettingsRepository, keyEventInteractor = keyEventInteractor, 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/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt index e289083a2d9e..a1b6587be0b8 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFaceAuthRepository.kt @@ -31,7 +31,6 @@ import kotlinx.coroutines.flow.filterNotNull @SysUISingleton class FakeDeviceEntryFaceAuthRepository @Inject constructor() : DeviceEntryFaceAuthRepository { - override val isAuthenticated = MutableStateFlow(false) override val canRunFaceAuth = MutableStateFlow(false) private val _authenticationStatus = MutableStateFlow<FaceAuthenticationStatus?>(null) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt index 299262b91027..6557bcfd8337 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DeviceEntryIconViewModelKosmos.kt @@ -16,7 +16,6 @@ package com.android.systemui.keyguard.ui.viewmodel -import com.android.systemui.deviceentry.domain.interactor.deviceEntryHapticsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor import com.android.systemui.keyguard.domain.interactor.burnInInteractor @@ -45,7 +44,6 @@ val Kosmos.deviceEntryIconViewModel by Fixture { shadeDependentFlows = shadeDependentFlows, sceneContainerFlags = sceneContainerFlags, keyguardViewController = { statusBarKeyguardViewManager }, - deviceEntryHapticsInteractor = deviceEntryHapticsInteractor, deviceEntryInteractor = deviceEntryInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt new file mode 100644 index 000000000000..10f9346aba14 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt @@ -0,0 +1,67 @@ +/* + * 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.log + +import android.util.Log +import android.util.Log.TerribleFailureHandler +import junit.framework.Assert + +/** + * Assert that the given block makes a call to Log.wtf + * + * @return the details of the log + */ +fun assertLogsWtf( + message: String = "Expected Log.wtf to be called", + allowMultiple: Boolean = false, + loggingBlock: () -> Unit, +): TerribleFailureLog { + var caught: TerribleFailureLog? = null + var count = 0 + val newHandler = TerribleFailureHandler { tag, failure, system -> + if (caught == null) { + caught = TerribleFailureLog(tag, failure, system) + } + count++ + } + val oldHandler = Log.setWtfHandler(newHandler) + try { + loggingBlock() + } finally { + Log.setWtfHandler(oldHandler) + } + Assert.assertNotNull(message, caught) + if (!allowMultiple && count != 1) { + Assert.fail("Unexpectedly caught Log.Wtf $count times; expected only 1. First: $caught") + } + return caught!! +} + +@JvmOverloads +fun assertLogsWtf( + message: String = "Expected Log.wtf to be called", + allowMultiple: Boolean = false, + loggingRunnable: Runnable, +): TerribleFailureLog = + assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() } + +/** The data passed to [TerribleFailureHandler.onTerribleFailure] */ +data class TerribleFailureLog( + val tag: String, + val failure: Log.TerribleFailure, + val system: Boolean +) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.kt new file mode 100644 index 000000000000..e9a394aef6de --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/saver/DataSaverTileKosmos.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.qs.tiles.impl.saver + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.connectivity.ConnectivityModule + +val Kosmos.qsDataSaverTileConfig by + Kosmos.Fixture { ConnectivityModule.provideDataSaverTileConfig(qsEventLogger) } 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/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt index be1f081d5b8f..a48b27015c02 100644 --- a/packages/overlays/NavigationBarModeGesturalOverlay/res/values-sw600dp/config.xml +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/collection/NotifPipelineKosmos.kt @@ -1,13 +1,11 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- -/** - * Copyright (c) 2023, The Android Open Source Project +/* + * 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 + * 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, @@ -15,8 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ ---> -<resources> - <!-- If true, attach the navigation bar to the app during app transition --> - <bool name="config_attachNavBarToAppDuringTransition">false</bool> -</resources> + +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/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java index 886722e46376..bade84890f41 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeDataSaverController.java @@ -19,19 +19,38 @@ import android.testing.LeakCheck; import com.android.systemui.statusbar.policy.DataSaverController; import com.android.systemui.statusbar.policy.DataSaverController.Listener; +import java.util.ArrayList; +import java.util.List; + public class FakeDataSaverController extends BaseLeakChecker<Listener> implements DataSaverController { + private boolean mIsEnabled = false; + private List<Listener> mListeners = new ArrayList<>(); + public FakeDataSaverController(LeakCheck test) { super(test, "datasaver"); } @Override public boolean isDataSaverEnabled() { - return false; + return mIsEnabled; } @Override public void setDataSaverEnabled(boolean enabled) { + mIsEnabled = enabled; + for (Listener listener: mListeners) { + listener.onDataSaverChanged(enabled); + } + } + @Override + public void addCallback(Listener listener) { + mListeners.add(listener); + } + + @Override + public void removeCallback(Listener listener) { + mListeners.remove(listener); } } diff --git a/ravenwood/framework-minus-apex-ravenwood-policies.txt b/ravenwood/framework-minus-apex-ravenwood-policies.txt index c9069e5db9cb..f5e4af50fc29 100644 --- a/ravenwood/framework-minus-apex-ravenwood-policies.txt +++ b/ravenwood/framework-minus-apex-ravenwood-policies.txt @@ -3,6 +3,9 @@ # Keep all AIDL interfaces class :aidl stubclass +# Keep all feature flag implementations +class :feature_flags stubclass + # Collections class android.util.ArrayMap stubclass class android.util.ArraySet stubclass diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt index 831fce12b9c9..2ba5f6b05e26 100644 --- a/ravenwood/ravenwood-annotation-allowed-classes.txt +++ b/ravenwood/ravenwood-annotation-allowed-classes.txt @@ -1,6 +1,8 @@ # Only classes listed here can use the Ravenwood annotations. com.android.internal.util.ArrayUtils +com.android.internal.os.LongArrayMultiStateCounter +com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer android.util.AtomicFile android.util.DataUnit @@ -22,6 +24,7 @@ android.util.SparseSetArray android.util.TimeUtils android.util.Xml +android.os.BatteryConsumer android.os.Binder android.os.Binder$IdentitySupplier android.os.FileUtils @@ -88,6 +91,8 @@ android.graphics.PointF android.graphics.Rect android.graphics.RectF +android.content.ContentProvider + com.android.server.LocalServices com.android.internal.os.SomeArgs diff --git a/ravenwood/test-authors.md b/ravenwood/test-authors.md index 5adef534a2b2..de05777f01cd 100644 --- a/ravenwood/test-authors.md +++ b/ravenwood/test-authors.md @@ -71,10 +71,10 @@ public class MyCodeTest { Once you’ve defined your test, you can use typical commands to execute it locally: ``` -$ atest MyTestsRavenwood +$ atest --host MyTestsRavenwood ``` -> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. +> **Note:** There's a known bug where `atest` currently requires a connected device to run Ravenwood tests, but that device isn't used for testing. Using the `--host` argument above is a way to bypass this requirement until bug #312525698 is fixed. You can also run your new tests automatically via `TEST_MAPPING` rules like this: @@ -89,6 +89,27 @@ You can also run your new tests automatically via `TEST_MAPPING` rules like this } ``` +## Strategies for feature flags + +Ravenwood supports writing tests against logic that uses feature flags through the existing `SetFlagsRule` infrastructure maintained by the feature flagging team: + +``` +import android.platform.test.flag.junit.SetFlagsRule; + +@RunWith(AndroidJUnit4.class) +public class MyCodeTest { + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + + @Test + public void testEnabled() { + mSetFlagsRule.enableFlags(Flags.FLAG_MY_FLAG); + // verify test logic that depends on flag being enabled + } +``` + +This naturally composes together well with any `RavenwoodRule` that your test may need. + ## Strategies for migration/bivalent tests Ravenwood aims to support tests that are written in a “bivalent” way, where the same test code can run on both a real Android device and under a Ravenwood environment. 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/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java index 71878954e9ec..0696807b3c8c 100644 --- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java +++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java @@ -1848,8 +1848,14 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ } public void notifyGesture(AccessibilityGestureEvent gestureEvent) { - mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE, - gestureEvent).sendToTarget(); + if (android.view.accessibility.Flags.copyEventsForGestureDetection()) { + // We will use this event async, so copy it because it contains MotionEvents. + mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE, + gestureEvent.copyForAsync()).sendToTarget(); + } else { + mInvocationHandler.obtainMessage(InvocationHandler.MSG_ON_GESTURE, + gestureEvent).sendToTarget(); + } } public void notifySystemActionsChangedLocked() { @@ -2323,9 +2329,13 @@ abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServ final int type = message.what; switch (type) { case MSG_ON_GESTURE: { - notifyGestureInternal((AccessibilityGestureEvent) message.obj); + if (message.obj instanceof AccessibilityGestureEvent gesture) { + notifyGestureInternal(gesture); + if (android.view.accessibility.Flags.copyEventsForGestureDetection()) { + gesture.recycle(); + } + } } break; - case MSG_CLEAR_ACCESSIBILITY_CACHE: { notifyClearAccessibilityCacheInternal(); } break; 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/gestures/GestureMatcher.java b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java index 6e2fc69f2942..3668eefe293d 100644 --- a/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java +++ b/services/accessibility/java/com/android/server/accessibility/gestures/GestureMatcher.java @@ -328,13 +328,21 @@ public abstract class GestureMatcher { + getStateSymbolicName(mTargetState)); } mHandler.removeCallbacks(this); + recycleEvent(); } public void post( int state, long delay, MotionEvent event, MotionEvent rawEvent, int policyFlags) { + // Recycle the old event first if necessary, to handle duplicate calls to post. + recycleEvent(); mTargetState = state; - mEvent = event; - mRawEvent = rawEvent; + if (android.view.accessibility.Flags.copyEventsForGestureDetection()) { + mEvent = event.copy(); + mRawEvent = rawEvent.copy(); + } else { + mEvent = event; + mRawEvent = rawEvent; + } mPolicyFlags = policyFlags; mHandler.postDelayed(this, delay); if (DEBUG) { @@ -367,6 +375,19 @@ public abstract class GestureMatcher { + getStateSymbolicName(mTargetState)); } setState(mTargetState, mEvent, mRawEvent, mPolicyFlags); + recycleEvent(); + } + + private void recycleEvent() { + if (android.view.accessibility.Flags.copyEventsForGestureDetection()) { + if (mEvent == null || mRawEvent == null) { + return; + } + mEvent.recycle(); + mRawEvent.recycle(); + mEvent = null; + mRawEvent = null; + } } } 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..baae1d934e8c 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -861,6 +861,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH } final class DetectingStateWithMultiFinger extends DetectingState { + private static final int TWO_FINGER_GESTURE_MAX_TAPS = 2; // A flag set to true when two fingers have touched down. // Used to indicate what next finger action should be. private boolean mIsTwoFingerCountReached = false; @@ -917,7 +918,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE); if (event.getPointerCount() == 2) { - if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event)) { + if (isMultiFingerMultiTapTriggered( + TWO_FINGER_GESTURE_MAX_TAPS - 1, event)) { // 3tap and hold afterLongTapTimeoutTransitionToDraggingState(event); } else { @@ -962,7 +964,8 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // (which is a rare combo to be used aside from magnification) if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) { transitionToViewportDraggingStateAndClear(event); - } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 2, event) + } else if (isMultiFingerMultiTapTriggered( + TWO_FINGER_GESTURE_MAX_TAPS - 1, event) && event.getPointerCount() == 2) { transitionToViewportDraggingStateAndClear(event); } else if (isActivated() && event.getPointerCount() == 2) { @@ -981,17 +984,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; @@ -1004,7 +1012,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mDisplayId, event.getX(), event.getY())) { transitionToDelegatingStateAndClear(); - } else if (isMultiFingerMultiTapTriggered(/* targetTapCount= */ 3, event)) { + } else if (isMultiFingerMultiTapTriggered(TWO_FINGER_GESTURE_MAX_TAPS, event)) { // Placing multiple fingers before a single finger, because achieving a // multi finger multi tap also means achieving a single finger triple tap onTripleTap(event); @@ -1046,7 +1054,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH mIsTwoFingerCountReached = false; } - if (mDetectTwoFingerTripleTap && mCompletedTapCount > 2) { + if (mDetectTwoFingerTripleTap && mCompletedTapCount > TWO_FINGER_GESTURE_MAX_TAPS - 1) { final boolean enabled = !isActivated(); mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled); } @@ -1070,7 +1078,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH // Only log the 3tap and hold event if (!shortcutTriggered) { final boolean enabled = !isActivated(); - if (mCompletedTapCount == 2) { + if (mCompletedTapCount == TWO_FINGER_GESTURE_MAX_TAPS - 1) { // Two finger triple tap and hold mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled); } else { diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java index 73c267a6e262..75d01f574cac 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java @@ -481,10 +481,10 @@ public class WindowMagnificationGestureHandler extends MagnificationGestureHandl if (mDetectTwoFingerTripleTap) { mGestureMatchers.add(new MultiFingerMultiTap(context, /* fingers= */ 2, - /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP, + /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP, null)); mGestureMatchers.add(new MultiFingerMultiTapAndHold(context, /* fingers= */ 2, - /* taps= */ 3, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD, + /* taps= */ 2, MagnificationGestureMatcher.GESTURE_TRIPLE_TAP_AND_HOLD, null)); } 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 45d7314f3fab..58aa2c303345 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -104,6 +104,7 @@ import com.android.server.LocalServices; import com.android.server.companion.virtual.GenericWindowPolicyController.RunningAppsChangedListener; import com.android.server.companion.virtual.audio.VirtualAudioController; import com.android.server.companion.virtual.camera.VirtualCameraController; +import com.android.server.inputmethod.InputMethodManagerInternal; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -352,6 +353,14 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED; } mBaseVirtualDisplayFlags = flags; + + if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) { + final String imeId = mParams.getInputMethodComponent().flattenToShortString(); + Slog.d(TAG, "Setting custom input method " + imeId + " as default for virtual device " + + deviceId); + InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers( + mDeviceId, imeId); + } } @VisibleForTesting @@ -556,6 +565,12 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mCameraAccessController.stopObservingIfNeeded(); } + // Clear any previously set custom IME components. + if (Flags.vdmCustomIme() && mParams.getInputMethodComponent() != null) { + InputMethodManagerInternal.get().setVirtualDeviceInputMethodForAllUsers( + mDeviceId, null); + } + mInputController.close(); mSensorController.close(); } finally { @@ -884,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(); @@ -1080,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/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java index 9b78ed41206d..923728ffb0f3 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java @@ -859,6 +859,11 @@ public class VirtualDeviceManagerService extends SystemService { } @Override + public boolean isValidVirtualDeviceId(int deviceId) { + return mImpl.isValidVirtualDeviceId(deviceId); + } + + @Override public @Nullable String getPersistentIdForDevice(int deviceId) { if (deviceId == Context.DEVICE_ID_DEFAULT) { return VirtualDeviceManager.PERSISTENT_DEVICE_ID_DEFAULT; diff --git a/services/core/Android.bp b/services/core/Android.bp index b4cf34e00c53..20a3b9ada85d 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -154,7 +154,6 @@ java_library_static { static_libs: [ "android.frameworks.location.altitude-V1-java", // AIDL - "android.frameworks.vibrator-V1-java", // AIDL "android.hardware.authsecret-V1.0-java", "android.hardware.authsecret-V1-java", "android.hardware.boot-V1.0-java", // HIDL diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java index e9d4d76c6131..33726d17da62 100644 --- a/services/core/java/com/android/server/BatteryService.java +++ b/services/core/java/com/android/server/BatteryService.java @@ -17,6 +17,8 @@ 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; @@ -166,6 +168,7 @@ public final class BatteryService extends SystemService { private int mLowBatteryCloseWarningLevel; private int mBatteryNearlyFullLevel; private int mShutdownBatteryTemperature; + private boolean mShutdownIfNoPower; private static String sSystemUiPackage; @@ -230,6 +233,8 @@ public final class BatteryService extends SystemService { com.android.internal.R.integer.config_lowBatteryCloseWarningBump); mShutdownBatteryTemperature = mContext.getResources().getInteger( com.android.internal.R.integer.config_shutdownBatteryTemperature); + mShutdownIfNoPower = mContext.getResources().getBoolean( + com.android.internal.R.bool.config_shutdownIfNoPower); sSystemUiPackage = mContext.getResources().getString( com.android.internal.R.string.config_systemUi); @@ -391,6 +396,9 @@ public final class BatteryService extends SystemService { if (mHealthInfo.batteryCapacityLevel != BatteryCapacityLevel.UNSUPPORTED) { return (mHealthInfo.batteryCapacityLevel == BatteryCapacityLevel.CRITICAL); } + if (!mShutdownIfNoPower) { + return false; + } if (mHealthInfo.batteryLevel > 0) { return false; } @@ -920,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]"); @@ -994,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; @@ -1051,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/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 4bb9f4f27b64..c436c7217d0f 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -127,6 +127,7 @@ public class Watchdog implements Dumpable { "/system/bin/mediaserver", "/system/bin/netd", "/system/bin/sdcard", + "/system/bin/servicemanager", "/system/bin/surfaceflinger", "/system/bin/vold", "media.extractor", // system/bin/mediaextractor 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/ForegroundServiceTypeLoggerModule.java b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java index caafb421a147..fc8ad6bc94e7 100644 --- a/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java +++ b/services/core/java/com/android/server/am/ForegroundServiceTypeLoggerModule.java @@ -35,6 +35,7 @@ import android.app.ActivityManager.ForegroundServiceApiType; import android.app.ForegroundServiceDelegationOptions; import android.content.ComponentName; import android.content.pm.ServiceInfo; +import android.os.Trace; import android.util.ArrayMap; import android.util.IntArray; import android.util.LongArray; @@ -134,6 +135,11 @@ public class ForegroundServiceTypeLoggerModule { * call of the right type will also be associated and logged */ public void logForegroundServiceStart(int uid, int pid, ServiceRecord record) { + if (record.getComponentName() != null) { + final String traceTag = record.getComponentName().flattenToString() + ":" + uid; + Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, traceTag, + "foregroundService", record.foregroundServiceType); + } // initialize the UID stack UidState uidState = mUids.get(uid); if (uidState == null) { @@ -205,6 +211,11 @@ public class ForegroundServiceTypeLoggerModule { // we need to log all the API end events and remove the start events // then we remove the FGS from the various stacks // and also clean up the start calls stack by UID + if (record.getComponentName() != null) { + final String traceTag = record.getComponentName().flattenToString() + ":" + uid; + Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, + traceTag, record.hashCode()); + } final IntArray apiTypes = convertFgsTypeToApiTypes(record.foregroundServiceType); final UidState uidState = mUids.get(uid); if (apiTypes.size() == 0) { 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/appop/DiscreteRegistry.java b/services/core/java/com/android/server/appop/DiscreteRegistry.java index e91b7e8e37fc..b1a12f7338da 100644 --- a/services/core/java/com/android/server/appop/DiscreteRegistry.java +++ b/services/core/java/com/android/server/appop/DiscreteRegistry.java @@ -37,6 +37,7 @@ import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; import static android.app.AppOpsManager.OP_RECORD_AUDIO; +import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING; import static android.app.AppOpsManager.flagsToString; import static android.app.AppOpsManager.getUidStateName; @@ -136,7 +137,7 @@ final class DiscreteRegistry { private static final String DEFAULT_DISCRETE_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + "," + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," - + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; + + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO + "," + OP_RESERVED_FOR_TESTING; private static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); private static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); private static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = diff --git a/services/core/java/com/android/server/audio/AdiDeviceState.java b/services/core/java/com/android/server/audio/AdiDeviceState.java index ffdab7dfbfa4..9ae43a03515a 100644 --- a/services/core/java/com/android/server/audio/AdiDeviceState.java +++ b/services/core/java/com/android/server/audio/AdiDeviceState.java @@ -235,9 +235,10 @@ public final class AdiDeviceState { * {@link AdiDeviceState#toPersistableString()}. */ public static int getPeristedMaxSize() { - return 36; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1 + return 39; /* (mDeviceType)2 + (mDeviceAddress)17 + (mInternalDeviceType)9 + (mSAEnabled)1 + (mHasHeadTracker)1 + (mHasHeadTrackerEnabled)1 - + (SETTINGS_FIELD_SEPARATOR)5 */ + + (mAudioDeviceCategory)1 + (SETTINGS_FIELD_SEPARATOR)6 + + (SETTING_DEVICE_SEPARATOR)1 */ } @Nullable diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 9cfcb1679429..80917533cce1 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); @@ -2687,11 +2686,15 @@ public class AudioDeviceBroker { return; } final SettingsAdapter settingsAdapter = mAudioService.getSettings(); - boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(), - Settings.Secure.AUDIO_DEVICE_INVENTORY, - deviceSettings, UserHandle.USER_CURRENT); - if (!res) { - Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings); + try { + boolean res = settingsAdapter.putSecureStringForUser(mAudioService.getContentResolver(), + Settings.Secure.AUDIO_DEVICE_INVENTORY, + deviceSettings, UserHandle.USER_CURRENT); + if (!res) { + Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings); + } + } catch (IllegalArgumentException e) { + Log.e(TAG, "error saving AdiDeviceState: " + deviceSettings, e); } } diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index e54bf64df09e..34cfdfaa7974 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -30,7 +30,6 @@ import static android.media.AudioSystem.isBluetoothOutDevice; import static android.media.AudioSystem.isBluetoothScoOutDevice; import static android.media.audio.Flags.automaticBtDeviceType; - import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; import android.annotation.NonNull; @@ -81,7 +80,6 @@ import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; @@ -104,6 +102,14 @@ public class AudioDeviceInventory { private static final String SETTING_DEVICE_SEPARATOR_CHAR = "|"; private static final String SETTING_DEVICE_SEPARATOR = "\\|"; + /** Max String length that can be persisted within the Settings. */ + // LINT.IfChange(settings_max_length_per_string) + private static final int MAX_SETTINGS_LENGTH_PER_STRING = 32768; + // LINT.ThenChange(/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java) + + private static final int MAX_DEVICE_INVENTORY_ENTRIES = + MAX_SETTINGS_LENGTH_PER_STRING / AdiDeviceState.getPeristedMaxSize(); + // lock to synchronize all access to mConnectedDevices and mApmConnectedDevices private final Object mDevicesLock = new Object(); @@ -113,7 +119,8 @@ public class AudioDeviceInventory { private final Object mDeviceInventoryLock = new Object(); @GuardedBy("mDeviceInventoryLock") - private final HashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = new HashMap<>(); + private final LinkedHashMap<Pair<Integer, String>, AdiDeviceState> mDeviceInventory = + new LinkedHashMap<>(); Collection<AdiDeviceState> getImmutableDeviceInventory() { final List<AdiDeviceState> newList; @@ -136,6 +143,7 @@ public class AudioDeviceInventory { oldState.setSAEnabled(newState.isSAEnabled()); return oldState; }); + checkDeviceInventorySize_l(); } mDeviceBroker.postSynchronizeAdiDevicesInInventory(deviceState); } @@ -173,6 +181,8 @@ public class AudioDeviceInventory { ads.setAudioDeviceCategory(category); mDeviceInventory.put(ads.getDeviceId(), ads); + checkDeviceInventorySize_l(); + mDeviceBroker.postUpdatedAdiDeviceState(ads); mDeviceBroker.postPersistAudioDeviceSettings(); } @@ -200,6 +210,7 @@ public class AudioDeviceInventory { } return oldState; }); + checkDeviceInventorySize_l(); } if (updatedCategory.get()) { mDeviceBroker.postUpdatedAdiDeviceState(deviceState); @@ -272,6 +283,18 @@ public class AudioDeviceInventory { } } + @GuardedBy("mDeviceInventoryLock") + private void checkDeviceInventorySize_l() { + if (mDeviceInventory.size() > MAX_DEVICE_INVENTORY_ENTRIES) { + // remove the first element + Iterator<Entry<Pair<Integer, String>, AdiDeviceState>> iterator = + mDeviceInventory.entrySet().iterator(); + if (iterator.hasNext()) { + iterator.remove(); + } + } + } + @GuardedBy({"mDevicesLock", "mDeviceInventoryLock"}) private boolean synchronizeBleDeviceInInventory(AdiDeviceState updatedDevice) { for (DeviceInfo di : mConnectedDevices.values()) { @@ -1501,7 +1524,7 @@ public class AudioDeviceInventory { } else { status = addOp.deviceRoleAction(useCase, role, devices); if (status == AudioSystem.SUCCESS) { - rolesMap.put(key, devices); + rolesMap.put(key, new ArrayList(devices)); } } return status; @@ -2806,7 +2829,7 @@ public class AudioDeviceInventory { deviceCatalogSize = mDeviceInventory.size(); final StringBuilder settingsBuilder = new StringBuilder( - deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); + deviceCatalogSize * AdiDeviceState.getPeristedMaxSize()); Iterator<AdiDeviceState> iterator = mDeviceInventory.values().iterator(); if (iterator.hasNext()) { 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/audio/RotationHelper.java b/services/core/java/com/android/server/audio/RotationHelper.java index 394e4af30a9e..e012d17e2e3f 100644 --- a/services/core/java/com/android/server/audio/RotationHelper.java +++ b/services/core/java/com/android/server/audio/RotationHelper.java @@ -80,6 +80,7 @@ class RotationHelper { sContext = context; sHandler = handler; sDisplayListener = new AudioDisplayListener(); + sFoldStateListener = new FoldStateListener(sContext, RotationHelper::updateFoldState); sRotationCallback = rotationCallback; sFoldStateCallback = foldStateCallback; enable(); @@ -90,7 +91,6 @@ class RotationHelper { .registerDisplayListener(sDisplayListener, sHandler); updateOrientation(); - sFoldStateListener = new FoldStateListener(sContext, folded -> updateFoldState(folded)); sContext.getSystemService(DeviceStateManager.class) .registerCallback(new HandlerExecutor(sHandler), sFoldStateListener); } diff --git a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java index cecde55ef89f..823788f0b249 100644 --- a/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java +++ b/services/core/java/com/android/server/companion/virtual/VirtualDeviceManagerInternal.java @@ -21,6 +21,7 @@ import android.annotation.Nullable; import android.companion.virtual.IVirtualDevice; import android.companion.virtual.VirtualDevice; import android.companion.virtual.sensor.VirtualSensor; +import android.content.Context; import android.os.LocaleList; import android.util.ArraySet; @@ -149,6 +150,14 @@ public abstract class VirtualDeviceManagerInternal { public abstract @NonNull ArraySet<Integer> getDisplayIdsForDevice(int deviceId); /** + * Checks whether the passed {@code deviceId} is a valid virtual device ID or not. + * + * <p>{@link Context#DEVICE_ID_DEFAULT} is not valid as it is the ID of the default + * device which is not a virtual device.</p> + */ + public abstract boolean isValidVirtualDeviceId(int deviceId); + + /** * Returns the ID of the device which owns the display with the given ID. * * <p>In case the virtual display ID is invalid or doesn't belong to a virtual device, then diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java index 5a9f113e79df..1e5e147749a2 100644 --- a/services/core/java/com/android/server/content/SyncManager.java +++ b/services/core/java/com/android/server/content/SyncManager.java @@ -439,7 +439,7 @@ public class SyncManager { } }; - private class PackageMonitorImpl extends PackageMonitor { + private static class PackageMonitorImpl extends PackageMonitor { @Override public boolean onHandleForceStop(Intent intent, String[] packageNames, int uid, boolean doit, Bundle extras) { @@ -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/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java index f57bf295a34e..405c14941442 100644 --- a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java +++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java @@ -130,8 +130,8 @@ public class DisplayNotificationManager implements ConnectedDisplayUsbErrorsDete } sendErrorNotification(createErrorNotification( - R.string.connected_display_cable_dont_support_displays_notification_title, - R.string.connected_display_cable_dont_support_displays_notification_content, + R.string.connected_display_unavailable_notification_title, + R.string.connected_display_unavailable_notification_content, R.drawable.usb_cable_unknown_issue)); } diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java index dcb86a7d1eb6..66807aeb6629 100644 --- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java +++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java @@ -23,7 +23,9 @@ import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.UiThread; import android.content.ComponentName; +import android.content.Context; import android.content.pm.PackageManagerInternal; +import android.hardware.input.InputManager; import android.hardware.input.InputManagerGlobal; import android.os.Handler; import android.os.IBinder; @@ -64,6 +66,7 @@ final class HandwritingModeController { private static final int LONG_EVENT_BUFFER_SIZE = EVENT_BUFFER_SIZE * 20; private static final long HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS = 3000; + private final Context mContext; // This must be the looper for the UiThread. private final Looper mLooper; private final InputManagerInternal mInputManagerInternal; @@ -87,7 +90,9 @@ final class HandwritingModeController { private int mCurrentRequestId; @AnyThread - HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable) { + HandwritingModeController(Context context, Looper uiThreadLooper, + Runnable inkWindowInitRunnable) { + mContext = context; mLooper = uiThreadLooper; mCurrentDisplayId = Display.INVALID_DISPLAY; mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); @@ -285,7 +290,14 @@ final class HandwritingModeController { mHandwritingSurface.startIntercepting(imePid, imeUid); // Unset the pointer icon for the stylus in case the app had set it. - InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED); + if (com.android.input.flags.Flags.enablePointerChoreographer()) { + Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon( + PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED), + downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0), + mHandwritingSurface.getInputChannel().getToken()); + } else { + InputManagerGlobal.getInstance().setPointerIconType(PointerIcon.TYPE_NOT_SPECIFIED); + } return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(), mHandwritingBuffer); diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index ffd714b26b08..f526dbe9c66d 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -120,6 +120,25 @@ public abstract class InputMethodManagerInternal { @UserIdInt int userId); /** + * Makes the input method associated with {@code imeId} the default input method for all users + * on displays that are owned by the virtual device with the given {@code deviceId}. If the + * input method associated with {@code imeId} is not available, there will be no IME on the + * relevant displays. + * + * <p>The caller of this method is responsible for resetting it to {@code null} after the + * virtual device is closed.</p> + * + * @param deviceId the device ID on which to use the given input method as default. + * @param imeId the input method ID to be used as default on the given device. If {@code null}, + * then any existing input method association with that device will be removed. + * @throws IllegalArgumentException if a non-{@code null} input method ID is passed for a + * device ID that already has a custom input method set or if + * the device ID is not a valid virtual device. + */ + public abstract void setVirtualDeviceInputMethodForAllUsers( + int deviceId, @Nullable String imeId); + + /** * Registers a new {@link InputMethodListListener}. * * @param listener the listener to add @@ -250,6 +269,11 @@ public abstract class InputMethodManagerInternal { } @Override + public void setVirtualDeviceInputMethodForAllUsers( + int deviceId, @Nullable String imeId) { + } + + @Override public void registerInputMethodListListener(InputMethodListListener listener) { } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index b6a42a2715b5..30e9f5bd2a14 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -169,6 +169,7 @@ import com.android.internal.os.TransferPipe; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; +import com.android.internal.util.Preconditions; import com.android.internal.view.IInputMethodManager; import com.android.server.AccessibilityManagerInternal; import com.android.server.EventLogTags; @@ -312,6 +313,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // All known input methods. final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>(); private final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>(); + // Mapping from deviceId to the device-specific imeId for that device. + private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>(); + final InputMethodSubtypeSwitchingController mSwitchingController; final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController = new HardwareKeyboardShortcutController(); @@ -1709,7 +1713,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); mNonPreemptibleInputMethods = mRes.getStringArray( com.android.internal.R.array.config_nonPreemptibleInputMethods); - mHwController = new HandwritingModeController(thread.getLooper(), + mHwController = new HandwritingModeController(mContext, thread.getLooper(), new InkWindowInitializer()); registerDeviceListenerAndCheckStylusSupport(); } @@ -5620,6 +5624,23 @@ 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, + "DeviceId " + deviceId + " does not belong to a virtual device."); + synchronized (ImfLock.class) { + if (imeId == null) { + mVirtualDeviceMethodMap.remove(deviceId); + } else if (mVirtualDeviceMethodMap.contains(deviceId)) { + throw new IllegalArgumentException("Virtual device " + deviceId + + " already has a custom input method component"); + } else { + mVirtualDeviceMethodMap.put(deviceId, imeId); + } + } + } + + @Override public void registerInputMethodListListener(InputMethodListListener listener) { mInputMethodListListeners.addIfAbsent(listener); } 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/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java index 49095ceb5bdd..42c2548264d4 100644 --- a/services/core/java/com/android/server/locksettings/LockSettingsService.java +++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java @@ -1432,7 +1432,7 @@ public class LockSettingsService extends ILockSettings.Stub { } private void unlockKeystore(int userId, SyntheticPassword sp) { - Authorization.onLockScreenEvent(false, userId, sp.deriveKeyStorePassword(), null); + Authorization.onDeviceUnlocked(userId, sp.deriveKeyStorePassword()); } @VisibleForTesting /** Note: this method is overridden in unit tests */ 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/notification/DefaultDeviceEffectsApplier.java b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java index 9fdeda4b4bd0..885566693b9a 100644 --- a/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java +++ b/services/core/java/com/android/server/notification/DefaultDeviceEffectsApplier.java @@ -16,14 +16,23 @@ package com.android.server.notification; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; + import android.app.UiModeManager; import android.app.WallpaperManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.display.ColorDisplayManager; import android.os.Binder; import android.os.PowerManager; import android.service.notification.DeviceEffectsApplier; import android.service.notification.ZenDeviceEffects; +import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ConfigChangeOrigin; + +import com.android.internal.annotations.GuardedBy; /** Default implementation for {@link DeviceEffectsApplier}. */ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { @@ -34,13 +43,24 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { private static final int SATURATION_LEVEL_FULL_COLOR = 100; private static final float WALLPAPER_DIM_AMOUNT_DIMMED = 0.6f; private static final float WALLPAPER_DIM_AMOUNT_NORMAL = 0f; + private static final IntentFilter SCREEN_OFF_INTENT_FILTER = new IntentFilter( + Intent.ACTION_SCREEN_OFF); + private final Context mContext; private final ColorDisplayManager mColorDisplayManager; private final PowerManager mPowerManager; private final UiModeManager mUiModeManager; private final WallpaperManager mWallpaperManager; + private final Object mRegisterReceiverLock = new Object(); + @GuardedBy("mRegisterReceiverLock") + private boolean mIsScreenOffReceiverRegistered; + + private ZenDeviceEffects mLastAppliedEffects = new ZenDeviceEffects.Builder().build(); + private boolean mPendingNightMode; + DefaultDeviceEffectsApplier(Context context) { + mContext = context; mColorDisplayManager = context.getSystemService(ColorDisplayManager.class); mPowerManager = context.getSystemService(PowerManager.class); mUiModeManager = context.getSystemService(UiModeManager.class); @@ -48,24 +68,90 @@ class DefaultDeviceEffectsApplier implements DeviceEffectsApplier { } @Override - public void apply(ZenDeviceEffects effects) { + public void apply(ZenDeviceEffects effects, @ConfigChangeOrigin int origin) { Binder.withCleanCallingIdentity(() -> { - mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN, - effects.shouldSuppressAmbientDisplay()); + if (mLastAppliedEffects.shouldSuppressAmbientDisplay() + != effects.shouldSuppressAmbientDisplay()) { + mPowerManager.suppressAmbientDisplay(SUPPRESS_AMBIENT_DISPLAY_TOKEN, + effects.shouldSuppressAmbientDisplay()); + } - if (mColorDisplayManager != null) { - mColorDisplayManager.setSaturationLevel( - effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE - : SATURATION_LEVEL_FULL_COLOR); + if (mLastAppliedEffects.shouldDisplayGrayscale() != effects.shouldDisplayGrayscale()) { + if (mColorDisplayManager != null) { + mColorDisplayManager.setSaturationLevel( + effects.shouldDisplayGrayscale() ? SATURATION_LEVEL_GRAYSCALE + : SATURATION_LEVEL_FULL_COLOR); + } } - if (mWallpaperManager != null) { - mWallpaperManager.setWallpaperDimAmount( - effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED - : WALLPAPER_DIM_AMOUNT_NORMAL); + if (mLastAppliedEffects.shouldDimWallpaper() != effects.shouldDimWallpaper()) { + if (mWallpaperManager != null) { + mWallpaperManager.setWallpaperDimAmount( + effects.shouldDimWallpaper() ? WALLPAPER_DIM_AMOUNT_DIMMED + : WALLPAPER_DIM_AMOUNT_NORMAL); + } } - // TODO: b/308673343 - Apply dark theme (via UiModeManager) when screen is off. + if (mLastAppliedEffects.shouldUseNightMode() != effects.shouldUseNightMode()) { + updateOrScheduleNightMode(effects.shouldUseNightMode(), origin); + } }); + + mLastAppliedEffects = effects; + } + + private void updateOrScheduleNightMode(boolean useNightMode, @ConfigChangeOrigin int origin) { + mPendingNightMode = useNightMode; + + // Changing the theme can be disruptive for the user (Activities are likely recreated, may + // lose some state). Therefore we only apply the change immediately if the rule was + // activated manually, or we are initializing, or the screen is currently off/dreaming. + if (origin == ZenModeConfig.UPDATE_ORIGIN_INIT + || origin == ZenModeConfig.UPDATE_ORIGIN_INIT_USER + || origin == ZenModeConfig.UPDATE_ORIGIN_USER + || origin == ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + || !mPowerManager.isInteractive()) { + unregisterScreenOffReceiver(); + updateNightModeImmediately(useNightMode); + } else { + registerScreenOffReceiver(); + } + } + + @GuardedBy("mRegisterReceiverLock") + private final BroadcastReceiver mNightModeWhenScreenOff = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + unregisterScreenOffReceiver(); + updateNightModeImmediately(mPendingNightMode); + } + }; + + private void updateNightModeImmediately(boolean useNightMode) { + Binder.withCleanCallingIdentity(() -> { + // TODO: b/314285749 - Placeholder; use real APIs when available. + mUiModeManager.setNightModeCustomType(MODE_NIGHT_CUSTOM_TYPE_BEDTIME); + mUiModeManager.setNightModeActivatedForCustomMode(MODE_NIGHT_CUSTOM_TYPE_BEDTIME, + useNightMode); + }); + } + + private void registerScreenOffReceiver() { + synchronized (mRegisterReceiverLock) { + if (!mIsScreenOffReceiverRegistered) { + mContext.registerReceiver(mNightModeWhenScreenOff, SCREEN_OFF_INTENT_FILTER, + Context.RECEIVER_NOT_EXPORTED); + mIsScreenOffReceiverRegistered = true; + } + } + } + + private void unregisterScreenOffReceiver() { + synchronized (mRegisterReceiverLock) { + if (mIsScreenOffReceiverRegistered) { + mIsScreenOffReceiverRegistered = false; + mContext.unregisterReceiver(mNightModeWhenScreenOff); + } + } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 02845fb03119..c2a145df4114 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5299,11 +5299,11 @@ public class NotificationManagerService extends SystemService { public void setZenMode(int mode, Uri conditionId, String reason) throws RemoteException { enforceSystemOrSystemUI("INotificationManager.setZenMode"); final int callingUid = Binder.getCallingUid(); - final boolean isSystemOrSystemUi = isCallerSystemOrSystemUi(); final long identity = Binder.clearCallingIdentity(); try { - mZenModeHelper.setManualZenMode(mode, conditionId, null, reason, callingUid, - isSystemOrSystemUi); + mZenModeHelper.setManualZenMode(mode, conditionId, + ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, // Checked by enforce() + reason, /* caller= */ null, callingUid); } finally { Binder.restoreCallingIdentity(identity); } @@ -5360,10 +5360,11 @@ public class NotificationManagerService extends SystemService { } return mZenModeHelper.addAutomaticZenRule(rulePkg, automaticZenRule, - "addAutomaticZenRule", Binder.getCallingUid(), - // TODO: b/308670715: Distinguish FROM_APP from FROM_USER - isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI - : ZenModeHelper.FROM_APP); + // TODO: b/308670715: Distinguish origin properly (e.g. USER if creating a rule + // manually in Settings). + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + "addAutomaticZenRule", Binder.getCallingUid()); } @Override @@ -5379,11 +5380,12 @@ public class NotificationManagerService extends SystemService { Objects.requireNonNull(automaticZenRule.getConditionId(), "ConditionId is null"); enforcePolicyAccess(Binder.getCallingUid(), "updateAutomaticZenRule"); + // TODO: b/308670715: Distinguish origin properly (e.g. USER if updating a rule + // manually in Settings). return mZenModeHelper.updateAutomaticZenRule(id, automaticZenRule, - "updateAutomaticZenRule", Binder.getCallingUid(), - // TODO: b/308670715: Distinguish FROM_APP from FROM_USER - isCallerSystemOrSystemUi() ? ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI - : ZenModeHelper.FROM_APP); + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + "updateAutomaticZenRule", Binder.getCallingUid()); } @Override @@ -5392,8 +5394,12 @@ public class NotificationManagerService extends SystemService { // Verify that they can modify zen rules. enforcePolicyAccess(Binder.getCallingUid(), "removeAutomaticZenRule"); - return mZenModeHelper.removeAutomaticZenRule(id, "removeAutomaticZenRule", - Binder.getCallingUid(), isCallerSystemOrSystemUi()); + // TODO: b/308670715: Distinguish origin properly (e.g. USER if removing a rule + // manually in Settings). + return mZenModeHelper.removeAutomaticZenRule(id, + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + "removeAutomaticZenRule", Binder.getCallingUid()); } @Override @@ -5402,8 +5408,9 @@ public class NotificationManagerService extends SystemService { enforceSystemOrSystemUI("removeAutomaticZenRules"); return mZenModeHelper.removeAutomaticZenRules(packageName, - packageName + "|removeAutomaticZenRules", Binder.getCallingUid(), - isCallerSystemOrSystemUi()); + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + packageName + "|removeAutomaticZenRules", Binder.getCallingUid()); } @Override @@ -5421,8 +5428,12 @@ public class NotificationManagerService extends SystemService { enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState"); - mZenModeHelper.setAutomaticZenRuleState(id, condition, Binder.getCallingUid(), - isCallerSystemOrSystemUi()); + // TODO: b/308670715: Distinguish origin properly (e.g. USER if toggling a rule + // manually in Settings). + mZenModeHelper.setAutomaticZenRuleState(id, condition, + isCallerSystemOrSystemUi() ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + Binder.getCallingUid()); } @Override @@ -5440,8 +5451,11 @@ public class NotificationManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { - mZenModeHelper.setManualZenMode(zen, null, pkg, "setInterruptionFilter", - callingUid, isSystemOrSystemUi); + mZenModeHelper.setManualZenMode(zen, null, + isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + /* reason= */ "setInterruptionFilter", /* caller= */ pkg, + callingUid); } finally { Binder.restoreCallingIdentity(identity); } @@ -5806,7 +5820,10 @@ public class NotificationManagerService extends SystemService { } else { ZenLog.traceSetNotificationPolicy(pkg, applicationInfo.targetSdkVersion, policy); - mZenModeHelper.setNotificationPolicy(policy, callingUid, isSystemOrSystemUi); + mZenModeHelper.setNotificationPolicy(policy, + isSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + callingUid); } } catch (RemoteException e) { Slog.e(TAG, "Failed to set notification policy", e); diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 6a7eebb32c8b..fd86870e5163 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -59,6 +59,7 @@ import android.provider.Settings; import android.service.notification.ConversationChannelWrapper; import android.service.notification.NotificationListenerService; import android.service.notification.RankingHelperProto; +import android.service.notification.ZenModeConfig; import android.text.TextUtils; import android.text.format.DateUtils; import android.util.ArrayMap; @@ -1862,12 +1863,16 @@ public class PreferencesHelper implements RankingConfig { public void updateZenPolicy(boolean areChannelsBypassingDnd, int callingUid, boolean fromSystemOrSystemUi) { NotificationManager.Policy policy = mZenModeHelper.getNotificationPolicy(); - mZenModeHelper.setNotificationPolicy(new NotificationManager.Policy( - policy.priorityCategories, policy.priorityCallSenders, - policy.priorityMessageSenders, policy.suppressedVisualEffects, - (areChannelsBypassingDnd ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND - : 0), - policy.priorityConversationSenders), callingUid, fromSystemOrSystemUi); + mZenModeHelper.setNotificationPolicy( + new NotificationManager.Policy( + policy.priorityCategories, policy.priorityCallSenders, + policy.priorityMessageSenders, policy.suppressedVisualEffects, + (areChannelsBypassingDnd + ? NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND : 0), + policy.priorityConversationSenders), + fromSystemOrSystemUi ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + callingUid); } // TODO: b/310620812 - rename to hasPriorityChannels() when modes_api is inlined. diff --git a/services/core/java/com/android/server/notification/ZenModeConditions.java b/services/core/java/com/android/server/notification/ZenModeConditions.java index 6ecd799bf8e6..86aa2d8b0a10 100644 --- a/services/core/java/com/android/server/notification/ZenModeConditions.java +++ b/services/core/java/com/android/server/notification/ZenModeConditions.java @@ -111,8 +111,10 @@ public class ZenModeConditions implements ConditionProviders.Callback { public void onServiceAdded(ComponentName component) { if (DEBUG) Log.d(TAG, "onServiceAdded " + component); final int callingUid = Binder.getCallingUid(); - mHelper.setConfig(mHelper.getConfig(), component, "zmc.onServiceAdded:" + component, - callingUid, callingUid == Process.SYSTEM_UID); + mHelper.setConfig(mHelper.getConfig(), component, + callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + "zmc.onServiceAdded:" + component, callingUid); } @Override @@ -121,8 +123,10 @@ public class ZenModeConditions implements ConditionProviders.Callback { ZenModeConfig config = mHelper.getConfig(); if (config == null) return; final int callingUid = Binder.getCallingUid(); - mHelper.setAutomaticZenRuleState(id, condition, callingUid, - callingUid == Process.SYSTEM_UID); + mHelper.setAutomaticZenRuleState(id, condition, + callingUid == Process.SYSTEM_UID ? ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + : ZenModeConfig.UPDATE_ORIGIN_APP, + callingUid); } // Only valid for CPS backed rules diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 218519fef68b..7ec94c315798 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2014, The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,11 +24,16 @@ import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_REMOVED; import static android.app.NotificationManager.AUTOMATIC_RULE_STATUS_UNKNOWN; import static android.app.NotificationManager.Policy.PRIORITY_SENDERS_ANY; import static android.service.notification.NotificationServiceProto.ROOT_CONFIG; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_RESTORE_BACKUP; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static com.android.internal.util.FrameworkStatsLog.DND_MODE_RULE; import android.annotation.DrawableRes; -import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SuppressLint; @@ -79,6 +84,7 @@ import android.service.notification.ConditionProviderService; import android.service.notification.DeviceEffectsApplier; import android.service.notification.ZenDeviceEffects; import android.service.notification.ZenModeConfig; +import android.service.notification.ZenModeConfig.ConfigChangeOrigin; import android.service.notification.ZenModeConfig.ZenRule; import android.service.notification.ZenModeProto; import android.service.notification.ZenPolicy; @@ -111,8 +117,6 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -137,21 +141,6 @@ public class ZenModeHelper { @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) static final long SEND_ACTIVATION_AZR_STATUSES = 308673617L; - /** A rule addition or update that is initiated by the System or SystemUI. */ - static final int FROM_SYSTEM_OR_SYSTEMUI = 1; - /** A rule addition or update that is initiated by the user (through system settings). */ - static final int FROM_USER = 2; - /** A rule addition or update that is initiated by an app (via NotificationManager APIs). */ - static final int FROM_APP = 3; - - @IntDef(prefix = { "FROM_" }, value = { - FROM_SYSTEM_OR_SYSTEMUI, - FROM_USER, - FROM_APP - }) - @Retention(RetentionPolicy.SOURCE) - @interface ChangeOrigin {} - // pkg|userId => uid @VisibleForTesting protected final ArrayMap<String, Integer> mRulesUidCache = new ArrayMap<>(); @@ -160,7 +149,7 @@ public class ZenModeHelper { private final SettingsObserver mSettingsObserver; private final AppOpsManager mAppOps; private final NotificationManager mNotificationManager; - private ZenModeConfig mDefaultConfig; + private final ZenModeConfig mDefaultConfig; private final ArrayList<Callback> mCallbacks = new ArrayList<Callback>(); private final ZenModeFiltering mFiltering; private final RingerModeDelegate mRingerModeDelegate = new @@ -275,9 +264,8 @@ public class ZenModeHelper { // "update" config to itself, which will have no effect in the case where a config // was read in via XML, but will initialize zen mode if nothing was read in and the // config remains the default. - updateConfigAndZenModeLocked(mConfig, "init", true /*setRingerMode*/, - Process.SYSTEM_UID /* callingUid */, true /* is system */, - false /* no broadcasts*/); + updateConfigAndZenModeLocked(mConfig, UPDATE_ORIGIN_INIT, "init", + true /*setRingerMode*/, Process.SYSTEM_UID /* callingUid */); } } @@ -297,24 +285,20 @@ public class ZenModeHelper { /** * Set the {@link DeviceEffectsApplier} used to apply the consolidated effects. * - * <p>If effects were calculated previously (for example, when we loaded a {@link ZenModeConfig} - * that includes activated rules), they will be applied immediately. + * <p>Previously calculated effects (as loaded from the user's {@link ZenModeConfig}) will be + * applied immediately. */ void setDeviceEffectsApplier(@NonNull DeviceEffectsApplier deviceEffectsApplier) { if (!Flags.modesApi()) { return; } - ZenDeviceEffects consolidatedDeviceEffects; synchronized (mConfigLock) { if (mDeviceEffectsApplier != null) { throw new IllegalStateException("Already set up a DeviceEffectsApplier!"); } mDeviceEffectsApplier = deviceEffectsApplier; - consolidatedDeviceEffects = mConsolidatedDeviceEffects; - } - if (consolidatedDeviceEffects.hasEffects()) { - applyConsolidatedDeviceEffects(); } + applyConsolidatedDeviceEffects(UPDATE_ORIGIN_INIT); } public void onUserSwitched(int user) { @@ -353,7 +337,8 @@ public class ZenModeHelper { config.user = user; } synchronized (mConfigLock) { - setConfigLocked(config, null, reason, Process.SYSTEM_UID, true); + setConfigLocked(config, null, UPDATE_ORIGIN_INIT_USER, reason, + Process.SYSTEM_UID); } cleanUpZenRules(); } @@ -366,9 +351,11 @@ public class ZenModeHelper { boolean fromSystemOrSystemUi) { final int newZen = NotificationManager.zenModeFromInterruptionFilter(filter, -1); if (newZen != -1) { - setManualZenMode(newZen, null, name != null ? name.getPackageName() : null, - "listener:" + (name != null ? name.flattenToShortString() : null), - callingUid, fromSystemOrSystemUi); + setManualZenMode(newZen, null, + fromSystemOrSystemUi ? UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI : UPDATE_ORIGIN_APP, + /* reason= */ "listener:" + (name != null ? name.flattenToShortString() : null), + /* caller= */ name != null ? name.getPackageName() : null, + callingUid); } } @@ -428,7 +415,7 @@ public class ZenModeHelper { } public String addAutomaticZenRule(String pkg, AutomaticZenRule automaticZenRule, - String reason, int callingUid, @ChangeOrigin int origin) { + @ConfigChangeOrigin int origin, String reason, int callingUid) { if (!ZenModeConfig.SYSTEM_AUTHORITY.equals(pkg)) { PackageItemInfo component = getServiceInfo(automaticZenRule.getOwner()); if (component == null) { @@ -462,10 +449,9 @@ public class ZenModeHelper { } newConfig = mConfig.copy(); ZenRule rule = new ZenRule(); - populateZenRule(pkg, automaticZenRule, rule, true, origin); + populateZenRule(pkg, automaticZenRule, rule, origin, /* isNew= */ true); newConfig.automaticRules.put(rule.id, rule); - if (setConfigLocked(newConfig, reason, rule.component, true, callingUid, - origin == FROM_SYSTEM_OR_SYSTEMUI)) { + if (setConfigLocked(newConfig, origin, reason, rule.component, true, callingUid)) { return rule.id; } else { throw new AndroidRuntimeException("Could not create rule"); @@ -474,7 +460,7 @@ public class ZenModeHelper { } public boolean updateAutomaticZenRule(String ruleId, AutomaticZenRule automaticZenRule, - String reason, int callingUid, @ChangeOrigin int origin) { + @ConfigChangeOrigin int origin, String reason, int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -502,9 +488,9 @@ public class ZenModeHelper { } } - populateZenRule(rule.pkg, automaticZenRule, rule, false, origin); - return setConfigLocked(newConfig, reason, rule.component, true, callingUid, - origin == FROM_SYSTEM_OR_SYSTEMUI); + populateZenRule(rule.pkg, automaticZenRule, rule, origin, /* isNew= */ false); + return setConfigLocked(newConfig, origin, reason, + rule.component, true, callingUid); } } @@ -541,8 +527,7 @@ public class ZenModeHelper { Condition deactivated = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_deactivated), Condition.STATE_FALSE); - setAutomaticZenRuleState(rule.id, deactivated, - callingUid, /* fromSystemOrSystemUi= */ false); + setAutomaticZenRuleState(rule.id, deactivated, UPDATE_ORIGIN_APP, callingUid); } } else { // Either create a new rule with a default ZenPolicy, or update an existing rule's @@ -558,9 +543,8 @@ public class ZenModeHelper { rule.condition = new Condition(rule.conditionId, mContext.getString(R.string.zen_mode_implicit_activated), Condition.STATE_TRUE); - setConfigLocked(newConfig, /* triggeringComponent= */ null, - "applyGlobalZenModeAsImplicitZenRule", - callingUid, /* fromSystemOrSystemUi= */ false); + setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP, + "applyGlobalZenModeAsImplicitZenRule", callingUid); } } } @@ -595,9 +579,8 @@ public class ZenModeHelper { } // TODO: b/308673679 - Keep user customization of this rule! rule.zenPolicy = ZenAdapters.notificationPolicyToZenPolicy(policy); - setConfigLocked(newConfig, /* triggeringComponent= */ null, - "applyGlobalPolicyAsImplicitZenRule", - callingUid, /* fromSystemOrSystemUi= */ false); + setConfigLocked(newConfig, /* triggeringComponent= */ null, UPDATE_ORIGIN_APP, + "applyGlobalPolicyAsImplicitZenRule", callingUid); } } @@ -669,8 +652,8 @@ public class ZenModeHelper { return "implicit_" + forPackage; } - public boolean removeAutomaticZenRule(String id, String reason, int callingUid, - boolean fromSystemOrSystemUi) { + boolean removeAutomaticZenRule(String id, @ConfigChangeOrigin int origin, String reason, + int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -695,13 +678,12 @@ public class ZenModeHelper { } dispatchOnAutomaticRuleStatusChanged( mConfig.user, ruleToRemove.getPkg(), id, AUTOMATIC_RULE_STATUS_REMOVED); - return setConfigLocked(newConfig, reason, null, true, callingUid, - fromSystemOrSystemUi); + return setConfigLocked(newConfig, origin, reason, null, true, callingUid); } } - public boolean removeAutomaticZenRules(String packageName, String reason, int callingUid, - boolean fromSystemOrSystemUi) { + boolean removeAutomaticZenRules(String packageName, @ConfigChangeOrigin int origin, + String reason, int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return false; @@ -712,13 +694,12 @@ public class ZenModeHelper { newConfig.automaticRules.removeAt(i); } } - return setConfigLocked(newConfig, reason, null, true, callingUid, - fromSystemOrSystemUi); + return setConfigLocked(newConfig, origin, reason, null, true, callingUid); } } - public void setAutomaticZenRuleState(String id, Condition condition, int callingUid, - boolean fromSystemOrSystemUi) { + void setAutomaticZenRuleState(String id, Condition condition, @ConfigChangeOrigin int origin, + int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -726,13 +707,12 @@ public class ZenModeHelper { newConfig = mConfig.copy(); ArrayList<ZenRule> rules = new ArrayList<>(); rules.add(newConfig.automaticRules.get(id)); - setAutomaticZenRuleStateLocked(newConfig, rules, condition, callingUid, - fromSystemOrSystemUi); + setAutomaticZenRuleStateLocked(newConfig, rules, condition, origin, callingUid); } } - public void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, int callingUid, - boolean fromSystemOrSystemUi) { + void setAutomaticZenRuleState(Uri ruleDefinition, Condition condition, + @ConfigChangeOrigin int origin, int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -740,20 +720,23 @@ public class ZenModeHelper { setAutomaticZenRuleStateLocked(newConfig, findMatchingRules(newConfig, ruleDefinition, condition), - condition, callingUid, fromSystemOrSystemUi); + condition, origin, callingUid); } } @GuardedBy("mConfigLock") private void setAutomaticZenRuleStateLocked(ZenModeConfig config, List<ZenRule> rules, - Condition condition, int callingUid, boolean fromSystemOrSystemUi) { + Condition condition, @ConfigChangeOrigin int origin, int callingUid) { if (rules == null || rules.isEmpty()) return; + if (Flags.modesApi() && condition.source == Condition.SOURCE_USER_ACTION) { + origin = UPDATE_ORIGIN_USER; // Although coming from app, it's actually a user action. + } + for (ZenRule rule : rules) { rule.condition = condition; updateSnoozing(rule); - setConfigLocked(config, rule.component, "conditionChanged", callingUid, - fromSystemOrSystemUi); + setConfigLocked(config, rule.component, origin, "conditionChanged", callingUid); } } @@ -857,7 +840,7 @@ public class ZenModeHelper { // update default rule (if locale changed, name of rule will change) currRule.name = defaultRule.name; updateAutomaticZenRule(defaultRule.id, zenRuleToAutomaticZenRule(currRule), - "locale changed", callingUid, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "locale changed", callingUid); } } } @@ -899,13 +882,12 @@ public class ZenModeHelper { return null; } - @VisibleForTesting void populateZenRule(String pkg, AutomaticZenRule automaticZenRule, ZenRule rule, - boolean isNew, @ChangeOrigin int origin) { - // TODO: b/308671593,b/311406021 - Handle origins more precisely: - // - FROM_USER can override anything and updates bitmask of user-modified fields; - // - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; - // - FROM_APP can only update if not user-modified. + @ConfigChangeOrigin int origin, boolean isNew) { + // TODO: b/308671593,b/311406021 - Handle origins more precisely: + // - USER can override anything and updates bitmask of user-modified fields; + // - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; + // - APP can only update if not user-modified. if (rule.enabled != automaticZenRule.isEnabled()) { rule.snoozing = false; } @@ -951,12 +933,12 @@ public class ZenModeHelper { */ @Nullable private static ZenDeviceEffects fixZenDeviceEffects(@Nullable ZenDeviceEffects oldEffects, - @Nullable ZenDeviceEffects newEffects, @ChangeOrigin int origin) { + @Nullable ZenDeviceEffects newEffects, @ConfigChangeOrigin int origin) { // TODO: b/308671593,b/311406021 - Handle origins more precisely: - // - FROM_USER can override anything and updates bitmask of user-modified fields; - // - FROM_SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; - // - FROM_APP can only update if not user-modified. - if (origin == FROM_SYSTEM_OR_SYSTEMUI || origin == FROM_USER) { + // - USER can override anything and updates bitmask of user-modified fields; + // - SYSTEM_OR_SYSTEMUI can override anything and preserves bitmask; + // - APP can only update if not user-modified. + if (origin != UPDATE_ORIGIN_APP) { return newEffects; } @@ -1033,16 +1015,16 @@ public class ZenModeHelper { : AUTOMATIC_RULE_STATUS_DISABLED); } - public void setManualZenMode(int zenMode, Uri conditionId, String caller, String reason, - int callingUid, boolean fromSystemOrSystemUi) { - setManualZenMode(zenMode, conditionId, reason, caller, true /*setRingerMode*/, callingUid, - fromSystemOrSystemUi); + void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin, + String reason, @Nullable String caller, int callingUid) { + setManualZenMode(zenMode, conditionId, origin, reason, caller, true /*setRingerMode*/, + callingUid); Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.SHOW_ZEN_SETTINGS_SUGGESTION, 0); } - private void setManualZenMode(int zenMode, Uri conditionId, String reason, String caller, - boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi) { + private void setManualZenMode(int zenMode, Uri conditionId, @ConfigChangeOrigin int origin, + String reason, @Nullable String caller, boolean setRingerMode, int callingUid) { ZenModeConfig newConfig; synchronized (mConfigLock) { if (mConfig == null) return; @@ -1069,8 +1051,7 @@ public class ZenModeHelper { } newConfig.manualRule = newRule; } - setConfigLocked(newConfig, reason, null, setRingerMode, callingUid, - fromSystemOrSystemUi); + setConfigLocked(newConfig, origin, reason, null, setRingerMode, callingUid); } } @@ -1199,7 +1180,9 @@ public class ZenModeHelper { } if (DEBUG) Log.d(TAG, reason); synchronized (mConfigLock) { - setConfigLocked(config, null, reason, Process.SYSTEM_UID, true); + setConfigLocked(config, null, + forRestore ? UPDATE_ORIGIN_RESTORE_BACKUP : UPDATE_ORIGIN_INIT, reason, + Process.SYSTEM_UID); } } } @@ -1233,13 +1216,13 @@ public class ZenModeHelper { /** * Sets the global notification policy used for priority only do not disturb */ - public void setNotificationPolicy(Policy policy, int callingUid, boolean fromSystemOrSystemUi) { + public void setNotificationPolicy(Policy policy, @ConfigChangeOrigin int origin, + int callingUid) { synchronized (mConfigLock) { if (policy == null || mConfig == null) return; final ZenModeConfig newConfig = mConfig.copy(); newConfig.applyNotificationPolicy(policy); - setConfigLocked(newConfig, null, "setNotificationPolicy", callingUid, - fromSystemOrSystemUi); + setConfigLocked(newConfig, null, origin, "setNotificationPolicy", callingUid); } } @@ -1264,8 +1247,8 @@ public class ZenModeHelper { } } } - setConfigLocked(newConfig, null, "cleanUpZenRules", Process.SYSTEM_UID, - true); + setConfigLocked(newConfig, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "cleanUpZenRules", + Process.SYSTEM_UID); } } @@ -1287,22 +1270,22 @@ public class ZenModeHelper { @GuardedBy("mConfigLock") private boolean setConfigLocked(ZenModeConfig config, ComponentName triggeringComponent, - String reason, int callingUid, boolean fromSystemOrSystemUi) { - return setConfigLocked(config, reason, triggeringComponent, true /*setRingerMode*/, - callingUid, fromSystemOrSystemUi); + @ConfigChangeOrigin int origin, String reason, int callingUid) { + return setConfigLocked(config, origin, reason, triggeringComponent, true /*setRingerMode*/, + callingUid); } - public void setConfig(ZenModeConfig config, ComponentName triggeringComponent, String reason, - int callingUid, boolean fromSystemOrSystemUi) { + void setConfig(ZenModeConfig config, ComponentName triggeringComponent, + @ConfigChangeOrigin int origin, String reason, int callingUid) { synchronized (mConfigLock) { - setConfigLocked(config, triggeringComponent, reason, callingUid, fromSystemOrSystemUi); + setConfigLocked(config, triggeringComponent, origin, reason, callingUid); } } @GuardedBy("mConfigLock") - private boolean setConfigLocked(ZenModeConfig config, String reason, - ComponentName triggeringComponent, boolean setRingerMode, int callingUid, - boolean fromSystemOrSystemUi) { + private boolean setConfigLocked(ZenModeConfig config, @ConfigChangeOrigin int origin, + String reason, ComponentName triggeringComponent, boolean setRingerMode, + int callingUid) { final long identity = Binder.clearCallingIdentity(); try { if (config == null || !config.isValid()) { @@ -1332,8 +1315,7 @@ public class ZenModeHelper { if (policyChanged) { dispatchOnPolicyChanged(); } - updateConfigAndZenModeLocked(config, reason, setRingerMode, callingUid, - fromSystemOrSystemUi, true); + updateConfigAndZenModeLocked(config, origin, reason, setRingerMode, callingUid); mConditions.evaluateConfig(config, triggeringComponent, true /*processSubscriptions*/); return true; } catch (SecurityException e) { @@ -1349,17 +1331,16 @@ public class ZenModeHelper { * If logging is enabled, will also request logging of the outcome of this change if needed. */ @GuardedBy("mConfigLock") - private void updateConfigAndZenModeLocked(ZenModeConfig config, String reason, - boolean setRingerMode, int callingUid, boolean fromSystemOrSystemUi, - boolean sendBroadcasts) { + private void updateConfigAndZenModeLocked(ZenModeConfig config, @ConfigChangeOrigin int origin, + String reason, boolean setRingerMode, int callingUid) { final boolean logZenModeEvents = mFlagResolver.isEnabled( SystemUiSystemPropertiesFlags.NotificationFlags.LOG_DND_STATE_EVENTS); // Store (a copy of) all config and zen mode info prior to any changes taking effect ZenModeEventLogger.ZenModeInfo prevInfo = new ZenModeEventLogger.ZenModeInfo( mZenMode, mConfig, mConsolidatedPolicy); if (!config.equals(mConfig)) { - // schedule broadcasts - if (Flags.modesApi() && sendBroadcasts) { + // Schedule broadcasts. Cannot be sent during boot, though. + if (Flags.modesApi() && origin != UPDATE_ORIGIN_INIT) { for (ZenRule rule : config.automaticRules.values()) { ZenRule original = mConfig.automaticRules.get(rule.id); if (original != null) { @@ -1377,15 +1358,19 @@ public class ZenModeHelper { mConfig = config; dispatchOnConfigChanged(); - updateAndApplyConsolidatedPolicyAndDeviceEffects(reason); + updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason); } final String val = Integer.toString(config.hashCode()); Global.putString(mContext.getContentResolver(), Global.ZEN_MODE_CONFIG_ETAG, val); - evaluateZenModeLocked(reason, setRingerMode); + evaluateZenModeLocked(origin, reason, setRingerMode); // After all changes have occurred, log if requested if (logZenModeEvents) { ZenModeEventLogger.ZenModeInfo newInfo = new ZenModeEventLogger.ZenModeInfo( mZenMode, mConfig, mConsolidatedPolicy); + boolean fromSystemOrSystemUi = origin == UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI + || origin == UPDATE_ORIGIN_INIT + || origin == UPDATE_ORIGIN_INIT_USER + || origin == UPDATE_ORIGIN_RESTORE_BACKUP; mZenModeEventLogger.maybeLogZenChange(prevInfo, newInfo, callingUid, fromSystemOrSystemUi); } @@ -1416,7 +1401,8 @@ public class ZenModeHelper { @VisibleForTesting @GuardedBy("mConfigLock") - protected void evaluateZenModeLocked(String reason, boolean setRingerMode) { + protected void evaluateZenModeLocked(@ConfigChangeOrigin int origin, String reason, + boolean setRingerMode) { if (DEBUG) Log.d(TAG, "evaluateZenMode"); if (mConfig == null) return; final int policyHashBefore = mConsolidatedPolicy == null ? 0 @@ -1426,7 +1412,7 @@ public class ZenModeHelper { ZenLog.traceSetZenMode(zen, reason); mZenMode = zen; setZenModeSetting(mZenMode); - updateAndApplyConsolidatedPolicyAndDeviceEffects(reason); + updateAndApplyConsolidatedPolicyAndDeviceEffects(origin, reason); boolean shouldApplyToRinger = setRingerMode && (zen != zenBefore || ( zen == Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS && policyHashBefore != mConsolidatedPolicy.hashCode())); @@ -1468,6 +1454,7 @@ public class ZenModeHelper { } } + @GuardedBy("mConfigLock") private void applyCustomPolicy(ZenPolicy policy, ZenRule rule) { if (rule.zenMode == Global.ZEN_MODE_NO_INTERRUPTIONS) { policy.apply(new ZenPolicy.Builder() @@ -1487,7 +1474,9 @@ public class ZenModeHelper { } } - private void updateAndApplyConsolidatedPolicyAndDeviceEffects(String reason) { + @GuardedBy("mConfigLock") + private void updateAndApplyConsolidatedPolicyAndDeviceEffects(@ConfigChangeOrigin int origin, + String reason) { synchronized (mConfigLock) { if (mConfig == null) return; ZenPolicy policy = new ZenPolicy(); @@ -1519,13 +1508,13 @@ public class ZenModeHelper { ZenDeviceEffects deviceEffects = deviceEffectsBuilder.build(); if (!deviceEffects.equals(mConsolidatedDeviceEffects)) { mConsolidatedDeviceEffects = deviceEffects; - mHandler.postApplyDeviceEffects(); + mHandler.postApplyDeviceEffects(origin); } } } } - private void applyConsolidatedDeviceEffects() { + private void applyConsolidatedDeviceEffects(@ConfigChangeOrigin int source) { if (!Flags.modesApi()) { return; } @@ -1536,7 +1525,7 @@ public class ZenModeHelper { effects = mConsolidatedDeviceEffects; } if (applier != null) { - applier.apply(effects); + applier.apply(effects, source); } } @@ -1801,8 +1790,8 @@ public class ZenModeHelper { } @Override - public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, String caller, - int ringerModeExternal, VolumePolicy policy) { + public int onSetRingerModeInternal(int ringerModeOld, int ringerModeNew, + @Nullable String caller, int ringerModeExternal, VolumePolicy policy) { final boolean isChange = ringerModeOld != ringerModeNew; int ringerModeExternalOut = ringerModeNew; @@ -1839,8 +1828,9 @@ public class ZenModeHelper { } if (newZen != -1) { - setManualZenMode(newZen, null, "ringerModeInternal", null, - false /*setRingerMode*/, Process.SYSTEM_UID, true); + setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "ringerModeInternal", /* caller= */ null, /* setRingerMode= */ false, + Process.SYSTEM_UID); } if (isChange || newZen != -1 || ringerModeExternal != ringerModeExternalOut) { ZenLog.traceSetRingerModeInternal(ringerModeOld, ringerModeNew, caller, @@ -1856,8 +1846,8 @@ public class ZenModeHelper { } @Override - public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, String caller, - int ringerModeInternal, VolumePolicy policy) { + public int onSetRingerModeExternal(int ringerModeOld, int ringerModeNew, + @Nullable String caller, int ringerModeInternal, VolumePolicy policy) { int ringerModeInternalOut = ringerModeNew; final boolean isChange = ringerModeOld != ringerModeNew; final boolean isVibrate = ringerModeInternal == AudioManager.RINGER_MODE_VIBRATE; @@ -1883,8 +1873,8 @@ public class ZenModeHelper { break; } if (newZen != -1) { - setManualZenMode(newZen, null, "ringerModeExternal", caller, - false /*setRingerMode*/, Process.SYSTEM_UID, true); + setManualZenMode(newZen, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "ringerModeExternal", caller, false /*setRingerMode*/, Process.SYSTEM_UID); } ZenLog.traceSetRingerModeExternal(ringerModeOld, ringerModeNew, caller, @@ -2024,11 +2014,9 @@ public class ZenModeHelper { } try { final Resources res = mPm.getResourcesForApplication(packageName); - final String fullName = res.getResourceName(resId); - - return fullName; + return res.getResourceName(resId); } catch (PackageManager.NameNotFoundException | Resources.NotFoundException e) { - Log.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName + Slog.e(TAG, "Resource name for ID=" + resId + " not found in package " + packageName + ". Resource IDs may change when the application is upgraded, and the system" + " may not be able to find the correct resource."); return null; @@ -2149,9 +2137,9 @@ public class ZenModeHelper { sendMessage(obtainMessage(MSG_RINGER_AUDIO, shouldApplyToRinger)); } - private void postApplyDeviceEffects() { + private void postApplyDeviceEffects(@ConfigChangeOrigin int origin) { removeMessages(MSG_APPLY_EFFECTS); - sendEmptyMessage(MSG_APPLY_EFFECTS); + sendMessage(obtainMessage(MSG_APPLY_EFFECTS, origin, 0)); } @Override @@ -2168,7 +2156,8 @@ public class ZenModeHelper { updateRingerAndAudio(shouldApplyToRinger); break; case MSG_APPLY_EFFECTS: - applyConsolidatedDeviceEffects(); + @ConfigChangeOrigin int origin = msg.arg1; + applyConsolidatedDeviceEffects(origin); break; } } 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/AggregatedPowerStatsConfig.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java index 43fd15d690e6..6fbbc0f072e8 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsConfig.java @@ -19,7 +19,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.os.BatteryConsumer; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; import java.lang.annotation.Retention; diff --git a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java index 5fd8ddfbf240..7feb9643fb8f 100644 --- a/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java +++ b/services/core/java/com/android/server/power/stats/AggregatedPowerStatsProcessor.java @@ -19,7 +19,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.util.Log; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; import java.util.ArrayList; 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/core/java/com/android/internal/os/MultiStateStats.java b/services/core/java/com/android/server/power/stats/MultiStateStats.java index ecfed537b798..935695008a9a 100644 --- a/core/java/com/android/internal/os/MultiStateStats.java +++ b/services/core/java/com/android/server/power/stats/MultiStateStats.java @@ -14,11 +14,12 @@ * limitations under the License. */ -package com.android.internal.os; +package com.android.server.power.stats; import android.util.Slog; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.os.LongArrayMultiStateCounter; import com.android.internal.util.Preconditions; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java index 0facb9c01d74..1637022f705d 100644 --- a/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java +++ b/services/core/java/com/android/server/power/stats/PowerComponentAggregatedPowerStats.java @@ -21,7 +21,6 @@ import android.annotation.Nullable; import android.util.IndentingPrintWriter; import android.util.SparseArray; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; import com.android.modules.utils.TypedXmlPullParser; import com.android.modules.utils.TypedXmlSerializer; diff --git a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java index 1f6f11320f1b..c267b799e2a4 100644 --- a/services/core/java/com/android/server/power/stats/PowerStatsExporter.java +++ b/services/core/java/com/android/server/power/stats/PowerStatsExporter.java @@ -22,7 +22,6 @@ import android.os.BatteryUsageStats; import android.os.UidBatteryConsumer; import android.util.Slog; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerStats; import java.util.ArrayList; diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java index d172d3fd4139..eac4fc00b667 100644 --- a/services/core/java/com/android/server/trust/TrustManagerService.java +++ b/services/core/java/com/android/server/trust/TrustManagerService.java @@ -865,21 +865,19 @@ public class TrustManagerService extends SystemService { mDeviceLockedForUser.put(userId, locked); } if (changed) { - dispatchDeviceLocked(userId, locked); - Authorization.onLockScreenEvent(locked, userId, null, - getBiometricSids(userId)); + notifyTrustAgentsOfDeviceLockState(userId, locked); + notifyKeystoreOfDeviceLockState(userId, locked); // Also update the user's profiles who have unified challenge, since they // share the same unlocked state (see {@link #isDeviceLocked(int)}) for (int profileHandle : mUserManager.getEnabledProfileIds(userId)) { if (mLockPatternUtils.isManagedProfileWithUnifiedChallenge(profileHandle)) { - Authorization.onLockScreenEvent(locked, profileHandle, null, - getBiometricSids(profileHandle)); + notifyKeystoreOfDeviceLockState(profileHandle, locked); } } } } - private void dispatchDeviceLocked(int userId, boolean isLocked) { + private void notifyTrustAgentsOfDeviceLockState(int userId, boolean isLocked) { for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo agent = mActiveAgents.valueAt(i); if (agent.userId == userId) { @@ -892,6 +890,17 @@ public class TrustManagerService extends SystemService { } } + private void notifyKeystoreOfDeviceLockState(int userId, boolean isLocked) { + if (isLocked) { + Authorization.onDeviceLocked(userId, getBiometricSids(userId)); + } else { + // Notify Keystore that the device is now unlocked for the user. Note that for unlocks + // with LSKF, this is redundant with the call from LockSettingsService which provides + // the password. However, for unlocks with biometric or trust agent, this is required. + Authorization.onDeviceUnlocked(userId, /* password= */ null); + } + } + private void dispatchEscrowTokenActivatedLocked(long handle, int userId) { for (int i = 0; i < mActiveAgents.size(); i++) { AgentInfo agent = mActiveAgents.valueAt(i); @@ -1425,10 +1434,10 @@ public class TrustManagerService extends SystemService { } } - private long[] getBiometricSids(int userId) { + private @NonNull long[] getBiometricSids(int userId) { BiometricManager biometricManager = mContext.getSystemService(BiometricManager.class); if (biometricManager == null) { - return null; + return new long[0]; } return biometricManager.getAuthenticatorIds(userId); } @@ -1680,8 +1689,7 @@ public class TrustManagerService extends SystemService { mDeviceLockedForUser.put(userId, locked); } - Authorization.onLockScreenEvent(locked, userId, null, - getBiometricSids(userId)); + notifyKeystoreOfDeviceLockState(userId, locked); if (locked) { try { diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index e4c7fc1f3797..6f2750767094 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -1851,14 +1851,14 @@ public final class TvInputManagerService extends SystemService { sessionState.currentChannel = channelUri; notifyCurrentChannelInfosUpdatedLocked(userState); if (!sessionState.isRecordingSession) { - String actualInputId = getActualInputId(sessionState); - if (!TextUtils.equals(mOnScreenInputId, actualInputId)) { + String sessionActualInputId = getSessionActualInputId(sessionState); + if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) { logExternalInputEvent( FrameworkStatsLog .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED, - actualInputId, sessionState); + sessionActualInputId, sessionState); } - mOnScreenInputId = actualInputId; + mOnScreenInputId = sessionActualInputId; mOnScreenSessionState = sessionState; } } @@ -2985,11 +2985,20 @@ public final class TvInputManagerService extends SystemService { // e.g. if an HDMI port has a CEC device plugged in, the actual input id of the HDMI // session should be the input id of CEC device instead of the default HDMI input id. @GuardedBy("mLock") - private String getActualInputId(SessionState sessionState) { + private String getSessionActualInputId(SessionState sessionState) { UserState userState = getOrCreateUserStateLocked(sessionState.userId); TvInputState tvInputState = userState.inputMap.get(sessionState.inputId); + if (tvInputState == null) { + Slog.w(TAG, "No TvInputState for sessionState.inputId " + sessionState.inputId); + return sessionState.inputId; + } TvInputInfo tvInputInfo = tvInputState.info; - String actualInputId = sessionState.inputId; + if (tvInputInfo == null) { + Slog.w(TAG, "TvInputInfo is null for input id " + sessionState.inputId); + return sessionState.inputId; + } + + String sessionActualInputId = sessionState.inputId; switch (tvInputInfo.getType()) { case TvInputInfo.TYPE_HDMI: // TODO: find a better approach towards active CEC device in future @@ -2997,13 +3006,13 @@ public final class TvInputManagerService extends SystemService { mTvInputHardwareManager.getHdmiParentInputMap(); if (hdmiParentInputMap.containsKey(sessionState.inputId)) { List<String> parentInputList = hdmiParentInputMap.get(sessionState.inputId); - actualInputId = parentInputList.get(0); + sessionActualInputId = parentInputList.get(0); } break; default: break; } - return actualInputId; + return sessionActualInputId; } @Nullable @@ -3111,7 +3120,21 @@ public final class TvInputManagerService extends SystemService { private void logExternalInputEvent(int eventType, String inputId, SessionState sessionState) { UserState userState = getOrCreateUserStateLocked(sessionState.userId); TvInputState tvInputState = userState.inputMap.get(inputId); + if (tvInputState == null) { + Slog.w(TAG, "Cannot find input state for input id " + inputId); + // If input id is not found, try to find the input id of this sessionState. + inputId = sessionState.inputId; + tvInputState = userState.inputMap.get(inputId); + } + if (tvInputState == null) { + Slog.w(TAG, "Cannot find input state for sessionState.inputId " + inputId); + return; + } TvInputInfo tvInputInfo = tvInputState.info; + if (tvInputInfo == null) { + Slog.w(TAG, "TvInputInfo is null for input id " + inputId); + return; + } int inputState = tvInputState.state; int inputType = tvInputInfo.getType(); String displayName = tvInputInfo.loadLabel(mContext).toString(); @@ -3647,14 +3670,14 @@ public final class TvInputManagerService extends SystemService { mSessionState.currentChannel = channelUri; notifyCurrentChannelInfosUpdatedLocked(userState); if (!mSessionState.isRecordingSession) { - String actualInputId = getActualInputId(mSessionState); - if (!TextUtils.equals(mOnScreenInputId, actualInputId)) { + String sessionActualInputId = getSessionActualInputId(mSessionState); + if (!TextUtils.equals(mOnScreenInputId, sessionActualInputId)) { logExternalInputEvent( FrameworkStatsLog .EXTERNAL_TV_INPUT_EVENT__EVENT_TYPE__TUNED, - actualInputId, mSessionState); + sessionActualInputId, mSessionState); } - mOnScreenInputId = actualInputId; + mOnScreenInputId = sessionActualInputId; mOnScreenSessionState = mSessionState; } } diff --git a/services/core/java/com/android/server/uri/UriGrantsManagerService.java b/services/core/java/com/android/server/uri/UriGrantsManagerService.java index 7862f58374a3..e501b9dc9959 100644 --- a/services/core/java/com/android/server/uri/UriGrantsManagerService.java +++ b/services/core/java/com/android/server/uri/UriGrantsManagerService.java @@ -146,17 +146,31 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements mGrantedUriPermissions = new SparseArray<>(); private UriGrantsManagerService() { - this(SystemServiceManager.ensureSystemDir()); + this(SystemServiceManager.ensureSystemDir(), "uri-grants"); } - private UriGrantsManagerService(File systemDir) { + private UriGrantsManagerService(File systemDir, String commitTag) { mH = new H(IoThread.get().getLooper()); - mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml"), "uri-grants"); + final File file = new File(systemDir, "urigrants.xml"); + mGrantFile = (commitTag != null) ? new AtomicFile(file, commitTag) : new AtomicFile(file); } @VisibleForTesting static UriGrantsManagerService createForTest(File systemDir) { - final UriGrantsManagerService service = new UriGrantsManagerService(systemDir); + final UriGrantsManagerService service = new UriGrantsManagerService(systemDir, null) { + @VisibleForTesting + protected int checkUidPermission(String permission, int uid) { + // Tests have no permission granted + return PackageManager.PERMISSION_DENIED; + } + + @VisibleForTesting + protected int checkComponentPermission(String permission, int uid, int owningUid, + boolean exported) { + // Tests have no permission granted + return PackageManager.PERMISSION_DENIED; + } + }; service.mAmInternal = LocalServices.getService(ActivityManagerInternal.class); service.mPmInternal = LocalServices.getService(PackageManagerInternal.class); return service; @@ -202,7 +216,8 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements } } - private int checkUidPermission(String permission, int uid) { + @VisibleForTesting + protected int checkUidPermission(String permission, int uid) { try { return AppGlobals.getPackageManager().checkUidPermission(permission, uid); } catch (RemoteException e) { @@ -210,6 +225,12 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements } } + @VisibleForTesting + protected int checkComponentPermission(String permission, int uid, int owningUid, + boolean exported) { + return ActivityManager.checkComponentPermission(permission, uid, owningUid, exported); + } + /** * Grant uri permissions to the specified app. * @@ -916,7 +937,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements ProviderInfo pi, GrantUri grantUri, int uid, final int modeFlags) { if (DEBUG) Slog.v(TAG, "checkHoldingPermissions: uri=" + grantUri + " uid=" + uid); if (UserHandle.getUserId(uid) != grantUri.sourceUserId) { - if (ActivityManager.checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true) + if (checkComponentPermission(INTERACT_ACROSS_USERS, uid, -1, true) != PERMISSION_GRANTED) { return false; } @@ -1340,7 +1361,7 @@ public class UriGrantsManagerService extends IUriGrantsManager.Stub implements if (uid == Process.SYSTEM_UID || uid == Process.ROOT_UID) { return true; } - return ActivityManager.checkComponentPermission( + return checkComponentPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, uid, /* owningUid = */-1, /* exported = */ true) == PackageManager.PERMISSION_GRANTED; diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java index 2b6dffb2b271..7b5192c4bd6b 100644 --- a/services/core/java/com/android/server/utils/AnrTimer.java +++ b/services/core/java/com/android/server/utils/AnrTimer.java @@ -16,37 +16,22 @@ package com.android.server.utils; -import static android.text.TextUtils.formatSimple; - -import android.annotation.IntDef; import android.annotation.NonNull; -import android.annotation.Nullable; import android.os.Handler; -import android.os.Looper; import android.os.Message; -import android.os.Process; import android.os.SystemClock; import android.os.Trace; -import android.text.TextUtils; import android.text.format.TimeMigrationUtils; -import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.Log; -import android.util.MathUtils; -import android.util.SparseArray; import com.android.internal.annotations.GuardedBy; -import com.android.internal.annotations.Keep; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.os.ProcessCpuTracker; import com.android.internal.util.RingBuffer; import java.io.PrintWriter; -import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Arrays; import java.util.Objects; -import java.util.concurrent.atomic.AtomicInteger; /** * This class managers AnrTimers. An AnrTimer is a substitute for a delayed Message. In legacy @@ -102,12 +87,6 @@ public class AnrTimer<V> { private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER; /** - * Enable tracing from the time a timer expires until it is accepted or discarded. This is - * used to diagnose long latencies in the client. - */ - private static final boolean ENABLE_TRACING = false; - - /** * Return true if the feature is enabled. By default, the value is take from the Flags class * but it can be changed for local testing. */ @@ -116,22 +95,13 @@ public class AnrTimer<V> { } /** - * The status of an ANR timer. TIMER_INVALID status is returned when an error is detected. + * This class allows test code to provide instance-specific overrides. */ - private static final int TIMER_INVALID = 0; - private static final int TIMER_RUNNING = 1; - private static final int TIMER_EXPIRED = 2; - - @IntDef(prefix = { "TIMER_" }, value = { - TIMER_INVALID, TIMER_RUNNING, TIMER_EXPIRED - }) - private @interface TimerStatus {} - - /** - * A static list of all known AnrTimer instances, used for dumping and testing. - */ - @GuardedBy("sAnrTimerList") - private static final ArrayList<WeakReference<AnrTimer>> sAnrTimerList = new ArrayList<>(); + static class Injector { + boolean anrTimerServiceEnabled() { + return AnrTimer.anrTimerServiceEnabled(); + } + } /** * An error is defined by its issue, the operation that detected the error, the tag of the @@ -161,6 +131,22 @@ public class AnrTimer<V> { this.arg = arg; this.timestamp = SystemClock.elapsedRealtime(); } + + /** + * Dump a single error to the output stream. + */ + private void dump(IndentingPrintWriter ipw, int seq) { + ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, operation, tag, issue, arg); + + final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); + final long etime = offset + timestamp; + ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime)); + ipw.increaseIndent(); + for (int i = 0; i < stack.length; i++) { + ipw.println(" " + stack[i].toString()); + } + ipw.decreaseIndent(); + } } /** @@ -171,132 +157,10 @@ public class AnrTimer<V> { @GuardedBy("sErrors") private static final RingBuffer<Error> sErrors = new RingBuffer<>(Error.class, 20); - /** - * A record of a single anr timer. The pid and uid are retained for reference but they do not - * participate in the equality tests. A {@link Timer} is bound to its parent {@link AnrTimer} - * through the owner field. Access to timer fields is guarded by the mLock of the owner. - */ - private static class Timer { - /** The AnrTimer that is managing this Timer. */ - final AnrTimer owner; - - /** The argument that uniquely identifies the Timer in the context of its current owner. */ - final Object arg; - /** The pid of the process being tracked by this Timer. */ - final int pid; - /** The uid of the process being tracked by this Timer as reported by the kernel. */ - final int uid; - /** The original timeout. */ - final long timeoutMs; - - /** The status of the Timer. */ - @GuardedBy("owner.mLock") - @TimerStatus - int status; - - /** The absolute time the timer was startd */ - final long startedMs; - - /** Fields used by the native timer service. */ - - /** The timer ID: used to exchange information with the native service. */ - int timerId; - - /** Fields used by the legacy timer service. */ - - /** - * The process's cpu delay time when the timer starts . It is meaningful only if - * extendable is true. The cpu delay is cumulative, so the incremental delay that occurs - * during a timer is the delay at the end of the timer minus this value. Units are in - * milliseconds. - */ - @GuardedBy("owner.mLock") - long initialCpuDelayMs; - - /** True if the timer has been extended. */ - @GuardedBy("owner.mLock") - boolean extended; - - /** - * Fetch a new Timer. This is private. Clients should get a new timer using the obtain() - * method. - */ - private Timer(int pid, int uid, @Nullable Object arg, long timeoutMs, - @NonNull AnrTimer service) { - this.arg = arg; - this.pid = pid; - this.uid = uid; - this.timerId = 0; - this.timeoutMs = timeoutMs; - this.startedMs = now(); - this.owner = service; - this.initialCpuDelayMs = 0; - this.extended = false; - this.status = TIMER_INVALID; - } - - /** Get a timer. This implementation constructs a new timer. */ - static Timer obtain(int pid, int uid, @Nullable Object arg, long timeout, - @NonNull AnrTimer service) { - return new Timer(pid, uid, arg, timeout, service); - } - - /** Release a timer. This implementation simply drops the timer. */ - void release() { - } - - /** Return the age of the timer. This is used for debugging. */ - long age() { - return now() - startedMs; - } - - /** - * The hash code is generated from the owner and the argument. By definition, the - * combination must be unique for the lifetime of an in-use Timer. - */ - @Override - public int hashCode() { - return Objects.hash(owner, arg); - } - - /** - * The equality check compares the owner and the argument. By definition, the combination - * must be unique for the lifetime of an in-use Timer. - */ - @Override - public boolean equals(Object r) { - if (r instanceof Timer) { - Timer t = (Timer) r; - return Objects.equals(owner, t.owner) && Objects.equals(arg, t.arg); - } - return false; - } - - @Override - public String toString() { - final int myStatus; - synchronized (owner.mLock) { - myStatus = status; - } - return "timerId=" + timerId + " pid=" + pid + " uid=" + uid - + " " + statusString(myStatus) + " " + owner.mLabel; - } - } - /** A lock for the AnrTimer instance. */ private final Object mLock = new Object(); /** - * The map from client argument to the associated timer. - */ - @GuardedBy("mLock") - private final ArrayMap<V, Timer> mTimerMap = new ArrayMap<>(); - - /** The highwater mark of started, but not closed, timers. */ - @GuardedBy("mLock") - private int mMaxStarted = 0; - - /** * The total number of timers started. */ @GuardedBy("mLock") @@ -309,176 +173,6 @@ public class AnrTimer<V> { private int mTotalErrors = 0; /** - * The total number of timers that have expired. - */ - @GuardedBy("mLock") - private int mTotalExpired = 0; - - /** - * A TimerService that generates a timeout event <n> milliseconds in the future. See the - * class documentation for an explanation of the operations. - */ - private abstract class TimerService { - /** Start a timer. The timeout must be initialized. */ - abstract boolean start(@NonNull Timer timer); - - abstract void cancel(@NonNull Timer timer); - - abstract void accept(@NonNull Timer timer); - - abstract void discard(@NonNull Timer timer); - } - - /** - * A class to assist testing. All methods are null by default but can be overridden as - * necessary for a test. - */ - @VisibleForTesting - static class Injector { - private final Handler mReferenceHandler; - - Injector(@NonNull Handler handler) { - mReferenceHandler = handler; - } - - /** - * Return a handler for the given Callback, based on the reference handler. The handler - * might be mocked, in which case it does not have a valid Looper. In this case, use the - * main Looper. - */ - @NonNull - Handler newHandler(@NonNull Handler.Callback callback) { - Looper looper = mReferenceHandler.getLooper(); - if (looper == null) looper = Looper.getMainLooper(); - return new Handler(looper, callback); - } - - /** - * Return a CpuTracker. The default behavior is to create a new CpuTracker but this changes - * for unit tests. - **/ - @NonNull - CpuTracker newTracker() { - return new CpuTracker(); - } - - /** Return true if the feature is enabled. */ - boolean isFeatureEnabled() { - return anrTimerServiceEnabled(); - } - } - - /** - * A helper class to measure CPU delays. Given a process ID, this class will return the - * cumulative CPU delay for the PID, since process inception. This class is defined to assist - * testing. - */ - @VisibleForTesting - static class CpuTracker { - /** - * The parameter to ProcessCpuTracker indicates that statistics should be collected on a - * single process and not on the collection of threads associated with that process. - */ - private final ProcessCpuTracker mCpu = new ProcessCpuTracker(false); - - /** A simple wrapper to fetch the delay. This method can be overridden for testing. */ - long delay(int pid) { - return mCpu.getCpuDelayTimeForPid(pid); - } - } - - /** - * The "user-space" implementation of the timer service. This service uses its own message - * handler to create timeouts. - */ - private class HandlerTimerService extends TimerService { - /** The lock for this handler */ - private final Object mLock = new Object(); - - /** The message handler for scheduling future events. */ - private final Handler mHandler; - - /** The interface to fetch process statistics that might extend an ANR timeout. */ - private final CpuTracker mCpu; - - /** Create a HandlerTimerService that directly uses the supplied handler and tracker. */ - @VisibleForTesting - HandlerTimerService(@NonNull Injector injector) { - mHandler = injector.newHandler(this::expires); - mCpu = injector.newTracker(); - } - - /** Post a message with the specified timeout. The timer is not modified. */ - private void post(@NonNull Timer t, long timeoutMillis) { - final Message msg = mHandler.obtainMessage(); - msg.obj = t; - mHandler.sendMessageDelayed(msg, timeoutMillis); - } - - /** - * The local expiration handler first attempts to compute a timer extension. If the timer - * should be extended, it is rescheduled in the future (granting more time to the - * associated process). If the timer should not be extended then the timeout is delivered - * to the client. - * - * A process is extended to account for the time the process was swapped out and was not - * runnable through no fault of its own. A timer can only be extended once and only if - * the AnrTimer permits extensions. Finally, a timer will never be extended by more than - * the original timeout, so the total timeout will never be more than twice the originally - * configured timeout. - */ - private boolean expires(Message msg) { - Timer t = (Timer) msg.obj; - synchronized (mLock) { - long extension = 0; - if (mExtend && !t.extended) { - extension = mCpu.delay(t.pid) - t.initialCpuDelayMs; - if (extension < 0) extension = 0; - if (extension > t.timeoutMs) extension = t.timeoutMs; - t.extended = true; - } - if (extension > 0) { - post(t, extension); - } else { - onExpiredLocked(t); - } - } - return true; - } - - @GuardedBy("mLock") - @Override - boolean start(@NonNull Timer t) { - if (mExtend) { - t.initialCpuDelayMs = mCpu.delay(t.pid); - } - post(t, t.timeoutMs); - return true; - } - - @Override - void cancel(@NonNull Timer t) { - mHandler.removeMessages(0, t); - } - - @Override - void accept(@NonNull Timer t) { - // Nothing to do. - } - - @Override - void discard(@NonNull Timer t) { - // Nothing to do. - } - - /** The string identifies this subclass of AnrTimerService as being based on handlers. */ - @Override - public String toString() { - return "handler"; - } - } - - /** * The handler for messages sent from this instance. */ private final Handler mHandler; @@ -499,18 +193,6 @@ public class AnrTimer<V> { private final boolean mExtend; /** - * The timer service to use for this AnrTimer. - */ - private final TimerService mTimerService; - - /** - * Whether or not canceling a non-existent timer is an error. Clients often cancel freely - * preemptively, without knowing if the timer was ever started. Keeping this variable true - * means that such behavior is not an error. - */ - private final boolean mLenientCancel = true; - - /** * The top-level switch for the feature enabled or disabled. */ private final FeatureSwitch mFeature; @@ -528,40 +210,34 @@ public class AnrTimer<V> { * AnrTimer may extend the individual timer rather than immediately delivering the timeout to * the client. The extension policy is not part of the instance. * - * This method accepts an {@link #Injector} to tune behavior for testing. This method should - * not be called directly by regular clients. - * * @param handler The handler to which the expiration message will be delivered. * @param what The "what" parameter for the expiration message. * @param label A name for this instance. * @param extend A flag to indicate if expired timers can be granted extensions. - * @param injector An {@link #Injector} to tune behavior for testing. + * @param injector An injector to provide overrides for testing. */ @VisibleForTesting AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend, - @NonNull Injector injector) { + @NonNull Injector injector) { mHandler = handler; mWhat = what; mLabel = label; mExtend = extend; - boolean enabled = injector.isFeatureEnabled(); - if (!enabled) { - mFeature = new FeatureDisabled(); - mTimerService = null; - } else { - mFeature = new FeatureEnabled(); - mTimerService = new HandlerTimerService(injector); - - synchronized (sAnrTimerList) { - sAnrTimerList.add(new WeakReference(this)); - } - } - Log.i(TAG, formatSimple("created %s label: \"%s\"", mTimerService, label)); + mFeature = new FeatureDisabled(); } /** - * Create an AnrTimer instance with the default {@link #Injector}. See {@link AnrTimer(Handler, - * int, String, boolean, Injector} for a functional description. + * Create one AnrTimer instance. The instance is given a handler and a "what". Individual + * timers are started with {@link #start}. If a timer expires, then a {@link Message} is sent + * immediately to the handler with {@link Message.what} set to what and {@link Message.obj} set + * to the timer key. + * + * AnrTimer instances have a label, which must be unique. The label is used for reporting and + * debug. + * + * If an individual timer expires internally, and the "extend" parameter is true, then the + * AnrTimer may extend the individual timer rather than immediately delivering the timeout to + * the client. The extension policy is not part of the instance. * * @param handler The handler to which the expiration message will be delivered. * @param what The "what" parameter for the expiration message. @@ -569,7 +245,7 @@ public class AnrTimer<V> { * @param extend A flag to indicate if expired timers can be granted extensions. */ public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) { - this(handler, what, label, extend, new Injector(handler)); + this(handler, what, label, extend, new Injector()); } /** @@ -596,105 +272,17 @@ public class AnrTimer<V> { } /** - * Start a trace on the timer. The trace is laid down in the AnrTimerTrack. - */ - private void traceBegin(Timer t, String what) { - if (ENABLE_TRACING) { - final String label = formatSimple("%s(%d,%d,%s)", what, t.pid, t.uid, mLabel); - final int cookie = t.hashCode(); - Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie); - } - } - - /** - * End a trace on the timer. - */ - private void traceEnd(Timer t) { - if (ENABLE_TRACING) { - final int cookie = t.hashCode(); - Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie); - } - } - - /** - * Return the string representation for a timer status. - */ - private static String statusString(int s) { - switch (s) { - case TIMER_INVALID: return "invalid"; - case TIMER_RUNNING: return "running"; - case TIMER_EXPIRED: return "expired"; - } - return formatSimple("unknown: %d", s); - } - - /** - * Delete the timer associated with arg from the maps and return it. Return null if the timer - * was not found. - */ - @GuardedBy("mLock") - private Timer removeLocked(V arg) { - Timer timer = mTimerMap.remove(arg); - return timer; - } - - /** - * Return the number of timers currently running. - */ - @VisibleForTesting - static int sizeOfTimerList() { - synchronized (sAnrTimerList) { - int totalTimers = 0; - for (int i = 0; i < sAnrTimerList.size(); i++) { - AnrTimer client = sAnrTimerList.get(i).get(); - if (client != null) totalTimers += client.mTimerMap.size(); - } - return totalTimers; - } - } - - /** - * Clear out all existing timers. This will lead to unexpected behavior if used carelessly. - * It is available only for testing. It returns the number of times that were actually - * erased. - */ - @VisibleForTesting - static int resetTimerListForHermeticTest() { - synchronized (sAnrTimerList) { - int mapLen = 0; - for (int i = 0; i < sAnrTimerList.size(); i++) { - AnrTimer client = sAnrTimerList.get(i).get(); - if (client != null) { - mapLen += client.mTimerMap.size(); - client.mTimerMap.clear(); - } - } - if (mapLen > 0) { - Log.w(TAG, formatSimple("erasing timer list: clearing %d timers", mapLen)); - } - return mapLen; - } - } - - /** - * Generate a log message for a timer. - */ - private void report(@NonNull Timer timer, @NonNull String msg) { - Log.i(TAG, msg + " " + timer + " " + Objects.toString(timer.arg)); - } - - /** * The FeatureSwitch class provides a quick switch between feature-enabled behavior and * feature-disabled behavior. */ private abstract class FeatureSwitch { - abstract boolean start(@NonNull V arg, int pid, int uid, long timeoutMs); + abstract void start(@NonNull V arg, int pid, int uid, long timeoutMs); - abstract boolean cancel(@NonNull V arg); + abstract void cancel(@NonNull V arg); - abstract boolean accept(@NonNull V arg); + abstract void accept(@NonNull V arg); - abstract boolean discard(@NonNull V arg); + abstract void discard(@NonNull V arg); abstract boolean enabled(); } @@ -706,29 +294,25 @@ public class AnrTimer<V> { private class FeatureDisabled extends FeatureSwitch { /** Start a timer by sending a message to the client's handler. */ @Override - boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { + void start(@NonNull V arg, int pid, int uid, long timeoutMs) { final Message msg = mHandler.obtainMessage(mWhat, arg); mHandler.sendMessageDelayed(msg, timeoutMs); - return true; } /** Cancel a timer by removing the message from the client's handler. */ @Override - boolean cancel(@NonNull V arg) { + void cancel(@NonNull V arg) { mHandler.removeMessages(mWhat, arg); - return true; } /** accept() is a no-op when the feature is disabled. */ @Override - boolean accept(@NonNull V arg) { - return true; + void accept(@NonNull V arg) { } /** discard() is a no-op when the feature is disabled. */ @Override - boolean discard(@NonNull V arg) { - return true; + void discard(@NonNull V arg) { } /** The feature is not enabled. */ @@ -739,113 +323,6 @@ public class AnrTimer<V> { } /** - * The FeatureEnabled class enables the AnrTimer logic. It is used when the AnrTimer service - * is enabled via Flags.anrTimerServiceEnabled. - */ - private class FeatureEnabled extends FeatureSwitch { - - /** - * Start a timer. - */ - @Override - boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { - final Timer timer = Timer.obtain(pid, uid, arg, timeoutMs, AnrTimer.this); - synchronized (mLock) { - Timer old = mTimerMap.get(arg); - // There is an existing timer. If the timer was running, then cancel the running - // timer and restart it. If the timer was expired record a protocol error and - // discard the expired timer. - if (old != null) { - if (old.status == TIMER_EXPIRED) { - restartedLocked(old.status, arg); - discard(arg); - } else { - cancel(arg); - } - } - if (mTimerService.start(timer)) { - timer.status = TIMER_RUNNING; - mTimerMap.put(arg, timer); - mTotalStarted++; - mMaxStarted = Math.max(mMaxStarted, mTimerMap.size()); - if (DEBUG) report(timer, "start"); - return true; - } else { - Log.e(TAG, "AnrTimer.start failed"); - return false; - } - } - } - - /** - * Cancel a timer. Return false if the timer was not found. - */ - @Override - boolean cancel(@NonNull V arg) { - synchronized (mLock) { - Timer timer = removeLocked(arg); - if (timer == null) { - if (!mLenientCancel) notFoundLocked("cancel", arg); - return false; - } - mTimerService.cancel(timer); - // There may be an expiration message in flight. Cancel it. - mHandler.removeMessages(mWhat, arg); - if (DEBUG) report(timer, "cancel"); - timer.release(); - return true; - } - } - - /** - * Accept a timer in the framework-level handler. The timeout has been accepted and the - * timeout handler is executing. Return false if the timer was not found. - */ - @Override - boolean accept(@NonNull V arg) { - synchronized (mLock) { - Timer timer = removeLocked(arg); - if (timer == null) { - notFoundLocked("accept", arg); - return false; - } - mTimerService.accept(timer); - traceEnd(timer); - if (DEBUG) report(timer, "accept"); - timer.release(); - return true; - } - } - - /** - * Discard a timer in the framework-level handler. For whatever reason, the timer is no - * longer interesting. No statistics are collected. Return false if the time was not - * found. - */ - @Override - boolean discard(@NonNull V arg) { - synchronized (mLock) { - Timer timer = removeLocked(arg); - if (timer == null) { - notFoundLocked("discard", arg); - return false; - } - mTimerService.discard(timer); - traceEnd(timer); - if (DEBUG) report(timer, "discard"); - timer.release(); - return true; - } - } - - /** The feature is enabled. */ - @Override - boolean enabled() { - return true; - } - } - - /** * Start a timer associated with arg. The same object must be used to cancel, accept, or * discard a timer later. If a timer already exists with the same arg, then the existing timer * is canceled and a new timer is created. @@ -854,32 +331,27 @@ public class AnrTimer<V> { * @param pid The Linux process ID of the target being timed. * @param uid The Linux user ID of the target being timed. * @param timeoutMs The timer timeout, in milliseconds. - * @return true if the timer was successfully created. */ - public boolean start(@NonNull V arg, int pid, int uid, long timeoutMs) { - return mFeature.start(arg, pid, uid, timeoutMs); + public void start(@NonNull V arg, int pid, int uid, long timeoutMs) { + mFeature.start(arg, pid, uid, timeoutMs); } /** * Cancel the running timer associated with arg. The timer is forgotten. If the timer has * expired, the call is treated as a discard. No errors are reported if the timer does not * exist or if the timer has expired. - * - * @return true if the timer was found and was running. */ - public boolean cancel(@NonNull V arg) { - return mFeature.cancel(arg); + public void cancel(@NonNull V arg) { + mFeature.cancel(arg); } /** * Accept the expired timer associated with arg. This indicates that the caller considers the * timer expiration to be a true ANR. (See {@link #discard} for an alternate response.) It is * an error to accept a running timer, however the running timer will be canceled. - * - * @return true if the timer was found and was expired. */ - public boolean accept(@NonNull V arg) { - return mFeature.accept(arg); + public void accept(@NonNull V arg) { + mFeature.accept(arg); } /** @@ -889,24 +361,9 @@ public class AnrTimer<V> { * such a process could be stopped at a breakpoint and its failure to respond would not be an * error. It is an error to discard a running timer, however the running timer will be * canceled. - * - * @return true if the timer was found and was expired. */ - public boolean discard(@NonNull V arg) { - return mFeature.discard(arg); - } - - /** - * The notifier that a timer has fired. The timer is not modified. - */ - @GuardedBy("mLock") - private void onExpiredLocked(@NonNull Timer timer) { - if (DEBUG) report(timer, "expire"); - traceBegin(timer, "expired"); - mHandler.sendMessage(Message.obtain(mHandler, mWhat, timer.arg)); - synchronized (mLock) { - mTotalExpired++; - } + public void discard(@NonNull V arg) { + mFeature.discard(arg); } /** @@ -916,9 +373,7 @@ public class AnrTimer<V> { synchronized (mLock) { pw.format("timer: %s\n", mLabel); pw.increaseIndent(); - pw.format("started=%d maxStarted=%d running=%d expired=%d error=%d\n", - mTotalStarted, mMaxStarted, mTimerMap.size(), - mTotalExpired, mTotalErrors); + pw.format("started=%d errors=%d\n", mTotalStarted, mTotalErrors); pw.decreaseIndent(); } } @@ -931,10 +386,20 @@ public class AnrTimer<V> { } /** - * The current time in milliseconds. + * Dump all errors to the output stream. */ - private static long now() { - return SystemClock.uptimeMillis(); + private static void dumpErrors(IndentingPrintWriter ipw) { + Error errors[]; + synchronized (sErrors) { + if (sErrors.size() == 0) return; + errors = sErrors.toArray(); + } + ipw.println("Errors"); + ipw.increaseIndent(); + for (int i = 0; i < errors.length; i++) { + if (errors[i] != null) errors[i].dump(ipw, i); + } + ipw.decreaseIndent(); } /** @@ -966,60 +431,12 @@ public class AnrTimer<V> { } /** - * Log an error about a timer that is started when there is an existing timer. - */ - @GuardedBy("mLock") - private void restartedLocked(@TimerStatus int status, Object arg) { - recordErrorLocked("start", status == TIMER_EXPIRED ? "autoDiscard" : "autoCancel", arg); - } - - /** - * Dump a single error to the output stream. - */ - private static void dump(IndentingPrintWriter ipw, int seq, Error err) { - ipw.format("%2d: op:%s tag:%s issue:%s arg:%s\n", seq, err.operation, err.tag, - err.issue, err.arg); - - final long offset = System.currentTimeMillis() - SystemClock.elapsedRealtime(); - final long etime = offset + err.timestamp; - ipw.println(" date:" + TimeMigrationUtils.formatMillisWithFixedFormat(etime)); - ipw.increaseIndent(); - for (int i = 0; i < err.stack.length; i++) { - ipw.println(" " + err.stack[i].toString()); - } - ipw.decreaseIndent(); - } - - /** - * Dump all errors to the output stream. - */ - private static void dumpErrors(IndentingPrintWriter ipw) { - Error errors[]; - synchronized (sErrors) { - if (sErrors.size() == 0) return; - errors = sErrors.toArray(); - } - ipw.println("Errors"); - ipw.increaseIndent(); - for (int i = 0; i < errors.length; i++) { - if (errors[i] != null) dump(ipw, i, errors[i]); - } - ipw.decreaseIndent(); - } - - /** * Dumpsys output. */ public static void dump(@NonNull PrintWriter pw, boolean verbose) { final IndentingPrintWriter ipw = new IndentingPrintWriter(pw); ipw.println("AnrTimer statistics"); ipw.increaseIndent(); - synchronized (sAnrTimerList) { - for (int i = 0; i < sAnrTimerList.size(); i++) { - AnrTimer client = sAnrTimerList.get(i).get(); - if (client != null) client.dump(ipw); - } - } if (verbose) dumpErrors(ipw); ipw.format("AnrTimerEnd\n"); ipw.decreaseIndent(); diff --git a/services/core/java/com/android/server/utils/OWNERS b/services/core/java/com/android/server/utils/OWNERS index be91611deccc..fbc0b56c2eb7 100644 --- a/services/core/java/com/android/server/utils/OWNERS +++ b/services/core/java/com/android/server/utils/OWNERS @@ -10,3 +10,8 @@ per-file Watcher.java = file:/services/core/java/com/android/server/pm/OWNERS per-file Watcher.java = shombert@google.com per-file EventLogger.java = file:/platform/frameworks/av:/media/janitors/media_solutions_OWNERS per-file EventLogger.java = jmtrivi@google.com + +# Bug component : 158088 = per-file AnrTimer*.java +per-file AnrTimer*.java = file:/PERFORMANCE_OWNERS + +per-file flags.aconfig = file:/PERFORMANCE_OWNERS 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/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java deleted file mode 100644 index 2eeb903bb551..000000000000 --- a/services/core/java/com/android/server/vibrator/VibratorControlService.java +++ /dev/null @@ -1,105 +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.vibrator; - -import android.annotation.NonNull; -import android.annotation.SuppressLint; -import android.frameworks.vibrator.IVibratorControlService; -import android.frameworks.vibrator.IVibratorController; -import android.frameworks.vibrator.VibrationParam; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Slog; - -import java.util.Objects; - -/** - * Implementation of {@link IVibratorControlService} which allows the registration of - * {@link IVibratorController} to set and receive vibration params. - * - * @hide - */ -public final class VibratorControlService extends IVibratorControlService.Stub { - private static final String TAG = "VibratorControlService"; - - private final VibratorControllerHolder mVibratorControllerHolder; - private final Object mLock; - - public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) { - mVibratorControllerHolder = vibratorControllerHolder; - mLock = lock; - } - - @Override - public void registerVibratorController(IVibratorController controller) - throws RemoteException { - synchronized (mLock) { - mVibratorControllerHolder.setVibratorController(controller); - } - } - - @Override - public void unregisterVibratorController(@NonNull IVibratorController controller) - throws RemoteException { - Objects.requireNonNull(controller); - - synchronized (mLock) { - if (mVibratorControllerHolder.getVibratorController() == null) { - Slog.w(TAG, "Received request to unregister IVibratorController = " - + controller + ", but no controller was previously registered. Request " - + "Ignored."); - return; - } - if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(), - controller.asBinder())) { - Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided " - + "controller doesn't match the registered one. " + this); - return; - } - mVibratorControllerHolder.setVibratorController(null); - } - } - - @Override - public void setVibrationParams( - @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token) - throws RemoteException { - // TODO(b/305939964): Add set vibration implementation. - } - - @Override - public void clearVibrationParams(int types, IVibratorController token) throws RemoteException { - // TODO(b/305939964): Add clear vibration implementation. - } - - @Override - public void onRequestVibrationParamsComplete( - IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result) - throws RemoteException { - // TODO(305942827): Cache the vibration params in VibrationScaler - } - - @Override - public int getInterfaceVersion() throws RemoteException { - return this.VERSION; - } - - @Override - public String getInterfaceHash() throws RemoteException { - return this.HASH; - } -} diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java deleted file mode 100644 index 63e69db9480f..000000000000 --- a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java +++ /dev/null @@ -1,70 +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.vibrator; - -import android.annotation.NonNull; -import android.frameworks.vibrator.IVibratorController; -import android.os.IBinder; -import android.os.RemoteException; -import android.util.Slog; - -/** - * Holder class for {@link IVibratorController}. - * - * @hide - */ -public final class VibratorControllerHolder implements IBinder.DeathRecipient { - private static final String TAG = "VibratorControllerHolder"; - - private IVibratorController mVibratorController; - - public IVibratorController getVibratorController() { - return mVibratorController; - } - - /** - * Sets the {@link IVibratorController} in {@link VibratorControllerHolder} to the new - * controller. This will also take care of registering and unregistering death notifications - * for the cached {@link IVibratorController}. - */ - public void setVibratorController(IVibratorController controller) { - try { - if (mVibratorController != null) { - mVibratorController.asBinder().unlinkToDeath(this, 0); - } - mVibratorController = controller; - if (mVibratorController != null) { - mVibratorController.asBinder().linkToDeath(this, 0); - } - } catch (RemoteException e) { - Slog.wtf(TAG, "Failed to set IVibratorController: " + this, e); - } - } - - @Override - public void binderDied(@NonNull IBinder deadBinder) { - if (deadBinder == mVibratorController.asBinder()) { - setVibratorController(null); - } - } - - @Override - public void binderDied() { - // Should not be used as binderDied(IBinder who) is overridden. - Slog.wtf(TAG, "binderDied() called unexpectedly."); - } -} diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index d5044d9bc660..7d4bd3baf613 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -53,7 +53,6 @@ import android.os.Trace; import android.os.VibrationAttributes; import android.os.VibrationEffect; import android.os.VibratorInfo; -import android.os.vibrator.Flags; import android.os.vibrator.PrebakedSegment; import android.os.vibrator.VibrationEffectSegment; import android.os.vibrator.VibratorInfoFactory; @@ -88,13 +87,10 @@ import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; - /** System implementation of {@link IVibratorManagerService}. */ public class VibratorManagerService extends IVibratorManagerService.Stub { private static final String TAG = "VibratorManagerService"; private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service"; - private static final String VIBRATOR_CONTROL_SERVICE = - "android.frameworks.vibrator.IVibratorControlService/default"; private static final boolean DEBUG = false; private static final VibrationAttributes DEFAULT_ATTRIBUTES = new VibrationAttributes.Builder().build(); @@ -273,10 +269,6 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED); injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService()); - if (Flags.adaptiveHapticsEnabled()) { - injector.addService(VIBRATOR_CONTROL_SERVICE, - new VibratorControlService(new VibratorControllerHolder(), mLock)); - } } /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */ @@ -411,9 +403,13 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @Override // Binder call public void performHapticFeedback( - int uid, int deviceId, String opPkg, int constant, boolean always, String reason, - IBinder token) { - performHapticFeedbackInternal(uid, deviceId, opPkg, constant, always, reason, token); + int uid, int deviceId, String opPkg, int constant, boolean always, String reason) { + // Note that the `performHapticFeedback` method does not take a token argument from the + // caller, and instead, uses this service as the token. This is to mitigate performance + // impact that would otherwise be caused due to marshal latency. Haptic feedback effects are + // short-lived, so we don't need to cancel when the process dies. + performHapticFeedbackInternal( + uid, deviceId, opPkg, constant, always, reason, /* token= */ this); } /** 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 91f45a7f0ae3..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); @@ -4259,7 +4265,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { void dumpActivityContainersLocked(PrintWriter pw) { pw.println("ACTIVITY MANAGER CONTAINERS (dumpsys activity containers)"); - mRootWindowContainer.dumpChildrenNames(pw, " "); + mRootWindowContainer.dumpChildrenNames(pw, ""); pw.println(" "); } 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 f8b22c97e218..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() @@ -443,6 +425,8 @@ public class BackgroundActivityStartController { // indicates BAL would be blocked because only creator of the PI has the privilege to allow // BAL, the sender does not have the privilege to allow BAL. private boolean mOnlyCreatorAllows; + /** indicates that this verdict is based on the real calling UID and not the calling UID */ + private boolean mBasedOnRealCaller; BalVerdict(@BalCode int balCode, boolean background, String message) { this.mBackground = background; @@ -472,6 +456,15 @@ public class BackgroundActivityStartController { return mOnlyCreatorAllows; } + private BalVerdict setBasedOnRealCaller() { + mBasedOnRealCaller = true; + return this; + } + + private boolean isBasedOnRealCaller() { + return mBasedOnRealCaller; + } + public String toString() { StringBuilder builder = new StringBuilder(); builder.append(balCodeToString(mCode)); @@ -495,7 +488,15 @@ public class BackgroundActivityStartController { return builder.toString(); } + public @BalCode int getRawCode() { + return mCode; + } + public @BalCode int getCode() { + if (mBasedOnRealCaller && mCode != BAL_BLOCK) { + // for compatibility always return BAL_ALLOW_PENDING_INTENT if based on real caller + return BAL_ALLOW_PENDING_INTENT; + } return mCode; } } @@ -516,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. * @@ -531,6 +533,7 @@ public class BackgroundActivityStartController { WindowProcessController callerApp, PendingIntentRecord originatingPendingIntent, BackgroundStartPrivileges forcedBalByPiSender, + ActivityRecord resultRecord, Intent intent, ActivityOptions checkedOptions) { @@ -541,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. @@ -580,7 +583,8 @@ public class BackgroundActivityStartController { // PendingIntents is null). BalVerdict resultForRealCaller = state.callerIsRealCaller() && resultForCaller.allows() ? resultForCaller - : checkBackgroundActivityStartAllowedBySender(state, checkedOptions); + : checkBackgroundActivityStartAllowedBySender(state, checkedOptions) + .setBasedOnRealCaller(); if (state.isPendingIntent()) { resultForCaller.setOnlyCreatorAllows( resultForCaller.allows() && resultForRealCaller.blocks()); @@ -828,7 +832,7 @@ public class BackgroundActivityStartController { && ActivityManager.checkComponentPermission( android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND, state.mRealCallingUid, NO_PROCESS_UID, true) == PackageManager.PERMISSION_GRANTED) { - return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + return new BalVerdict(BAL_ALLOW_PERMISSION, /*background*/ false, "realCallingUid has BAL permission."); } @@ -839,18 +843,18 @@ public class BackgroundActivityStartController { || state.mAppSwitchState == APP_SWITCH_FG_ONLY; if (Flags.balImproveRealCallerVisibilityCheck()) { if (appSwitchAllowedOrFg && state.mRealCallingUidHasAnyVisibleWindow) { - return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, "realCallingUid has visible window"); } if (mService.mActiveUids.hasNonAppVisibleWindow(state.mRealCallingUid)) { - return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, "realCallingUid has non-app visible window"); } } else { // don't abort if the realCallingUid has a visible window // TODO(b/171459802): We should check appSwitchAllowed also if (state.mRealCallingUidHasAnyVisibleWindow) { - return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, "realCallingUid has visible (non-toast) window."); } @@ -860,7 +864,7 @@ public class BackgroundActivityStartController { // wasn't allowed to start an activity if (state.mForcedBalByPiSender.allowsBackgroundActivityStarts() && state.mIsRealCallingUidPersistentSystemProcess) { - return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_UID, /*background*/ false, "realCallingUid is persistent system process AND intent " + "sender forced to allow."); @@ -868,7 +872,7 @@ public class BackgroundActivityStartController { // don't abort if the realCallingUid is an associated companion app if (mService.isAssociatedCompanionApp( UserHandle.getUserId(state.mRealCallingUid), state.mRealCallingUid)) { - return new BalVerdict(BAL_ALLOW_PENDING_INTENT, + return new BalVerdict(BAL_ALLOW_ALLOWLISTED_COMPONENT, /*background*/ false, "realCallingUid is a companion app."); } @@ -1469,7 +1473,7 @@ public class BackgroundActivityStartController { intent != null ? intent.getComponent().flattenToShortString() : ""; FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED, activityName, - code, + BAL_ALLOW_PENDING_INTENT, callingUid, realCallingUid); } diff --git a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java index 9a32dc8d9190..478524b7bd1c 100644 --- a/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java +++ b/services/core/java/com/android/server/wm/BackgroundLaunchProcessController.java @@ -22,7 +22,7 @@ import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM; import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME; import static com.android.server.wm.ActivityTaskManagerService.ACTIVITY_BG_START_GRACE_PERIOD_MS; import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW; -import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY; +import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_DISALLOW; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_FOREGROUND; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_GRACE_PERIOD; import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION; @@ -49,6 +49,7 @@ import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.wm.BackgroundActivityStartController.BalVerdict; +import com.android.window.flags.Flags; import java.io.PrintWriter; import java.util.ArrayList; @@ -113,13 +114,17 @@ class BackgroundLaunchProcessController { "process allowed by token"); } // Allow if the caller is bound by a UID that's currently foreground. - if (isBoundByForegroundUid()) { + // But still respect the appSwitchState. + boolean allowBoundByForegroundUid = + Flags.balRespectAppSwitchStateWhenCheckBoundByForegroundUid() + ? appSwitchState != APP_SWITCH_DISALLOW && isBoundByForegroundUid() + : isBoundByForegroundUid(); + if (allowBoundByForegroundUid) { return new BalVerdict(BAL_ALLOW_VISIBLE_WINDOW, /*background*/ false, "process bound by foreground uid"); } // Allow if the caller has an activity in any foreground task. - if (hasActivityInVisibleTask - && (appSwitchState == APP_SWITCH_ALLOW || appSwitchState == APP_SWITCH_FG_ONLY)) { + if (hasActivityInVisibleTask && appSwitchState != APP_SWITCH_DISALLOW) { return new BalVerdict(BAL_ALLOW_FOREGROUND, /*background*/ false, "process has activity in foreground task"); } diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java index 794711262a75..be7c18c49373 100644 --- a/services/core/java/com/android/server/wm/ConfigurationContainer.java +++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java @@ -23,7 +23,9 @@ import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS; import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD; import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.app.WindowConfiguration.activityTypeToString; import static android.app.WindowConfiguration.windowingModeToString; import static android.app.WindowConfigurationProto.WINDOWING_MODE; @@ -739,17 +741,43 @@ public abstract class ConfigurationContainer<E extends ConfigurationContainer> { * level with the input prefix. */ public void dumpChildrenNames(PrintWriter pw, String prefix) { - final String childPrefix = prefix + " "; + dumpChildrenNames(pw, prefix, true /* isLastChild */); + } + + /** + * Dumps the names of this container children in the input print writer indenting each + * level with the input prefix. + */ + public void dumpChildrenNames(PrintWriter pw, String prefix, boolean isLastChild) { + int curWinMode = getWindowingMode(); + String winMode = windowingModeToString(curWinMode); + if (curWinMode != WINDOWING_MODE_UNDEFINED && + curWinMode != WINDOWING_MODE_FULLSCREEN) { + winMode = winMode.toUpperCase(); + } + int requestedWinMode = getRequestedOverrideWindowingMode(); + String overrideWinMode = windowingModeToString(requestedWinMode); + if (requestedWinMode != WINDOWING_MODE_UNDEFINED && + requestedWinMode != WINDOWING_MODE_FULLSCREEN) { + overrideWinMode = overrideWinMode.toUpperCase(); + } + String actType = activityTypeToString(getActivityType()); + if (getActivityType() != ACTIVITY_TYPE_UNDEFINED + && getActivityType() != ACTIVITY_TYPE_STANDARD) { + actType = actType.toUpperCase(); + } + pw.print(prefix + (isLastChild ? "└─ " : "├─ ")); pw.println(getName() - + " type=" + activityTypeToString(getActivityType()) - + " mode=" + windowingModeToString(getWindowingMode()) - + " override-mode=" + windowingModeToString(getRequestedOverrideWindowingMode()) + + " type=" + actType + + " mode=" + winMode + + " override-mode=" + overrideWinMode + " requested-bounds=" + getRequestedOverrideBounds().toShortString() + " bounds=" + getBounds().toShortString()); + + String childPrefix = prefix + (isLastChild ? " " : "│ "); for (int i = getChildCount() - 1; i >= 0; --i) { final E cc = getChildAt(i); - pw.print(childPrefix + "#" + i + " "); - cc.dumpChildrenNames(pw, childPrefix); + cc.dumpChildrenNames(pw, childPrefix, i == 0 /* isLastChild */); } } 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 50376fed2005..3f206248cf00 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 @@ -545,10 +546,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp boolean isDefaultDisplay; /** Detect user tapping outside of current focused task bounds .*/ + // TODO(b/315321016): Remove once pointer event detection is removed from WM. @VisibleForTesting final TaskTapPointerEventListener mTapDetector; /** Detect user tapping outside of current focused root task bounds .*/ + // TODO(b/315321016): Remove once pointer event detection is removed from WM. private Region mTouchExcludeRegion = new Region(); /** Save allocating when calculating rects */ @@ -1158,7 +1161,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; @@ -1189,12 +1196,18 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp "PointerEventDispatcher" + mDisplayId, mDisplayId); mPointerEventDispatcher = new PointerEventDispatcher(inputChannel); - // Tap Listeners are supported for: - // 1. All physical displays (multi-display). - // 2. VirtualDisplays on VR, AA (and everything else). - mTapDetector = new TaskTapPointerEventListener(mWmService, this); - registerPointerEventListener(mTapDetector); - registerPointerEventListener(mWmService.mMousePositionTracker); + if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) { + mTapDetector = null; + } else { + // Tap Listeners are supported for: + // 1. All physical displays (multi-display). + // 2. VirtualDisplays on VR, AA (and everything else). + mTapDetector = new TaskTapPointerEventListener(mWmService, this); + registerPointerEventListener(mTapDetector); + } + if (mWmService.mMousePositionTracker != null) { + registerPointerEventListener(mWmService.mMousePositionTracker); + } if (mWmService.mAtmService.getRecentTasks() != null) { registerPointerEventListener( mWmService.mAtmService.getRecentTasks().getInputListener()); @@ -1636,12 +1649,13 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mWmService.mWindowPlacerLocked.performSurfacePlacement(); } - void sendNewConfiguration() { + /** Returns {@code true} if the display configuration is changed. */ + boolean sendNewConfiguration() { if (!isReady()) { - return; + return false; } if (mRemoteDisplayChangeController.isWaitingForRemoteDisplayChange()) { - return; + return false; } final Transition.ReadyCondition displayConfig = mTransitionController.isCollecting() @@ -1656,7 +1670,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp displayConfig.meet(); } if (configUpdated) { - return; + return true; } // The display configuration doesn't change. If there is a launching transformed app, that @@ -1674,6 +1688,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp setLayoutNeeded(); mWmService.mWindowPlacerLocked.performSurfacePlacement(); } + return false; } @Override @@ -3260,6 +3275,12 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } void updateTouchExcludeRegion() { + if (mTapDetector == null) { + // The touch exclude region is used to detect the region outside of the focused task + // so that the tap detector can detect outside touches. Don't calculate the exclude + // region when the tap detector is disabled. + return; + } final Task focusedTask = (mFocusedApp != null ? mFocusedApp.getTask() : null); if (focusedTask == null) { mTouchExcludeRegion.setEmpty(); @@ -3298,6 +3319,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp } private void processTaskForTouchExcludeRegion(Task task, Task focusedTask, int delta) { + if (mTapDetector == null) { + // The touch exclude region is used to detect the region outside of the focused task + // so that the tap detector can detect outside touches. Don't calculate the exclude + // region when the tap detector is disabled. + } final ActivityRecord topVisibleActivity = task.getTopVisibleActivity(); if (topVisibleActivity == null || !topVisibleActivity.hasContentToDisplay()) { @@ -5502,6 +5528,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp configureDisplayPolicy(); } + if (!isDefaultDisplay) { + mDisplayRotation.updateRotationUnchecked(true); + } + reconfigureDisplayLocked(); onRequestedOverrideConfigurationChanged(getRequestedOverrideConfiguration()); mWmService.mDisplayNotificationController.dispatchDisplayAdded(this); 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/RefreshRatePolicy.java b/services/core/java/com/android/server/wm/RefreshRatePolicy.java index 23c135a7b83a..03574029c061 100644 --- a/services/core/java/com/android/server/wm/RefreshRatePolicy.java +++ b/services/core/java/com/android/server/wm/RefreshRatePolicy.java @@ -26,6 +26,7 @@ import android.view.Display; import android.view.Display.Mode; import android.view.DisplayInfo; import android.view.Surface; +import android.view.SurfaceControl; import android.view.SurfaceControl.RefreshRateRange; import java.util.HashMap; @@ -191,26 +192,35 @@ class RefreshRatePolicy { public static class FrameRateVote { float mRefreshRate; @Surface.FrameRateCompatibility int mCompatibility; + @SurfaceControl.FrameRateSelectionStrategy int mSelectionStrategy; - FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility) { - update(refreshRate, compatibility); + + + FrameRateVote(float refreshRate, @Surface.FrameRateCompatibility int compatibility, + @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) { + update(refreshRate, compatibility, selectionStrategy); } FrameRateVote() { reset(); } - boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility) { - if (!refreshRateEquals(refreshRate) || mCompatibility != compatibility) { + boolean update(float refreshRate, @Surface.FrameRateCompatibility int compatibility, + @SurfaceControl.FrameRateSelectionStrategy int selectionStrategy) { + if (!refreshRateEquals(refreshRate) + || mCompatibility != compatibility + || mSelectionStrategy != selectionStrategy) { mRefreshRate = refreshRate; mCompatibility = compatibility; + mSelectionStrategy = selectionStrategy; return true; } return false; } boolean reset() { - return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + return update(0, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_PROPAGATE); } @Override @@ -221,17 +231,20 @@ class RefreshRatePolicy { FrameRateVote other = (FrameRateVote) o; return refreshRateEquals(other.mRefreshRate) - && mCompatibility == other.mCompatibility; + && mCompatibility == other.mCompatibility + && mSelectionStrategy == other.mSelectionStrategy; } @Override public int hashCode() { - return Objects.hash(mRefreshRate, mCompatibility); + return Objects.hash(mRefreshRate, mCompatibility, mSelectionStrategy); + } @Override public String toString() { - return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility; + return "mRefreshRate=" + mRefreshRate + ", mCompatibility=" + mCompatibility + + ", mSelectionStrategy=" + mSelectionStrategy; } private boolean refreshRateEquals(float refreshRate) { @@ -265,7 +278,8 @@ class RefreshRatePolicy { for (Display.Mode mode : mDisplayInfo.supportedModes) { if (preferredModeId == mode.getModeId()) { return w.mFrameRateVote.update(mode.getRefreshRate(), - Surface.FRAME_RATE_COMPATIBILITY_EXACT); + Surface.FRAME_RATE_COMPATIBILITY_EXACT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } } } @@ -273,7 +287,8 @@ class RefreshRatePolicy { if (w.mAttrs.preferredRefreshRate > 0) { return w.mFrameRateVote.update(w.mAttrs.preferredRefreshRate, - Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } // If the app didn't set a preferred mode id or refresh rate, but it is part of the deny @@ -282,7 +297,8 @@ class RefreshRatePolicy { final String packageName = w.getOwningPackage(); if (mHighRefreshRateDenylist.isDenylisted(packageName)) { return w.mFrameRateVote.update(mLowRefreshRateMode.getRefreshRate(), - Surface.FRAME_RATE_COMPATIBILITY_EXACT); + Surface.FRAME_RATE_COMPATIBILITY_EXACT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); } } diff --git a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java index 7d22b744ad5f..ac244c7b048e 100644 --- a/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java +++ b/services/core/java/com/android/server/wm/TaskTapPointerEventListener.java @@ -45,6 +45,10 @@ public class TaskTapPointerEventListener implements PointerEventListener { public TaskTapPointerEventListener(WindowManagerService service, DisplayContent displayContent) { + // TODO(b/315321016): Remove this class when the flag rollout is complete. + if (com.android.input.flags.Flags.removePointerEventTrackingInWm()) { + throw new IllegalStateException("TaskTapPointerEventListener should not be used!"); + } mService = service; mDisplayContent = displayContent; } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 76c4a0ee438b..f020bfa8cbc7 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -2868,8 +2868,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { final WindowContainer<?> wc = mParticipants.valueAt(i); final DisplayContent dc = wc.asDisplayContent(); if (dc == null || !mChanges.get(dc).hasChanged()) continue; - final int originalSeq = dc.getConfiguration().seq; - dc.sendNewConfiguration(); + final boolean changed = dc.sendNewConfiguration(); // Set to ready if no other change controls the ready state. But if there is, such as // if an activity is pausing, it will call setReady(ar, false) and wait for the next // resumed activity. Then do not set to ready because the transition only contains @@ -2877,7 +2876,7 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { if (!mReadyTrackerOld.mUsed) { setReady(dc, true); } - if (originalSeq == dc.getConfiguration().seq) continue; + if (!changed) continue; // If the update is deferred, sendNewConfiguration won't deliver new configuration to // clients, then it is the caller's responsibility to deliver the changes. if (mController.mAtm.mTaskSupervisor.isRootVisibilityUpdateDeferred()) { 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 0d2c94d103fb..10dd334ed50c 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 = @@ -7076,7 +7081,7 @@ public class WindowManagerService extends IWindowManager.Stub return; } else if ("containers".equals(cmd)) { synchronized (mGlobalLock) { - mRoot.dumpChildrenNames(pw, " "); + mRoot.dumpChildrenNames(pw, ""); pw.println(" "); mRoot.forAllWindows(w -> {pw.println(w);}, true /* traverseTopToBottom */); } @@ -7171,6 +7176,7 @@ public class WindowManagerService extends IWindowManager.Stub pw.println(separator); } mSystemPerformanceHinter.dump(pw, ""); + mTrustedPresentationListenerController.dump(pw); } } @@ -7302,7 +7308,12 @@ public class WindowManagerService extends IWindowManager.Stub } } - MousePositionTracker mMousePositionTracker = new MousePositionTracker(); + // The mouse position tracker will be obsolete after the Pointer Icon Refactor. + // TODO(b/293587049): Remove after the refactoring is fully rolled out. + @Nullable + final MousePositionTracker mMousePositionTracker = + com.android.input.flags.Flags.enablePointerChoreographer() ? null + : new MousePositionTracker(); private static class MousePositionTracker implements PointerEventListener { private boolean mLatestEventWasMouse; @@ -7354,6 +7365,9 @@ public class WindowManagerService extends IWindowManager.Stub }; void updatePointerIcon(IWindow client) { + if (mMousePositionTracker == null) { + return; + } int pointerDisplayId; float mouseX, mouseY; @@ -7400,6 +7414,9 @@ public class WindowManagerService extends IWindowManager.Stub } void restorePointerIconLocked(DisplayContent displayContent, float latestX, float latestY) { + if (mMousePositionTracker == null) { + return; + } // Mouse position tracker has not been getting updates while dragging, update it now. if (!mMousePositionTracker.updatePosition( displayContent.getDisplayId(), latestX, latestY)) { @@ -7423,6 +7440,9 @@ public class WindowManagerService extends IWindowManager.Stub } } void setMousePointerDisplayId(int displayId) { + if (mMousePositionTracker == null) { + return; + } mMousePositionTracker.setPointerDisplayId(displayId); } @@ -9771,4 +9791,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..4e9d23c88db4 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -178,6 +178,7 @@ import static com.android.server.wm.WindowStateProto.UNRESTRICTED_KEEP_CLEAR_ARE import static com.android.server.wm.WindowStateProto.VIEW_VISIBILITY; import static com.android.server.wm.WindowStateProto.WINDOW_CONTAINER; import static com.android.server.wm.WindowStateProto.WINDOW_FRAMES; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import static com.android.window.flags.Flags.secureWindowState; import static com.android.window.flags.Flags.surfaceTrustedOverlay; @@ -668,6 +669,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 +1196,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 +1499,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 +2407,9 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } mWmService.postWindowRemoveCleanupLocked(this); + + mWmService.mTrustedPresentationListenerController.removeIgnoredWindowTokens( + getWindowToken()); } @Override @@ -3796,13 +3813,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 +4211,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 +4245,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 @@ -5185,9 +5208,13 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP boolean voteChanged = refreshRatePolicy.updateFrameRateVote(this); if (voteChanged) { - getPendingTransaction().setFrameRate( - mSurfaceControl, mFrameRateVote.mRefreshRate, - mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS); + getPendingTransaction() + .setFrameRate(mSurfaceControl, mFrameRateVote.mRefreshRate, + mFrameRateVote.mCompatibility, Surface.CHANGE_FRAME_RATE_ALWAYS); + if (explicitRefreshRateHints()) { + getPendingTransaction().setFrameRateSelectionStrategy(mSurfaceControl, + mFrameRateVote.mSelectionStrategy); + } } } 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/core/jni/OWNERS b/services/core/jni/OWNERS index 0e45f61cbfe1..061fe0fc88d9 100644 --- a/services/core/jni/OWNERS +++ b/services/core/jni/OWNERS @@ -30,3 +30,6 @@ per-file com_android_server_tv_* = file:/media/java/android/media/tv/OWNERS per-file com_android_server_vibrator_* = file:/services/core/java/com/android/server/vibrator/OWNERS per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS + +# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java +per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS diff --git a/services/manifest_services.xml b/services/manifest_services.xml index e2fdfe9e8e47..76389154a885 100644 --- a/services/manifest_services.xml +++ b/services/manifest_services.xml @@ -4,14 +4,4 @@ <version>1</version> <fqname>IAltitudeService/default</fqname> </hal> - <hal format="aidl"> - <name>android.frameworks.vibrator</name> - <version>1</version> - <fqname>IVibratorController/default</fqname> - </hal> - <hal format="aidl"> - <name>android.frameworks.vibrator</name> - <version>1</version> - <fqname>IVibratorControlService/default</fqname> - </hal> </manifest> diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index 39aaab25d7be..a212812b0768 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -31,6 +31,7 @@ import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.PackageManager.Property; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.content.res.Resources; @@ -1396,16 +1397,20 @@ public class MidiService extends IMidiManager.Stub { XmlResourceParser parser = null; try { - parser = serviceInfo.loadXmlMetaData(mPackageManager, - MidiDeviceService.SERVICE_INTERFACE); - if (parser == null) return; + if (serviceInfo == null) { + Log.w(TAG, "Skipping null service info"); + return; + } // ignore virtual device servers that do not require the correct permission if (!android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE.equals( serviceInfo.permission)) { - Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName - + ": it does not require the permission " - + android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE); + return; + } + parser = serviceInfo.loadXmlMetaData(mPackageManager, + MidiDeviceService.SERVICE_INTERFACE); + if (parser == null) { + Log.w(TAG, "loading xml metadata failed"); return; } @@ -1533,21 +1538,14 @@ public class MidiService extends IMidiManager.Stub { XmlResourceParser parser = null; try { - ComponentName componentName = new ComponentName(serviceInfo.packageName, - serviceInfo.name); - int resId = mPackageManager.getProperty(MidiUmpDeviceService.SERVICE_INTERFACE, - componentName).getResourceId(); - Resources resources = mPackageManager.getResourcesForApplication( - serviceInfo.packageName); - parser = resources.getXml(resId); - if (parser == null) return; + if (serviceInfo == null) { + Log.w(TAG, "Skipping null service info"); + return; + } // ignore virtual device servers that do not require the correct permission if (!android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE.equals( serviceInfo.permission)) { - Log.w(TAG, "Skipping MIDI device service " + serviceInfo.packageName - + ": it does not require the permission " - + android.Manifest.permission.BIND_MIDI_DEVICE_SERVICE); return; } @@ -1557,6 +1555,31 @@ public class MidiService extends IMidiManager.Stub { return; } + ComponentName componentName = new ComponentName(serviceInfo.packageName, + serviceInfo.name); + Property property = mPackageManager.getProperty(MidiUmpDeviceService.SERVICE_INTERFACE, + componentName); + if (property == null) { + Log.w(TAG, "Getting MidiUmpDeviceService property failed"); + return; + } + int resId = property.getResourceId(); + if (resId == 0) { + Log.w(TAG, "Getting MidiUmpDeviceService resourceId failed"); + return; + } + Resources resources = mPackageManager.getResourcesForApplication( + serviceInfo.packageName); + if (resources == null) { + Log.w(TAG, "Getting resource failed " + serviceInfo.packageName); + return; + } + parser = resources.getXml(resId); + if (parser == null) { + Log.w(TAG, "Getting XML failed " + resId); + return; + } + Bundle properties = null; int numPorts = 0; boolean isPrivate = false; diff --git a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt index 44609acf7894..ea5fb5d62fad 100644 --- a/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt +++ b/services/permission/java/com/android/server/permission/access/permission/DevicePermissionPolicy.kt @@ -304,7 +304,7 @@ class DevicePermissionPolicy : SchemePolicy() { /** These permissions are supported for virtual devices. */ // TODO: b/298661870 - Use new API to get the list of device aware permissions. val DEVICE_AWARE_PERMISSIONS = - if (Flags.deviceAwarePermissionApis()) { + if (Flags.deviceAwarePermissionApisEnabled()) { setOf(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO) } else { emptySet<String>() diff --git a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt index a7d32492d6e2..0f65494cde3e 100644 --- a/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt +++ b/services/permission/java/com/android/server/permission/access/permission/PermissionService.kt @@ -1551,7 +1551,8 @@ class PermissionService(private val service: AccessCheckingService) : permissionName: String, deviceId: Int, ): Int { - return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) { + return if (!Flags.deviceAwarePermissionApisEnabled() || + deviceId == Context.DEVICE_ID_DEFAULT) { with(policy) { getPermissionFlags(appId, userId, permissionName) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { @@ -1586,7 +1587,8 @@ class PermissionService(private val service: AccessCheckingService) : deviceId: Int, flags: Int ): Boolean { - return if (!Flags.deviceAwarePermissionApis() || deviceId == Context.DEVICE_ID_DEFAULT) { + return if (!Flags.deviceAwarePermissionApisEnabled() || + deviceId == Context.DEVICE_ID_DEFAULT) { with(policy) { setPermissionFlags(appId, userId, permissionName, flags) } } else { if (permissionName !in DevicePermissionPolicy.DEVICE_AWARE_PERMISSIONS) { 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/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java index 25d331fcc5ca..23886a17c890 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java @@ -167,6 +167,11 @@ public class BackgroundJobsControllerTest { } private void setStoppedState(int uid, String pkgName, boolean stopped) { + doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid); + sendPackageStoppedBroadcast(uid, pkgName, stopped); + } + + private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) { Intent intent = new Intent( stopped ? Intent.ACTION_PACKAGE_RESTARTED : Intent.ACTION_PACKAGE_UNSTOPPED); intent.putExtra(Intent.EXTRA_UID, uid); @@ -174,14 +179,6 @@ public class BackgroundJobsControllerTest { mStoppedReceiver.onReceive(mContext, intent); } - private void setUidBias(int uid, int bias) { - int prevBias = mJobSchedulerService.getUidBias(uid); - doReturn(bias).when(mJobSchedulerService).getUidBias(uid); - synchronized (mBackgroundJobsController.mLock) { - mBackgroundJobsController.onUidBiasChangedLocked(uid, prevBias, bias); - } - } - private void trackJobs(JobStatus... jobs) { for (JobStatus job : jobs) { mJobStore.add(job); @@ -208,6 +205,47 @@ public class BackgroundJobsControllerTest { } @Test + public void testRestartedBroadcastWithoutStopping() { + mSetFlagsRule.enableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED); + // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself. + JobStatus directJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, SOURCE_UID, + createBaseJobInfoBuilder(SOURCE_PACKAGE, 1).build()); + // Scheduled by ALTERNATE_UID:ALTERNATE_SOURCE_PACKAGE for itself. + JobStatus directJob2 = createJobStatus("testStopped", + ALTERNATE_SOURCE_PACKAGE, ALTERNATE_UID, + createBaseJobInfoBuilder(ALTERNATE_SOURCE_PACKAGE, 2).build()); + // Scheduled by CALLING_PACKAGE for SOURCE_PACKAGE. + JobStatus proxyJob1 = createJobStatus("testStopped", SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(CALLING_PACKAGE, 3).build()); + // Scheduled by CALLING_PACKAGE for ALTERNATE_SOURCE_PACKAGE. + JobStatus proxyJob2 = createJobStatus("testStopped", + ALTERNATE_SOURCE_PACKAGE, CALLING_UID, + createBaseJobInfoBuilder(CALLING_PACKAGE, 4).build()); + + trackJobs(directJob1, directJob2, proxyJob1, proxyJob2); + + sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, true); + assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); + assertFalse(directJob1.isUserBgRestricted()); + assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); + assertFalse(directJob2.isUserBgRestricted()); + assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); + assertFalse(proxyJob1.isUserBgRestricted()); + assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); + assertFalse(proxyJob2.isUserBgRestricted()); + + sendPackageStoppedBroadcast(ALTERNATE_UID, ALTERNATE_SOURCE_PACKAGE, false); + assertTrue(directJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); + assertFalse(directJob1.isUserBgRestricted()); + assertTrue(directJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); + assertFalse(directJob2.isUserBgRestricted()); + assertTrue(proxyJob1.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); + assertFalse(proxyJob1.isUserBgRestricted()); + assertTrue(proxyJob2.isConstraintSatisfied(CONSTRAINT_BACKGROUND_NOT_RESTRICTED)); + assertFalse(proxyJob2.isUserBgRestricted()); + } + + @Test public void testStopped_disabled() { mSetFlagsRule.disableFlags(android.content.pm.Flags.FLAG_STAY_STOPPED); // Scheduled by SOURCE_UID:SOURCE_PACKAGE for itself. 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/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java index a250ac75635b..07027645411d 100644 --- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java @@ -368,10 +368,15 @@ public class QuotaControllerTest { } } + private JobInfo.Builder createJobInfoBuilder(int jobId) { + return new JobInfo.Builder(jobId, new ComponentName(mContext, "TestQuotaJobService")); + } + private JobStatus createJobStatus(String testTag, int jobId) { - JobInfo jobInfo = new JobInfo.Builder(jobId, - new ComponentName(mContext, "TestQuotaJobService")) - .build(); + return createJobStatus(testTag, createJobInfoBuilder(jobId).build()); + } + + private JobStatus createJobStatus(String testTag, JobInfo jobInfo) { return createJobStatus(testTag, SOURCE_PACKAGE, CALLING_UID, jobInfo); } @@ -1333,39 +1338,70 @@ public class QuotaControllerTest { mQuotaController.saveTimingSession(0, SOURCE_PACKAGE, createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + final long timeUntilQuotaConsumedMs = 7 * MINUTE_IN_MILLIS; JobStatus job = createJobStatus("testGetMaxJobExecutionTimeLocked", 0); + //noinspection deprecation + JobStatus jobDefIWF = createJobStatus("testGetMaxJobExecutionTimeLocked", + createJobInfoBuilder(1) + .setImportantWhileForeground(true) + .setPriority(JobInfo.PRIORITY_DEFAULT) + .build()); + JobStatus jobHigh = createJobStatus("testGetMaxJobExecutionTimeLocked", + createJobInfoBuilder(2).setPriority(JobInfo.PRIORITY_HIGH).build()); setStandbyBucket(RARE_INDEX, job); + setStandbyBucket(RARE_INDEX, jobDefIWF); + setStandbyBucket(RARE_INDEX, jobHigh); setCharging(); synchronized (mQuotaController.mLock) { assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, mQuotaController.getMaxJobExecutionTimeMsLocked((job))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh))); } setDischarging(); setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE); synchronized (mQuotaController.mLock) { - assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + assertEquals(timeUntilQuotaConsumedMs, mQuotaController.getMaxJobExecutionTimeMsLocked((job))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh))); } // Top-started job setProcessState(ActivityManager.PROCESS_STATE_TOP); synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job, null); + trackJobs(job, jobDefIWF, jobHigh); mQuotaController.prepareForExecutionLocked(job); + mQuotaController.prepareForExecutionLocked(jobDefIWF); + mQuotaController.prepareForExecutionLocked(jobHigh); } setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); synchronized (mQuotaController.mLock) { - assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + assertEquals(timeUntilQuotaConsumedMs, mQuotaController.getMaxJobExecutionTimeMsLocked((job))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobDefIWF))); + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked((jobHigh))); mQuotaController.maybeStopTrackingJobLocked(job, null); + mQuotaController.maybeStopTrackingJobLocked(jobDefIWF, null); + mQuotaController.maybeStopTrackingJobLocked(jobHigh, null); } setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); synchronized (mQuotaController.mLock) { - assertEquals(7 * MINUTE_IN_MILLIS, + assertEquals(timeUntilQuotaConsumedMs, mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + assertEquals(timeUntilQuotaConsumedMs, + mQuotaController.getMaxJobExecutionTimeMsLocked(jobDefIWF)); + assertEquals(timeUntilQuotaConsumedMs, + mQuotaController.getMaxJobExecutionTimeMsLocked(jobHigh)); } } 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/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp index f02e5a5ece24..07197b1ab9df 100644 --- a/services/tests/powerstatstests/Android.bp +++ b/services/tests/powerstatstests/Android.bp @@ -12,6 +12,7 @@ android_test { ], exclude_srcs: [ + "src/com/android/server/power/stats/MultiStateStatsTest.java", "src/com/android/server/power/stats/PowerStatsStoreTest.java", ], @@ -62,13 +63,12 @@ android_ravenwood_test { static_libs: [ "services.core", "modules-utils-binary-xml", - "androidx.annotation_annotation", "androidx.test.rules", ], srcs: [ + "src/com/android/server/power/stats/MultiStateStatsTest.java", "src/com/android/server/power/stats/PowerStatsStoreTest.java", ], - sdk_version: "test_current", auto_gen_config: true, } diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java index 48e2dd74fcef..6d61dc8d31fa 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java @@ -26,8 +26,6 @@ import android.os.BatteryConsumer; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.MultiStateStats; - import org.junit.Test; import org.junit.runner.RunWith; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java index 8ca4ff6f86f5..993d834b9500 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/CpuAggregatedPowerStatsProcessorTest.java @@ -38,7 +38,6 @@ import android.util.LongArray; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.MultiStateStats; import com.android.internal.os.PowerProfile; import com.android.internal.os.PowerStats; diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java index eb03a6c14f7d..e8f46b30fb8c 100644 --- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java +++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java @@ -22,12 +22,12 @@ import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertThrows; import android.os.BatteryConsumer; +import android.platform.test.ravenwood.RavenwoodRule; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.os.MultiStateStats; - +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -39,6 +39,10 @@ import java.util.Arrays; @SmallTest public class MultiStateStatsTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder() + .build(); + public static final int DIMENSION_COUNT = 2; @Test diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp index 2ece8c74420c..9b8419021c5c 100644 --- a/services/tests/servicestests/Android.bp +++ b/services/tests/servicestests/Android.bp @@ -79,6 +79,7 @@ android_test { "coretests-aidl", "securebox", "flag-junit", + "ravenwood-junit", ], libs: [ @@ -140,6 +141,23 @@ android_test { resource_zips: [":FrameworksServicesTests_apks_as_resources"], } +android_ravenwood_test { + name: "FrameworksServicesTestsRavenwood", + libs: [ + "android.test.mock", + ], + static_libs: [ + "androidx.annotation_annotation", + "androidx.test.rules", + "mockito_ravenwood", + "services.core", + ], + srcs: [ + "src/com/android/server/uri/**/*.java", + ], + auto_gen_config: true, +} + java_library { name: "servicestests-core-utils", srcs: [ 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..71d64cf4c8d4 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 @@ -532,12 +532,11 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) - public void testTwoFingerTripleTap_StateIsIdle_shouldInActivated() { + public void testTwoFingerDoubleTap_StateIsIdle_shouldInActivated() { goFromStateIdleTo(STATE_IDLE); twoFingerTap(); twoFingerTap(); - twoFingerTap(); assertIn(STATE_ACTIVATED); verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); @@ -546,13 +545,12 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) - public void testTwoFingerTripleTap_StateIsActivated_shouldInIdle() { + public void testTwoFingerDoubleTap_StateIsActivated_shouldInIdle() { goFromStateIdleTo(STATE_ACTIVATED); reset(mMockMagnificationLogger); twoFingerTap(); twoFingerTap(); - twoFingerTap(); assertIn(STATE_IDLE); verify(mMockMagnificationLogger, never()).logMagnificationTripleTap(anyBoolean()); @@ -561,11 +559,10 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) - public void testTwoFingerTripleTapAndHold_StateIsIdle_shouldZoomsImmediately() { + public void testTwoFingerDoubleTapAndHold_StateIsIdle_shouldZoomsImmediately() { goFromStateIdleTo(STATE_IDLE); twoFingerTap(); - twoFingerTap(); twoFingerTapAndHold(); assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); @@ -575,11 +572,10 @@ public class FullScreenMagnificationGestureHandlerTest { @Test @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_MULTIPLE_FINGER_MULTIPLE_TAP_GESTURE) - public void testTwoFingerTripleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() { + public void testTwoFingerDoubleSwipeAndHold_StateIsIdle_shouldZoomsImmediately() { goFromStateIdleTo(STATE_IDLE); twoFingerTap(); - twoFingerTap(); twoFingerSwipeAndHold(); assertIn(STATE_NON_ACTIVATED_ZOOMED_TMP); @@ -717,6 +713,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 +769,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/accessibility/magnification/TwoFingersDownOrSwipeTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java index 162d2a9d98af..d94faec4cf01 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/TwoFingersDownOrSwipeTest.java @@ -20,6 +20,7 @@ import static com.android.server.accessibility.utils.TouchEventGenerator.movePoi import static com.android.server.accessibility.utils.TouchEventGenerator.twoPointersDownEvents; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.after; import static org.mockito.Mockito.timeout; @@ -27,6 +28,10 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.graphics.PointF; +import android.platform.test.annotations.RequiresFlagsDisabled; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.view.Display; import android.view.MotionEvent; import android.view.ViewConfiguration; @@ -37,6 +42,7 @@ import com.android.server.accessibility.utils.TouchEventGenerator; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -48,6 +54,9 @@ import java.util.List; */ public class TwoFingersDownOrSwipeTest { + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + private static final float DEFAULT_X = 100f; private static final float DEFAULT_Y = 100f; @@ -85,7 +94,8 @@ public class TwoFingersDownOrSwipeTest { } @Test - public void sendTwoFingerDownEvent_onGestureCompleted() { + @RequiresFlagsDisabled(android.view.accessibility.Flags.FLAG_COPY_EVENTS_FOR_GESTURE_DETECTION) + public void sendTwoFingerDownEvent_onGestureCompleted_withoutCopiedEvents() { final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY, new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10)); @@ -99,6 +109,23 @@ public class TwoFingersDownOrSwipeTest { } @Test + @RequiresFlagsEnabled(android.view.accessibility.Flags.FLAG_COPY_EVENTS_FOR_GESTURE_DETECTION) + public void sendTwoFingerDownEvent_onGestureCompleted() { + final List<MotionEvent> downEvents = twoPointersDownEvents(Display.DEFAULT_DISPLAY, + new PointF(DEFAULT_X, DEFAULT_Y), new PointF(DEFAULT_X + 10, DEFAULT_Y + 10)); + + for (MotionEvent event : downEvents) { + mGesturesObserver.onMotionEvent(event, event, 0); + } + + verify(mListener, timeout(sTimeoutMillis)).onGestureCompleted( + eq(MagnificationGestureMatcher.GESTURE_TWO_FINGERS_DOWN_OR_SWIPE), + argThat(argument -> downEvents.get(1).getId() == argument.getId()), + argThat(argument -> downEvents.get(1).getId() == argument.getId()), + eq(0)); + } + + @Test public void sendSingleTapEvent_onGestureCancelled() { final MotionEvent downEvent = TouchEventGenerator.downEvent(Display.DEFAULT_DISPLAY, DEFAULT_X, DEFAULT_Y); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java index a3b67aef551a..c99e04037023 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/WindowMagnificationGestureHandlerTest.java @@ -72,15 +72,15 @@ public class WindowMagnificationGestureHandlerTest { public static final int STATE_SHOW_MAGNIFIER_TRIPLE_TAP = 4; public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 5; public static final int STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD = 6; - public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP = 7; - public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 8; - public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD = 9; + public static final int STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP = 7; + public static final int STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 8; + public static final int STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD = 9; //TODO: Test it after can injecting Handler to GestureMatcher is available. public static final int FIRST_STATE = STATE_IDLE; public static final int LAST_STATE = STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD; public static final int LAST_STATE_WITH_MULTI_FINGER = - STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD; + STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD; // Co-prime x and y, to potentially catch x-y-swapped errors public static final float DEFAULT_TAP_X = 301; @@ -257,15 +257,15 @@ public class WindowMagnificationGestureHandlerTest { break; case STATE_SHOW_MAGNIFIER_SHORTCUT: case STATE_SHOW_MAGNIFIER_TRIPLE_TAP: - case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: + case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: check(isWindowMagnifierEnabled(DISPLAY_0), state); check(mWindowMagnificationGestureHandler.mCurrentState == mWindowMagnificationGestureHandler.mDetectingState, state); break; case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: - case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: - case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: { check(isWindowMagnifierEnabled(DISPLAY_0), state); check(mWindowMagnificationGestureHandler.mCurrentState == mWindowMagnificationGestureHandler.mViewportDraggingState, state); @@ -337,8 +337,7 @@ public class WindowMagnificationGestureHandlerTest { tapAndHold(); } break; - case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: { - twoFingerTap(); + case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: { twoFingerTap(); twoFingerTap(); // Wait for two-finger tap gesture completed. @@ -346,17 +345,15 @@ public class WindowMagnificationGestureHandlerTest { InstrumentationRegistry.getInstrumentation().waitForIdleSync(); } break; - case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { - twoFingerTap(); + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: { twoFingerTap(); twoFingerTapAndHold(); } break; - case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: { + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: { // enabled then perform two finger triple tap and hold gesture goFromStateIdleTo(STATE_SHOW_MAGNIFIER_SHORTCUT); twoFingerTap(); - twoFingerTap(); twoFingerTapAndHold(); } break; @@ -394,16 +391,15 @@ public class WindowMagnificationGestureHandlerTest { } break; case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: - case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: + case STATE_NOT_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); break; case STATE_ENABLED_SHOW_MAGNIFIER_TRIPLE_TAP_AND_HOLD: - case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP_AND_HOLD: + case STATE_ENABLED_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP_AND_HOLD: send(upEvent(DEFAULT_TAP_X, DEFAULT_TAP_Y)); returnToNormalFrom(STATE_SHOW_MAGNIFIER_SHORTCUT); break; - case STATE_SHOW_MAGNIFIER_TWO_FINGER_TRIPLE_TAP: { - twoFingerTap(); + case STATE_SHOW_MAGNIFIER_TWO_FINGER_DOUBLE_TAP: { twoFingerTap(); twoFingerTap(); // Wait for two-finger tap gesture completed. 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/AudioDeviceBrokerTest.java b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java index cc3c880b8927..f1c1dc365b90 100644 --- a/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java +++ b/services/tests/servicestests/src/com/android/server/audio/AudioDeviceBrokerTest.java @@ -266,18 +266,22 @@ public class AudioDeviceBrokerTest { .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED); // no metadata set - assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, - DEVICE_TYPE_DEFAULT.getBytes())); - assertFalse( - mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed( - mFakeBtDevice.getAddress())); + if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_DEFAULT.getBytes())) { + assertFalse(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed( + mFakeBtDevice.getAddress())); + } // metadata set - assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, - DEVICE_TYPE_HEADSET.getBytes())); - assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed( - mFakeBtDevice.getAddress())); + if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_HEADSET.getBytes())) { + assertTrue(mAudioDeviceBroker.isBluetoothAudioDeviceCategoryFixed( + mFakeBtDevice.getAddress())); + } } finally { + // reset the metadata device type + mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_DEFAULT.getBytes()); InstrumentationRegistry.getInstrumentation().getUiAutomation() .dropShellPermissionIdentity(); } @@ -304,25 +308,30 @@ public class AudioDeviceBrokerTest { .adoptShellPermissionIdentity(Manifest.permission.BLUETOOTH_PRIVILEGED); // no metadata set - assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, - DEVICE_TYPE_DEFAULT.getBytes())); - assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER, - mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress( - mFakeBtDevice.getAddress())); - verify(mMockAudioService, - timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState( - eq(devState)); + if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_DEFAULT.getBytes())) { + assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER, + mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress( + mFakeBtDevice.getAddress())); + verify(mMockAudioService, + timeout(MAX_MESSAGE_HANDLING_DELAY_MS).times(0)).onUpdatedAdiDeviceState( + eq(devState)); + } // metadata set - assertTrue(mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, - DEVICE_TYPE_HEADSET.getBytes())); - assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES, - mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress( - mFakeBtDevice.getAddress())); - verify(mMockAudioService, - timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState( - any()); + if (mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_HEADSET.getBytes())) { + assertEquals(AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES, + mAudioDeviceBroker.getAndUpdateBtAdiDeviceStateCategoryForAddress( + mFakeBtDevice.getAddress())); + verify(mMockAudioService, + timeout(MAX_MESSAGE_HANDLING_DELAY_MS)).onUpdatedAdiDeviceState( + any()); + } } finally { + // reset the metadata device type + mFakeBtDevice.setMetadata(BluetoothDevice.METADATA_DEVICE_TYPE, + DEVICE_TYPE_DEFAULT.getBytes()); InstrumentationRegistry.getInstrumentation().getUiAutomation() .dropShellPermissionIdentity(); } 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/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java index 9213601a6144..995d1f4d5520 100644 --- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java @@ -444,18 +444,27 @@ public class VirtualDeviceManagerServiceTest { } @Test + public void isDeviceIdValid_invalidDeviceId_returnsFalse() { + assertThat(mVdm.isValidVirtualDeviceId(DEVICE_ID_INVALID)).isFalse(); + assertThat(mLocalService.isValidVirtualDeviceId(DEVICE_ID_INVALID)).isFalse(); + } + + @Test public void isDeviceIdValid_defaultDeviceId_returnsFalse() { assertThat(mVdm.isValidVirtualDeviceId(DEVICE_ID_DEFAULT)).isFalse(); + assertThat(mLocalService.isValidVirtualDeviceId(DEVICE_ID_DEFAULT)).isFalse(); } @Test public void isDeviceIdValid_validVirtualDeviceId_returnsTrue() { assertThat(mVdm.isValidVirtualDeviceId(mDeviceImpl.getDeviceId())).isTrue(); + assertThat(mLocalService.isValidVirtualDeviceId(mDeviceImpl.getDeviceId())).isTrue(); } @Test public void isDeviceIdValid_nonExistentDeviceId_returnsFalse() { assertThat(mVdm.isValidVirtualDeviceId(mDeviceImpl.getDeviceId() + 1)).isFalse(); + assertThat(mLocalService.isValidVirtualDeviceId(mDeviceImpl.getDeviceId() + 1)).isFalse(); } @Test 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/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java index e418d2f8d328..769ec5fac023 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsManagerServiceTest.java @@ -57,17 +57,22 @@ import android.content.pm.ProviderInfo; import android.net.Uri; import android.os.Process; import android.os.UserHandle; +import android.platform.test.ravenwood.RavenwoodRule; import android.util.ArraySet; import androidx.test.InstrumentationRegistry; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import java.util.Arrays; import java.util.Set; public class UriGrantsManagerServiceTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + private UriGrantsMockContext mContext; private UriGrantsManagerInternal mService; @@ -79,7 +84,7 @@ public class UriGrantsManagerServiceTest { @Before public void setUp() throws Exception { - mContext = new UriGrantsMockContext(InstrumentationRegistry.getContext()); + mContext = new UriGrantsMockContext(); mService = UriGrantsManagerService.createForTest(mContext.getFilesDir()).getLocalService(); } diff --git a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java index 7eb6c9789fe4..4c11de0924ba 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriGrantsMockContext.java @@ -21,11 +21,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import android.annotation.NonNull; import android.app.ActivityManagerInternal; -import android.content.ContentResolver; -import android.content.Context; -import android.content.ContextWrapper; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; @@ -33,18 +29,19 @@ import android.content.pm.PackageManagerInternal; import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.net.Uri; -import android.os.FileUtils; import android.os.PatternMatcher; import android.os.Process; import android.os.UserHandle; -import android.test.mock.MockContentResolver; +import android.test.mock.MockContext; import android.test.mock.MockPackageManager; import com.android.server.LocalServices; import java.io.File; +import java.io.IOException; +import java.nio.file.Files; -public class UriGrantsMockContext extends ContextWrapper { +public class UriGrantsMockContext extends MockContext { static final String TAG = "UriGrants"; static final int FLAG_READ = Intent.FLAG_GRANT_READ_URI_PERMISSION; @@ -98,19 +95,14 @@ public class UriGrantsMockContext extends ContextWrapper { private final File mDir; private final MockPackageManager mPackage; - private final MockContentResolver mResolver; final ActivityManagerInternal mAmInternal; final PackageManagerInternal mPmInternal; - public UriGrantsMockContext(@NonNull Context base) { - super(base); - mDir = new File(base.getFilesDir(), TAG); - mDir.mkdirs(); - FileUtils.deleteContents(mDir); + public UriGrantsMockContext() throws IOException { + mDir = Files.createTempDirectory(TAG).toFile(); mPackage = new MockPackageManager(); - mResolver = new MockContentResolver(this); mAmInternal = mock(ActivityManagerInternal.class); LocalServices.removeServiceForTest(ActivityManagerInternal.class); @@ -239,11 +231,6 @@ public class UriGrantsMockContext extends ContextWrapper { } @Override - public ContentResolver getContentResolver() { - return mResolver; - } - - @Override public File getFilesDir() { return mDir; } diff --git a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java index 07005a9902d7..4d4f5ed15ad6 100644 --- a/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java +++ b/services/tests/servicestests/src/com/android/server/uri/UriPermissionTest.java @@ -35,12 +35,18 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import android.platform.test.ravenwood.RavenwoodRule; + import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class UriPermissionTest { + @Rule + public final RavenwoodRule mRavenwood = new RavenwoodRule(); + @Mock private UriGrantsManagerInternal mService; diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java index 330dbb83e949..861d14a2cf66 100644 --- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java +++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java @@ -30,13 +30,19 @@ import androidx.test.filters.SmallTest; import com.android.internal.annotations.GuardedBy; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @SmallTest @Presubmit +@RunWith(Parameterized.class) public class AnrTimerTest { // The commonly used message timeout key. @@ -106,6 +112,16 @@ public class AnrTimerTest { } /** + * Force AnrTimer to use the test parameter for the feature flag. + */ + class TestInjector extends AnrTimer.Injector { + @Override + boolean anrTimerServiceEnabled() { + return mEnabled; + } + } + + /** * An instrumented AnrTimer. */ private static class TestAnrTimer extends AnrTimer<TestArg> { @@ -137,6 +153,17 @@ public class AnrTimerTest { assertEquals(actual.what, MSG_TIMEOUT); } + @Parameters(name = "featureEnabled={0}") + public static Collection<Object[]> data() { + return Arrays.asList(new Object[][] { {false}, {true} }); + } + + /** True if the feature is enabled. */ + private boolean mEnabled; + + public AnrTimerTest(boolean featureEnabled) { + mEnabled = featureEnabled; + } /** * Verify that a simple expiration succeeds. The timer is started for 10ms. The test diff --git a/services/tests/servicestests/src/com/android/server/utils/OWNERS b/services/tests/servicestests/src/com/android/server/utils/OWNERS index 5e2482825c74..f5b19a1c40ae 100644 --- a/services/tests/servicestests/src/com/android/server/utils/OWNERS +++ b/services/tests/servicestests/src/com/android/server/utils/OWNERS @@ -1,2 +1,5 @@ per-file EventLoggerTest.java = file:/platform/frameworks/av:/media/janitors/media_solutions_OWNERS per-file EventLoggerTest.java = jmtrivi@google.com + +# Bug component : 158088 = per-file AnrTimer*.java +per-file AnrTimer*.java = file:/PERFORMANCE_OWNERS diff --git a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java index 5febd02a75a8..8936dc61e6ef 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/DefaultDeviceEffectsApplierTest.java @@ -16,13 +16,28 @@ package com.android.server.notification; +import static android.app.UiModeManager.MODE_NIGHT_CUSTOM_TYPE_BEDTIME; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; import android.app.UiModeManager; import android.app.WallpaperManager; +import android.content.BroadcastReceiver; +import android.content.Intent; +import android.content.IntentFilter; import android.hardware.display.ColorDisplayManager; import android.os.PowerManager; import android.platform.test.flag.junit.SetFlagsRule; @@ -36,6 +51,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -55,7 +71,7 @@ public class DefaultDeviceEffectsApplierTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = new TestableContext(InstrumentationRegistry.getContext(), null); + mContext = spy(new TestableContext(InstrumentationRegistry.getContext(), null)); mContext.addMockSystemService(PowerManager.class, mPowerManager); mContext.addMockSystemService(ColorDisplayManager.class, mColorDisplayManager); mContext.addMockSystemService(UiModeManager.class, mUiModeManager); @@ -74,25 +90,33 @@ public class DefaultDeviceEffectsApplierTest { .setShouldDisplayGrayscale(true) .setShouldUseNightMode(true) .build(); - mApplier.apply(effects); + mApplier.apply(effects, UPDATE_ORIGIN_USER); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); verify(mColorDisplayManager).setSaturationLevel(eq(0)); verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); - verifyZeroInteractions(mUiModeManager); // Coming later; adding now so test fails then. :) + verify(mUiModeManager).setNightModeActivatedForCustomMode( + eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); } @Test - public void apply_removesEffects() { + public void apply_removesPreviouslyAppliedEffects() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + ZenDeviceEffects previousEffects = new ZenDeviceEffects.Builder() + .setShouldSuppressAmbientDisplay(true) + .setShouldDimWallpaper(true) + .build(); + mApplier.apply(previousEffects, UPDATE_ORIGIN_USER); + verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); + ZenDeviceEffects noEffects = new ZenDeviceEffects.Builder().build(); - mApplier.apply(noEffects); + mApplier.apply(noEffects, UPDATE_ORIGIN_USER); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(false)); - verify(mColorDisplayManager).setSaturationLevel(eq(100)); verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); - verifyZeroInteractions(mUiModeManager); + verifyZeroInteractions(mColorDisplayManager, mUiModeManager); } @Test @@ -107,9 +131,104 @@ public class DefaultDeviceEffectsApplierTest { .setShouldDisplayGrayscale(true) .setShouldUseNightMode(true) .build(); - mApplier.apply(effects); + mApplier.apply(effects, UPDATE_ORIGIN_USER); verify(mPowerManager).suppressAmbientDisplay(anyString(), eq(true)); // (And no crash from missing services). } + + @Test + public void apply_someEffects_onlyThoseEffectsApplied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + ZenDeviceEffects effects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldDisplayGrayscale(true) + .build(); + mApplier.apply(effects, UPDATE_ORIGIN_USER); + + verify(mColorDisplayManager).setSaturationLevel(eq(0)); + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); + + verify(mPowerManager, never()).suppressAmbientDisplay(anyString(), anyBoolean()); + verify(mUiModeManager, never()).setNightModeActivatedForCustomMode(anyInt(), anyBoolean()); + } + + @Test + public void apply_onlyEffectDeltaApplied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build(), + UPDATE_ORIGIN_USER); + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.6f)); + + // Apply a second effect and remove the first one. + mApplier.apply(new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build(), + UPDATE_ORIGIN_USER); + + // Wallpaper dimming was undone, Grayscale was applied, nothing else was touched. + verify(mWallpaperManager).setWallpaperDimAmount(eq(0.0f)); + verify(mColorDisplayManager).setSaturationLevel(eq(0)); + verifyZeroInteractions(mPowerManager); + verifyZeroInteractions(mUiModeManager); + } + + @Test + public void apply_darkThemeFromApp_appliedOnScreenOff() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = + ArgumentCaptor.forClass(BroadcastReceiver.class); + ArgumentCaptor<IntentFilter> intentFilterCaptor = + ArgumentCaptor.forClass(IntentFilter.class); + + when(mPowerManager.isInteractive()).thenReturn(true); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), + UPDATE_ORIGIN_APP); + + // Effect was not yet applied, but a broadcast receiver was registered. + verifyZeroInteractions(mUiModeManager); + verify(mContext).registerReceiver(broadcastReceiverCaptor.capture(), + intentFilterCaptor.capture(), anyInt()); + assertThat(intentFilterCaptor.getValue().getAction(0)).isEqualTo(Intent.ACTION_SCREEN_OFF); + BroadcastReceiver screenOffReceiver = broadcastReceiverCaptor.getValue(); + + // Now the "screen off" event comes. + screenOffReceiver.onReceive(mContext, new Intent(Intent.ACTION_SCREEN_OFF)); + + // So the effect is applied, and we stopped listening for this event. + verify(mUiModeManager).setNightModeActivatedForCustomMode( + eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mContext).unregisterReceiver(eq(screenOffReceiver)); + } + + @Test + public void apply_darkThemeFromAppWithScreenOff_appliedImmediately() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + when(mPowerManager.isInteractive()).thenReturn(false); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), + UPDATE_ORIGIN_APP); + + // Effect was applied, and no broadcast receiver was registered. + verify(mUiModeManager).setNightModeActivatedForCustomMode( + eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mContext, never()).registerReceiver(any(), any(), anyInt()); + } + + @Test + public void testDeviceEffects_darkThemeFromUser_appliedImmediately() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + + when(mPowerManager.isInteractive()).thenReturn(true); + + mApplier.apply(new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(), + UPDATE_ORIGIN_USER); + + // Effect was applied, and no broadcast receiver was registered. + verify(mUiModeManager).setNightModeActivatedForCustomMode( + eq(MODE_NIGHT_CUSTOM_TYPE_BEDTIME), eq(true)); + verify(mContext, never()).registerReceiver(any(), any(), anyInt()); + } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index ee08fd26d631..8bb11a45181e 100755 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -220,6 +220,7 @@ import android.service.notification.NotificationListenerService; import android.service.notification.NotificationRankingUpdate; import android.service.notification.NotificationStats; import android.service.notification.StatusBarNotification; +import android.service.notification.ZenModeConfig; import android.service.notification.ZenPolicy; import android.telecom.TelecomManager; import android.telephony.TelephonyManager; @@ -8929,8 +8930,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.addAutomaticZenRule(rule, "com.android.settings"); // verify that zen mode helper gets passed in a package name of "android" - verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(), - anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call + verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), + eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt()); } @Test @@ -8951,8 +8952,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.addAutomaticZenRule(rule, "com.android.settings"); // verify that zen mode helper gets passed in a package name of "android" - verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), anyString(), - anyInt(), eq(ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI)); // system call + verify(mockZenModeHelper).addAutomaticZenRule(eq("android"), eq(rule), + eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), anyInt()); } @Test @@ -8972,8 +8973,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { // verify that zen mode helper gets passed in the package name from the arg, not the owner verify(mockZenModeHelper).addAutomaticZenRule( - eq("another.package"), eq(rule), anyString(), anyInt(), - eq(ZenModeHelper.FROM_APP)); // doesn't count as a system/systemui call + eq("another.package"), eq(rule), eq(ZenModeConfig.UPDATE_ORIGIN_APP), + anyString(), anyInt()); // doesn't count as a system/systemui call } @Test @@ -13185,7 +13186,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy); - verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); } @Test @@ -13207,7 +13208,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy); - verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); } @Test @@ -13223,7 +13224,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0); mBinderService.setNotificationPolicy("package", policy); - verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyBoolean()); + verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt()); } @Test @@ -13271,7 +13272,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY); verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), - eq("package"), anyString(), anyInt(), anyBoolean()); + eq(ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI), anyString(), eq("package"), + anyInt()); } @Test @@ -13280,6 +13282,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); ZenModeHelper zenModeHelper = mock(ZenModeHelper.class); mService.mZenModeHelper = zenModeHelper; + mService.setCallerIsNormalPackage(); when(mConditionProviders.isPackageOrComponentAllowed(anyString(), anyInt())) .thenReturn(true); when(mCompanionMgr.getAssociations(anyString(), anyInt())) @@ -13292,7 +13295,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY); verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null), - eq("package"), anyString(), anyInt(), anyBoolean()); + eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt()); } @Test diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index fe21103096ca..5ddac034d116 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -67,7 +67,6 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -2370,7 +2369,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // create notification channel that can bypass dnd @@ -2380,18 +2379,18 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // delete channels mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2407,7 +2406,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // Recreate a channel & now the app has dnd access granted and can set the bypass dnd field @@ -2417,7 +2416,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2433,7 +2432,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // create notification channel that can bypass dnd, using local app level settings @@ -2443,18 +2442,18 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // delete channels mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel.getId(), uid, false); assertTrue(mHelper.areChannelsBypassingDnd()); // channel2 can still bypass DND - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); mHelper.deleteNotificationChannel(PKG_N_MR1, uid, channel2.getId(), uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2481,8 +2480,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), - anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2504,7 +2502,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2526,7 +2524,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel2, true, true, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2542,7 +2540,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.createNotificationChannel(PKG_N_MR1, uid, channel, true, false, uid, false); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // update channel so it CAN bypass dnd: @@ -2550,7 +2548,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel.setBypassDnd(true); mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true); assertTrue(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); // update channel so it can't bypass dnd: @@ -2558,7 +2556,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { channel.setBypassDnd(false); mHelper.updateNotificationChannel(PKG_N_MR1, uid, channel, true, SYSTEM_UID, true); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2571,7 +2569,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); mHelper.syncChannelsBypassingDnd(); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } @@ -2581,7 +2579,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); assertFalse(mHelper.areChannelsBypassingDnd()); - verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyBoolean()); + verify(mMockZenModeHelper, never()).setNotificationPolicy(any(), anyInt(), anyInt()); resetZenModeHelper(); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java index ef6fcedf7339..646ee3f6d206 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java @@ -45,6 +45,12 @@ import static android.provider.Settings.Global.ZEN_MODE_IMPORTANT_INTERRUPTIONS; import static android.provider.Settings.Global.ZEN_MODE_OFF; import static android.service.notification.Condition.STATE_FALSE; import static android.service.notification.Condition.STATE_TRUE; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_APP; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_INIT_USER; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_UNKNOWN; +import static android.service.notification.ZenModeConfig.UPDATE_ORIGIN_USER; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_CONTACTS; import static android.service.notification.ZenPolicy.PEOPLE_TYPE_STARRED; @@ -53,9 +59,6 @@ import static com.android.os.dnd.DNDProtoEnums.PEOPLE_STARRED; import static com.android.os.dnd.DNDProtoEnums.ROOT_CONFIG; import static com.android.os.dnd.DNDProtoEnums.STATE_ALLOW; import static com.android.os.dnd.DNDProtoEnums.STATE_DISALLOW; -import static com.android.server.notification.ZenModeHelper.FROM_APP; -import static com.android.server.notification.ZenModeHelper.FROM_SYSTEM_OR_SYSTEMUI; -import static com.android.server.notification.ZenModeHelper.FROM_USER; import static com.android.server.notification.ZenModeHelper.RULE_LIMIT_PER_PACKAGE; import static com.google.common.truth.Truth.assertThat; @@ -305,8 +308,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.writeXml(serializer, false, version, UserHandle.USER_ALL); serializer.endDocument(); serializer.flush(); - mZenModeHelper.setConfig(new ZenModeConfig(), null, "writing xml", Process.SYSTEM_UID, - true); + mZenModeHelper.setConfig(new ZenModeConfig(), null, UPDATE_ORIGIN_INIT, "writing xml", + Process.SYSTEM_UID); return baos; } @@ -321,7 +324,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { serializer.flush(); ZenModeConfig newConfig = new ZenModeConfig(); newConfig.user = userId; - mZenModeHelper.setConfig(newConfig, null, "writing xml", Process.SYSTEM_UID, true); + mZenModeHelper.setConfig(newConfig, null, UPDATE_ORIGIN_INIT, "writing xml", + Process.SYSTEM_UID); return baos; } @@ -838,7 +842,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mConfig = null; // will evaluate config to zen mode off for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenModeLocked("test", true); + mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -865,7 +869,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenModeLocked("test", true); + mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -892,7 +896,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; for (int i = 0; i < 3; i++) { // if zen doesn't change, zen should not reapply itself to the ringer - mZenModeHelper.evaluateZenModeLocked("test", true); + mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true); } verify(mAudioManager, never()).setRingerModeInternal(AudioManager.RINGER_MODE_NORMAL, mZenModeHelper.TAG); @@ -905,8 +909,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // Turn manual zen mode on - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, - "test", CUSTOM_PKG_UID, false); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, + null, "test", CUSTOM_PKG_UID); // audio manager shouldn't do anything until the handler processes its messages verify(mAudioManager, never()).updateRingerModeAffectedStreamsInternal(); @@ -1185,7 +1189,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { List<StatsEvent> events = new LinkedList<>(); mZenModeHelper.pullRules(events); - mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, "test", CUSTOM_PKG_UID, false); + mZenModeHelper.removeAutomaticZenRule(CUSTOM_RULE_ID, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); assertTrue(-1 == mZenModeHelper.mRulesUidCache.getOrDefault(CUSTOM_PKG_NAME + "|" + 0, -1)); } @@ -1240,12 +1245,14 @@ public class ZenModeHelperTest extends UiServiceTestCase { config10.user = 10; config10.allowAlarms = true; config10.allowMedia = true; - mZenModeHelper.setConfig(config10, null, "writeXml", Process.SYSTEM_UID, true); + mZenModeHelper.setConfig(config10, null, UPDATE_ORIGIN_INIT, "writeXml", + Process.SYSTEM_UID); ZenModeConfig config11 = mZenModeHelper.mConfig.copy(); config11.user = 11; config11.allowAlarms = false; config11.allowMedia = false; - mZenModeHelper.setConfig(config11, null, "writeXml", Process.SYSTEM_UID, true); + mZenModeHelper.setConfig(config11, null, UPDATE_ORIGIN_INIT, "writeXml", + Process.SYSTEM_UID); // Backup user 10 and reset values. ByteArrayOutputStream baos = writeXmlAndPurgeForUser(null, 10); @@ -1849,8 +1856,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); // We need the package name to be something that's not "android" so there aren't any // existing rules under that package. - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); assertNotNull(id); } try { @@ -1860,8 +1867,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1881,8 +1888,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(si), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); assertNotNull(id); } try { @@ -1892,8 +1899,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1913,8 +1920,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(si), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); assertNotNull(id); } try { @@ -1924,8 +1931,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule("pkgname", zenRule, UPDATE_ORIGIN_APP, + "test", CUSTOM_PKG_UID); fail("allowed too many rules to be created"); } catch (IllegalArgumentException e) { // yay @@ -1940,8 +1947,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); @@ -1961,8 +1968,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ComponentName("android", "ScheduleConditionProvider"), ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); @@ -1985,11 +1992,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); mZenModeHelper.setAutomaticZenRuleState(zenRule.getConditionId(), new Condition(zenRule.getConditionId(), "", STATE_TRUE), - CUSTOM_PKG_UID, false); + UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertEquals(STATE_TRUE, ruleInConfig.condition.state); @@ -2004,8 +2012,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); AutomaticZenRule zenRule2 = new AutomaticZenRule("NEW", null, @@ -2014,7 +2022,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - mZenModeHelper.updateAutomaticZenRule(id, zenRule2, "", CUSTOM_PKG_UID, FROM_APP); + mZenModeHelper.updateAutomaticZenRule(id, zenRule2, UPDATE_ORIGIN_APP, "", CUSTOM_PKG_UID); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertEquals("NEW", ruleInConfig.name); @@ -2029,15 +2037,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertTrue(ruleInConfig != null); assertEquals(zenRule.getName(), ruleInConfig.name); - mZenModeHelper.removeAutomaticZenRule(id, "test", CUSTOM_PKG_UID, false); + mZenModeHelper.removeAutomaticZenRule(id, UPDATE_ORIGIN_APP, "test", CUSTOM_PKG_UID); assertNull(mZenModeHelper.mConfig.automaticRules.get(id)); } @@ -2049,16 +2057,16 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), new ZenPolicy.Builder().build(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, "test", - CUSTOM_PKG_UID, FROM_APP); + String id = mZenModeHelper.addAutomaticZenRule(null, zenRule, UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); assertTrue(id != null); ZenModeConfig.ZenRule ruleInConfig = mZenModeHelper.mConfig.automaticRules.get(id); assertTrue(ruleInConfig != null); assertEquals(zenRule.getName(), ruleInConfig.name); - mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), "test", - CUSTOM_PKG_UID, false); + mZenModeHelper.removeAutomaticZenRules(mContext.getPackageName(), UPDATE_ORIGIN_APP, "test", + CUSTOM_PKG_UID); assertNull(mZenModeHelper.mConfig.automaticRules.get(id)); } @@ -2073,17 +2081,18 @@ public class ZenModeHelperTest extends UiServiceTestCase { new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule("android", zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", new ComponentName("android", "ScheduleConditionProvider"), sharedUri, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id2 = mZenModeHelper.addAutomaticZenRule("android", zenRule2, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); Condition condition = new Condition(sharedUri, "", STATE_TRUE); - mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true); + mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) { if (rule.id.equals(id)) { @@ -2097,7 +2106,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { } condition = new Condition(sharedUri, "", STATE_FALSE); - mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, Process.SYSTEM_UID, true); + mZenModeHelper.setAutomaticZenRuleState(sharedUri, condition, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); for (ZenModeConfig.ZenRule rule : mZenModeHelper.mConfig.automaticRules.values()) { if (rule.id.equals(id)) { @@ -2133,7 +2143,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(zde) .build(), - "reasons", 0, FROM_APP); + UPDATE_ORIGIN_APP, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo( @@ -2167,7 +2177,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(zde) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); @@ -2195,7 +2205,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(zde) .build(), - "reasons", 0, FROM_USER); + UPDATE_ORIGIN_USER, + "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo(zde); @@ -2212,7 +2223,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); ZenDeviceEffects updateFromApp = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // Good @@ -2223,7 +2234,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(updateFromApp) .build(), - "reasons", 0, FROM_APP); + UPDATE_ORIGIN_APP, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo( @@ -2244,7 +2255,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); ZenDeviceEffects updateFromSystem = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // Good @@ -2254,7 +2265,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new AutomaticZenRule.Builder("Rule", CONDITION_ID) .setDeviceEffects(updateFromSystem) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromSystem); @@ -2271,7 +2282,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { .setOwner(OWNER) .setDeviceEffects(original) .build(), - "reasons", 0, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "reasons", 0); ZenDeviceEffects updateFromUser = new ZenDeviceEffects.Builder() .setShouldUseNightMode(true) // Good @@ -2281,7 +2292,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { new AutomaticZenRule.Builder("Rule", CONDITION_ID) .setDeviceEffects(updateFromUser) .build(), - "reasons", 0, FROM_USER); + UPDATE_ORIGIN_USER, "reasons", 0); AutomaticZenRule savedRule = mZenModeHelper.getAutomaticZenRule(ruleId); assertThat(savedRule.getDeviceEffects()).isEqualTo(updateFromUser); @@ -2293,16 +2304,16 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // note that caller=null because that's how it comes in from NMS.setZenMode - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // confirm that setting zen mode via setManualZenMode changed the zen mode correctly assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode); assertEquals(true, mZenModeHelper.mConfig.manualRule.allowManualInvocation); // and also that it works to turn it back off again - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", null, Process.SYSTEM_UID); assertEquals(Global.ZEN_MODE_OFF, mZenModeHelper.mZenMode); } @@ -2312,15 +2323,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // note that caller=null because that's how it comes in from NMS.setZenMode - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // confirm that setting zen mode via setManualZenMode changed the zen mode correctly assertEquals(ZEN_MODE_IMPORTANT_INTERRUPTIONS, mZenModeHelper.mZenMode); // and also that it works to turn it back off again - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + null, Process.SYSTEM_UID); assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode); } @@ -2332,13 +2343,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Turn zen mode on (to important_interruptions) // Need to additionally call the looper in order to finish the post-apply-config process - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // Now turn zen mode off, but via a different package UID -- this should get registered as // "not an action by the user" because some other app is changing zen mode - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", CUSTOM_PKG_UID, - false); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", null, + CUSTOM_PKG_UID); // In total, this should be 2 loggable changes assertEquals(2, mZenModeEventLogger.numLoggedChanges()); @@ -2397,17 +2408,18 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, - "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + Process.SYSTEM_UID); // Event 2: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + Process.SYSTEM_UID); // Add a new system rule AutomaticZenRule systemRule = new AutomaticZenRule("systemRule", @@ -2417,15 +2429,16 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String systemId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), systemRule, - "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Event 3: turn on the system rule mZenModeHelper.setAutomaticZenRuleState(systemId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Event 4: "User" deletes the rule - mZenModeHelper.removeAutomaticZenRule(systemId, "", Process.SYSTEM_UID, true); + mZenModeHelper.removeAutomaticZenRule(systemId, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + Process.SYSTEM_UID); // In total, this represents 4 events assertEquals(4, mZenModeEventLogger.numLoggedChanges()); @@ -2486,26 +2499,26 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // First just turn zen mode on - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // Now change the policy slightly; want to confirm that this'll be reflected in the logs ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); newConfig.allowAlarms = true; newConfig.allowRepeatCallers = false; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Turn zen mode off; we want to make sure policy changes do not get logged when zen mode // is off. - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + null, Process.SYSTEM_UID); // Change the policy again newConfig.allowMessages = false; newConfig.allowRepeatCallers = true; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Total events: we only expect ones for turning on, changing policy, and turning off assertEquals(3, mZenModeEventLogger.numLoggedChanges()); @@ -2548,8 +2561,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Rule 2, same as rule 1 AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", @@ -2558,8 +2571,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Rule 3, has stricter settings than the default settings ZenModeConfig ruleConfig = mZenModeHelper.mConfig.copy(); @@ -2572,28 +2585,28 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), ruleConfig.toZenPolicy(), NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id3 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule3, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // First: turn on rule 1 mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Second: turn on rule 2 mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Third: turn on rule 3 mZenModeHelper.setAutomaticZenRuleState(id3, new Condition(zenRule3.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Fourth: Turn *off* rule 2 mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_FALSE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // This should result in a total of four events assertEquals(4, mZenModeEventLogger.numLoggedChanges()); @@ -2649,7 +2662,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Artificially turn zen mode "on". Re-evaluating zen mode should cause it to turn back off // given that we don't have any zen rules active. mZenModeHelper.mZenMode = ZEN_MODE_IMPORTANT_INTERRUPTIONS; - mZenModeHelper.evaluateZenModeLocked("test", true); + mZenModeHelper.evaluateZenModeLocked(UPDATE_ORIGIN_UNKNOWN, "test", true); // Check that the change actually took: zen mode should be off now assertEquals(ZEN_MODE_OFF, mZenModeHelper.mZenMode); @@ -2672,8 +2685,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Rule 2, same as rule 1 but owned by the system AutomaticZenRule zenRule2 = new AutomaticZenRule("name2", @@ -2682,37 +2695,37 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Turn on rule 1; call looks like it's from the system. Because setting a condition is // typically an automatic (non-user-initiated) action, expect the calling UID to be // re-evaluated to the one associat.d with CUSTOM_PKG_NAME. mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Second: turn on rule 2. This is a system-owned rule and the UID should not be modified // (nor even looked up; the mock PackageManager won't handle "android" as input). mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Disable rule 1. Because this looks like a user action, the UID should not be modified // from the system-provided one. zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(id, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(id, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", + Process.SYSTEM_UID); // Add a manual rule. Any manual rule changes should not get calling uids reassigned. - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - CUSTOM_PKG_UID, false); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, + "", null, CUSTOM_PKG_UID); // Change rule 2's condition, but from some other UID. Since it doesn't look like it's from // the system, we keep the UID info. mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_FALSE), - 12345, false); + UPDATE_ORIGIN_APP, 12345); // That was 5 events total assertEquals(5, mZenModeEventLogger.numLoggedChanges()); @@ -2767,26 +2780,26 @@ public class ZenModeHelperTest extends UiServiceTestCase { // Turn on zen mode with a manual rule with an enabler set. This should *not* count // as a user action, and *should* get its UID reassigned. mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - CUSTOM_PKG_NAME, "", Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", CUSTOM_PKG_NAME, Process.SYSTEM_UID); // Now change apps bypassing to true ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); newConfig.areChannelsBypassingDnd = true; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // and then back to false, all without changing anything else newConfig.areChannelsBypassingDnd = false; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Turn off manual mode, call from a package: don't reset UID even though enabler is set - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, - CUSTOM_PKG_NAME, "", 12345, false); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "", + CUSTOM_PKG_NAME, 12345); // And likewise when turning it back on again - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, - CUSTOM_PKG_NAME, "", 12345, false); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, UPDATE_ORIGIN_APP, + "", CUSTOM_PKG_NAME, 12345); // These are 5 events in total. assertEquals(5, mZenModeEventLogger.numLoggedChanges()); @@ -2831,15 +2844,15 @@ public class ZenModeHelperTest extends UiServiceTestCase { setupZenConfig(); // First just turn zen mode on - mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_IMPORTANT_INTERRUPTIONS, null, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "", null, Process.SYSTEM_UID); // Now change only the channels part of the policy; want to confirm that this'll be // reflected in the logs ZenModeConfig newConfig = mZenModeHelper.mConfig.copy(); newConfig.allowPriorityChannels = false; - mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), Process.SYSTEM_UID, - true); + mZenModeHelper.setNotificationPolicy(newConfig.toNotificationPolicy(), + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Total events: one for turning on, one for changing policy assertThat(mZenModeEventLogger.numLoggedChanges()).isEqualTo(2); @@ -2881,13 +2894,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable the rule mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // inspect the consolidated policy. Based on setupZenConfig() values. assertFalse(mZenModeHelper.mConsolidatedPolicy.allowAlarms()); @@ -2924,13 +2937,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), customPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable the rule; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // since this is the only active rule, the consolidated policy should match the custom // policy for every field specified, and take default values for unspecified things @@ -2960,13 +2973,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable rule 1 mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // custom policy for rule 2 ZenPolicy customPolicy = new ZenPolicy.Builder() @@ -2985,12 +2998,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { customPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, - "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable rule 2; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // now both rules should be on, and the consolidated policy should reflect the most // restrictive option of each of the two @@ -3022,13 +3035,13 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.toScheduleConditionId(new ScheduleInfo()), customPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); - String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, "test", - Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + String id = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule, + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable the rule; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // confirm that channels make it through assertTrue(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels()); @@ -3045,12 +3058,12 @@ public class ZenModeHelperTest extends UiServiceTestCase { strictPolicy, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); String id2 = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), zenRule2, - "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // enable rule 2; this will update the consolidated policy mZenModeHelper.setAutomaticZenRuleState(id2, new Condition(zenRule2.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // rule 2 should override rule 1 assertFalse(mZenModeHelper.mConsolidatedPolicy.allowPriorityChannels()); @@ -3121,7 +3134,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenModeConfig.ZenRule rule = new ZenModeConfig.ZenRule(); - mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, true, FROM_APP); + mZenModeHelper.populateZenRule(OWNER.getPackageName(), azr, rule, UPDATE_ORIGIN_APP, true); assertEquals(NAME, rule.name); assertEquals(OWNER, rule.component); @@ -3149,7 +3162,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -3165,8 +3178,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.addCallback(callback); zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_DISABLED, actualStatus[0]); @@ -3184,7 +3197,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, false); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -3200,8 +3213,8 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.addCallback(callback); zenRule.setEnabled(true); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_ENABLED, actualStatus[0]); @@ -3220,7 +3233,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[1]; @@ -3237,7 +3250,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_ACTIVATED, actualStatus[0]); @@ -3256,7 +3269,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[2]; @@ -3274,10 +3287,10 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + null, "", Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]); @@ -3296,7 +3309,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); CountDownLatch latch = new CountDownLatch(1); final int[] actualStatus = new int[2]; @@ -3314,11 +3327,11 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_FALSE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); assertTrue(latch.await(500, TimeUnit.MILLISECONDS)); assertEquals(AUTOMATIC_RULE_STATUS_DEACTIVATED, actualStatus[1]); @@ -3336,21 +3349,21 @@ public class ZenModeHelperTest extends UiServiceTestCase { null, NotificationManager.INTERRUPTION_FILTER_PRIORITY, true); final String createdId = mZenModeHelper.addAutomaticZenRule(mContext.getPackageName(), - zenRule, "test", Process.SYSTEM_UID, FROM_SYSTEM_OR_SYSTEMUI); + zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, "test", Process.SYSTEM_UID); // Event 1: Mimic the rule coming on automatically by setting the Condition to STATE_TRUE mZenModeHelper.setAutomaticZenRuleState(createdId, new Condition(zenRule.getConditionId(), "", STATE_TRUE), - Process.SYSTEM_UID, true); + UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, Process.SYSTEM_UID); // Event 2: Snooze rule by turning off DND - mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, null, "", - Process.SYSTEM_UID, true); + mZenModeHelper.setManualZenMode(Global.ZEN_MODE_OFF, null, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", null, Process.SYSTEM_UID); // Event 3: "User" turns off the automatic rule (sets it to not enabled) zenRule.setEnabled(false); - mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, "", Process.SYSTEM_UID, - FROM_SYSTEM_OR_SYSTEMUI); + mZenModeHelper.updateAutomaticZenRule(createdId, zenRule, UPDATE_ORIGIN_SYSTEM_OR_SYSTEMUI, + "", Process.SYSTEM_UID); assertEquals(false, mZenModeHelper.mConfig.automaticRules.get(createdId).snoozing); } @@ -3359,18 +3372,20 @@ public class ZenModeHelperTest extends UiServiceTestCase { public void testDeviceEffects_applied() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT)); ZenDeviceEffects effects = new ZenDeviceEffects.Builder() .setShouldSuppressAmbientDisplay(true) .setShouldDimWallpaper(true) .build(); String ruleId = addRuleWithEffects(effects); - verify(mDeviceEffectsApplier, never()).apply(any()); + verifyNoMoreInteractions(mDeviceEffectsApplier); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier).apply(eq(effects)); + verify(mDeviceEffectsApplier).apply(eq(effects), eq(UPDATE_ORIGIN_APP)); } @Test @@ -3380,31 +3395,66 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); String ruleId = addRuleWithEffects(zde); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier).apply(eq(zde)); + verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP)); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_FALSE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS)); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_APP)); } @Test + public void testDeviceEffects_changeToConsolidatedEffects_applied() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT)); + + String ruleId = addRuleWithEffects( + new ZenDeviceEffects.Builder().setShouldDisplayGrayscale(true).build()); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); + mTestableLooper.processAllMessages(); + verify(mDeviceEffectsApplier).apply( + eq(new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(true) + .build()), + eq(UPDATE_ORIGIN_APP)); + + // Now create and activate a second rule that adds more effects. + String secondRuleId = addRuleWithEffects( + new ZenDeviceEffects.Builder().setShouldDimWallpaper(true).build()); + mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); + mTestableLooper.processAllMessages(); + + verify(mDeviceEffectsApplier).apply( + eq(new ZenDeviceEffects.Builder() + .setShouldDisplayGrayscale(true) + .setShouldDimWallpaper(true) + .build()), + eq(UPDATE_ORIGIN_APP)); + } + @Test public void testDeviceEffects_noChangeToConsolidatedEffects_notApplied() { mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT)); ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); String ruleId = addRuleWithEffects(zde); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier).apply(eq(zde)); + verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_APP)); // Now create and activate a second rule that doesn't add any more effects. String secondRuleId = addRuleWithEffects(zde); - mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, CUSTOM_PKG_UID, - false); + mZenModeHelper.setAutomaticZenRuleState(secondRuleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); verifyNoMoreInteractions(mDeviceEffectsApplier); @@ -3416,21 +3466,50 @@ public class ZenModeHelperTest extends UiServiceTestCase { ZenDeviceEffects zde = new ZenDeviceEffects.Builder().setShouldUseNightMode(true).build(); String ruleId = addRuleWithEffects(zde); - verify(mDeviceEffectsApplier, never()).apply(any()); + verify(mDeviceEffectsApplier, never()).apply(any(), anyInt()); - mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, CUSTOM_PKG_UID, false); + mZenModeHelper.setAutomaticZenRuleState(ruleId, CONDITION_TRUE, UPDATE_ORIGIN_APP, + CUSTOM_PKG_UID); mTestableLooper.processAllMessages(); - verify(mDeviceEffectsApplier, never()).apply(any()); + verify(mDeviceEffectsApplier, never()).apply(any(), anyInt()); mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); - verify(mDeviceEffectsApplier).apply(eq(zde)); + verify(mDeviceEffectsApplier).apply(eq(zde), eq(UPDATE_ORIGIN_INIT)); + } + + @Test + public void testDeviceEffects_onUserSwitch_appliedImmediately() { + mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API); + mZenModeHelper.setDeviceEffectsApplier(mDeviceEffectsApplier); + verify(mDeviceEffectsApplier).apply(eq(NO_EFFECTS), eq(UPDATE_ORIGIN_INIT)); + + // Initialize default configurations (default rules) for both users. + mZenModeHelper.onUserSwitched(1); + mZenModeHelper.onUserSwitched(2); + + // Current user is now 2. Tweak a rule for user 1 so it's active and has effects. + ZenRule user1Rule = mZenModeHelper.mConfigs.get(1).automaticRules.valueAt(0); + user1Rule.enabled = true; + user1Rule.condition = new Condition(user1Rule.conditionId, "on", STATE_TRUE); + user1Rule.zenDeviceEffects = new ZenDeviceEffects.Builder() + .setShouldDimWallpaper(true) + .setShouldUseNightMode(true) + .build(); + verifyNoMoreInteractions(mDeviceEffectsApplier); + + mZenModeHelper.onUserSwitched(1); + mTestableLooper.processAllMessages(); + + verify(mDeviceEffectsApplier).apply(eq(user1Rule.zenDeviceEffects), + eq(UPDATE_ORIGIN_INIT_USER)); } private String addRuleWithEffects(ZenDeviceEffects effects) { AutomaticZenRule rule = new AutomaticZenRule.Builder("Test", CONDITION_ID) .setDeviceEffects(effects) .build(); - return mZenModeHelper.addAutomaticZenRule("pkg", rule, "", CUSTOM_PKG_UID, FROM_APP); + return mZenModeHelper.addAutomaticZenRule("pkg", rule, UPDATE_ORIGIN_APP, "", + CUSTOM_PKG_UID); } @Test @@ -3455,7 +3534,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, ZEN_MODE_IMPORTANT_INTERRUPTIONS); - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0); assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, @@ -3505,7 +3584,7 @@ public class ZenModeHelperTest extends UiServiceTestCase { assertThat(mZenModeHelper.mConfig.automaticRules).hasSize(1); assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isFalse(); - mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, "test", "test", 0, true); + mZenModeHelper.setManualZenMode(ZEN_MODE_OFF, null, UPDATE_ORIGIN_APP, "test", "test", 0); assertThat(mZenModeHelper.mConfig.automaticRules.valueAt(0).snoozing).isTrue(); mZenModeHelper.applyGlobalZenModeAsImplicitZenRule(CUSTOM_PKG_NAME, CUSTOM_PKG_UID, diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java deleted file mode 100644 index 49efd1bdd92a..000000000000 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.vibrator; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.RemoteException; - -import org.junit.Before; -import org.junit.Test; - -public class VibratorControlServiceTest { - - private VibratorControlService mVibratorControlService; - private final Object mLock = new Object(); - - @Before - public void setUp() throws Exception { - mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock); - } - - @Test - public void testRegisterVibratorController() throws RemoteException { - FakeVibratorController fakeController = new FakeVibratorController(); - mVibratorControlService.registerVibratorController(fakeController); - - assertThat(fakeController.isLinkedToDeath).isTrue(); - } - - @Test - public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest() - throws RemoteException { - FakeVibratorController fakeController = new FakeVibratorController(); - mVibratorControlService.registerVibratorController(fakeController); - mVibratorControlService.unregisterVibratorController(fakeController); - assertThat(fakeController.isLinkedToDeath).isFalse(); - } - - @Test - public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest() - throws RemoteException { - FakeVibratorController fakeController1 = new FakeVibratorController(); - FakeVibratorController fakeController2 = new FakeVibratorController(); - mVibratorControlService.registerVibratorController(fakeController1); - - mVibratorControlService.unregisterVibratorController(fakeController2); - assertThat(fakeController1.isLinkedToDeath).isTrue(); - } -} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java deleted file mode 100644 index 79abe21a301d..000000000000 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.vibrator; - -import static com.google.common.truth.Truth.assertThat; - -import android.os.RemoteException; - -import org.junit.Before; -import org.junit.Test; - -public class VibratorControllerHolderTest { - - private final FakeVibratorController mFakeVibratorController = new FakeVibratorController(); - private VibratorControllerHolder mVibratorControllerHolder; - - @Before - public void setUp() throws Exception { - mVibratorControllerHolder = new VibratorControllerHolder(); - } - - @Test - public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException { - mVibratorControllerHolder.setVibratorController(mFakeVibratorController); - assertThat(mVibratorControllerHolder.getVibratorController()) - .isEqualTo(mFakeVibratorController); - assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); - } - - @Test - public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath() - throws RemoteException { - mVibratorControllerHolder.setVibratorController(mFakeVibratorController); - mVibratorControllerHolder.setVibratorController(null); - assertThat(mFakeVibratorController.isLinkedToDeath).isFalse(); - assertThat(mVibratorControllerHolder.getVibratorController()).isNull(); - } - - @Test - public void testBinderDied_withValidController_unlinksVibratorControllerToDeath() - throws RemoteException { - mVibratorControllerHolder.setVibratorController(mFakeVibratorController); - mVibratorControllerHolder.binderDied(mFakeVibratorController); - assertThat(mFakeVibratorController.isLinkedToDeath).isFalse(); - assertThat(mVibratorControllerHolder.getVibratorController()).isNull(); - } - - @Test - public void testBinderDied_withInvalidController_ignoresRequest() - throws RemoteException { - mVibratorControllerHolder.setVibratorController(mFakeVibratorController); - FakeVibratorController imposterVibratorController = new FakeVibratorController(); - mVibratorControllerHolder.binderDied(imposterVibratorController); - assertThat(mFakeVibratorController.isLinkedToDeath).isTrue(); - assertThat(mVibratorControllerHolder.getVibratorController()) - .isEqualTo(mFakeVibratorController); - } -} diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java index a105649c9b5b..4e9bbe0a28fe 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -307,10 +307,9 @@ public class VibratorManagerServiceTest { @Override void addService(String name, IBinder service) { - if (service instanceof VibratorManagerService.ExternalVibratorService) { - mExternalVibratorService = - (VibratorManagerService.ExternalVibratorService) service; - } + Object serviceInstance = service; + mExternalVibratorService = + (VibratorManagerService.ExternalVibratorService) serviceInstance; } HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider( @@ -1399,6 +1398,17 @@ public class VibratorManagerServiceTest { } @Test + public void performHapticFeedback_usesServiceAsToken() throws Exception { + VibratorManagerService service = createSystemReadyService(); + + HalVibration vibration = + performHapticFeedbackAndWaitUntilFinished( + service, HapticFeedbackConstants.SCROLL_TICK, /* always= */ true); + + assertTrue(vibration.callerToken == service); + } + + @Test public void vibrate_withIntensitySettings_appliesSettingsToScaleVibrations() throws Exception { int defaultNotificationIntensity = mVibrator.getDefaultVibrationIntensity(VibrationAttributes.USAGE_NOTIFICATION); diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java deleted file mode 100644 index 7e235870cedc..000000000000 --- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (C) 2020 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.vibrator; - -import android.annotation.NonNull; -import android.frameworks.vibrator.IVibratorController; -import android.os.IBinder; -import android.os.RemoteException; - -/** - * Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for - * testing. - */ -public final class FakeVibratorController extends IVibratorController.Stub { - - public boolean isLinkedToDeath = false; - - @Override - public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException { - - } - - @Override - public int getInterfaceVersion() throws RemoteException { - return 0; - } - - @Override - public String getInterfaceHash() throws RemoteException { - return null; - } - - @Override - public void linkToDeath(@NonNull DeathRecipient recipient, int flags) { - super.linkToDeath(recipient, flags); - isLinkedToDeath = true; - } - - @Override - public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) { - isLinkedToDeath = false; - return super.unlinkToDeath(recipient, flags); - } -} 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/FrameRateSelectionPriorityTests.java b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java index c0a90b20c999..e77c14a60179 100644 --- a/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/FrameRateSelectionPriorityTests.java @@ -19,10 +19,13 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyFloat; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.eq; import static com.android.dx.mockito.inline.extended.ExtendedMockito.never; import static com.android.dx.mockito.inline.extended.ExtendedMockito.times; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; +import static com.android.window.flags.Flags.explicitRefreshRateHints; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; @@ -62,9 +65,11 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote(); private static final FrameRateVote FRAME_RATE_VOTE_60_EXACT = - new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_EXACT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_60_PREFERRED = - new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + new FrameRateVote(60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); WindowState createWindow(String name) { WindowState window = createWindow(null, TYPE_APPLICATION, name); @@ -110,6 +115,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { any(SurfaceControl.class), anyInt()); verify(appWindow.getPendingTransaction(), never()).setFrameRate( any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); } @Test @@ -140,6 +147,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), 1); verify(appWindow.getPendingTransaction(), never()).setFrameRate( any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); } @Test @@ -175,8 +184,17 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), 0); verify(appWindow.getPendingTransaction(), times(2)).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 1); - verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( + eq(appWindow.getSurfaceControl()), anyFloat(), + eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS)); + if (explicitRefreshRateHints()) { + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + } else { + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); + } } @Test @@ -202,8 +220,17 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction()).setFrameRateSelectionPriority( appWindow.getSurfaceControl(), 2); - verify(appWindow.getPendingTransaction(), never()).setFrameRate( - any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( + eq(appWindow.getSurfaceControl()), anyFloat(), + eq(Surface.FRAME_RATE_COMPATIBILITY_EXACT), eq(Surface.CHANGE_FRAME_RATE_ALWAYS)); + if (explicitRefreshRateHints()) { + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + } else { + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); + } } @Test @@ -229,6 +256,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction(), never()).setFrameRate( any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); } @Test @@ -256,6 +285,14 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_EXACT, Surface.CHANGE_FRAME_RATE_ALWAYS); + if (explicitRefreshRateHints()) { + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + } else { + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); + } } @Test @@ -283,6 +320,8 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { appWindow.getSurfaceControl(), RefreshRatePolicy.LAYER_PRIORITY_UNSET); verify(appWindow.getPendingTransaction(), never()).setFrameRate( any(SurfaceControl.class), anyInt(), anyInt(), anyInt()); + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); } @Test @@ -310,5 +349,13 @@ public class FrameRateSelectionPriorityTests extends WindowTestsBase { verify(appWindow.getPendingTransaction(), times(1)).setFrameRate( appWindow.getSurfaceControl(), 60, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, Surface.CHANGE_FRAME_RATE_ALWAYS); + if (explicitRefreshRateHints()) { + verify(appWindow.getPendingTransaction(), times(1)).setFrameRateSelectionStrategy( + appWindow.getSurfaceControl(), + SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); + } else { + verify(appWindow.getPendingTransaction(), never()).setFrameRateSelectionStrategy( + any(SurfaceControl.class), anyInt()); + } } } 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/RefreshRatePolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java index 49a888689e60..c9a83b0bc2ee 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RefreshRatePolicyTest.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import static android.view.SurfaceControl.FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN; import static android.view.SurfaceControl.RefreshRateRange.FLOAT_TOLERANCE; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; @@ -69,15 +70,20 @@ public class RefreshRatePolicyTest extends WindowTestsBase { private static final FrameRateVote FRAME_RATE_VOTE_NONE = new FrameRateVote(); private static final FrameRateVote FRAME_RATE_VOTE_DENY_LIST = - new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_LOW_EXACT = - new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_HI_EXACT = - new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT); + new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_EXACT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_LOW_PREFERRED = - new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + new FrameRateVote(LOW_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); private static final FrameRateVote FRAME_RATE_VOTE_HI_PREFERRED = - new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT); + new FrameRateVote(HI_REFRESH_RATE, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT, + FRAME_RATE_SELECTION_STRATEGY_OVERRIDE_CHILDREN); // Parcel and Unparcel the LayoutParams in the window state to test the path the object // travels from the app's process to system server 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/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java index 0e1e0c8e3ad3..3d2340cca378 100644 --- a/services/usage/java/com/android/server/usage/UsageStatsService.java +++ b/services/usage/java/com/android/server/usage/UsageStatsService.java @@ -332,6 +332,7 @@ public class UsageStatsService extends SystemService implements mUsageEventListeners.valueAt(i).onUsageEvent(userId, event); } } + return true; } } return false; @@ -1973,6 +1974,8 @@ public class UsageStatsService extends SystemService implements + ": " + Flags.userInteractionTypeApi()); pw.println(" " + Flags.FLAG_USE_PARCELED_LIST + ": " + Flags.useParceledList()); + pw.println(" " + Flags.FLAG_FILTER_BASED_EVENT_QUERY_API + + ": " + Flags.filterBasedEventQueryApi()); final int[] userIds; synchronized (mLock) { @@ -2245,7 +2248,7 @@ public class UsageStatsService extends SystemService implements final int callingUid = Binder.getCallingUid(); final int callingPid = Binder.getCallingPid(); final boolean obfuscateInstantApps = shouldObfuscateInstantAppsForCaller( - callingUid, userId); + callingUid, UserHandle.getCallingUserId()); final long token = Binder.clearCallingIdentity(); try { @@ -2384,6 +2387,7 @@ public class UsageStatsService extends SystemService implements if (!hasQueryPermission(callingPackage)) { return null; } + return queryEventsHelper(UserHandle.getCallingUserId(), query.getBeginTimeMillis(), query.getEndTimeMillis(), callingPackage, query.getEventTypeFilter()); } diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java index 1e686873c1b0..7cb2cc398c46 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. * @@ -9814,7 +9892,6 @@ public class CarrierConfigManager { * * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_CANCELED * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_TIMEOUT - * @see TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_USER_DISABLED */ public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = @@ -10628,6 +10705,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)); } diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java index e12a815a84f5..b356fde53417 100644 --- a/telephony/java/android/telephony/SubscriptionManager.java +++ b/telephony/java/android/telephony/SubscriptionManager.java @@ -109,10 +109,6 @@ import java.util.stream.Collectors; * Then for SDK 35+, if the caller identity is personal profile, then * {@link #getActiveSubscriptionInfoList} will return subscription 1 only and vice versa. * - * <p>If the caller needs to see all subscriptions across user profiles, - * use {@link #createForAllUserProfiles} to convert the instance to see all. Additional permission - * may be required as documented on the each API. - * */ @SystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE) @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) @@ -1815,9 +1811,6 @@ public class SubscriptionManager { * Then for SDK 35+, if the caller identity is personal profile, then this will return * subscription 1 only and vice versa. * - * <p>If the caller needs to see all subscriptions across user profiles, - * use {@link #createForAllUserProfiles} to convert this instance to see all. - * * <p> The records will be sorted by {@link SubscriptionInfo#getSimSlotIndex} then by * {@link SubscriptionInfo#getSubscriptionId}. * @@ -2085,9 +2078,6 @@ public class SubscriptionManager { * Android SDK 35(V) and above, while Android SDK 34(U) and below can see all subscriptions as * it does today. * - * <p>If the caller needs to see all subscriptions across user profiles, - * use {@link #createForAllUserProfiles} to convert this instance to see all. - * * @return The current number of active subscriptions. * * @see #getActiveSubscriptionInfoList() diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java new file mode 100644 index 000000000000..068dfe8f3d11 --- /dev/null +++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java @@ -0,0 +1,362 @@ +/* + * 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.hoststubgen.nativesubstitution; + +import android.os.BadParcelableException; +import android.os.Parcel; + +import java.util.Arrays; +import java.util.HashMap; + +/** + * Native implementation substitutions for the LongArrayMultiStateCounter class. + */ +public class LongArrayMultiStateCounter_host { + + /** + * A reimplementation of {@link com.android.internal.os.LongArrayMultiStateCounter}, only in + * Java instead of native. The majority of the code (in C++) can be found in + * /frameworks/native/libs/battery/MultiStateCounter.h + */ + private static class LongArrayMultiStateCounterRavenwood { + private final int mStateCount; + private final int mArrayLength; + private int mCurrentState; + private long mLastStateChangeTimestampMs = -1; + private long mLastUpdateTimestampMs = -1; + private boolean mEnabled = true; + + private static class State { + private long mTimeInStateSinceUpdate; + private long[] mCounter; + } + + private final State[] mStates; + private final long[] mValues; + private final long[] mDelta; + + LongArrayMultiStateCounterRavenwood(int stateCount, int arrayLength) { + mStateCount = stateCount; + mArrayLength = arrayLength; + mStates = new State[stateCount]; + for (int i = 0; i < mStateCount; i++) { + mStates[i] = new State(); + mStates[i].mCounter = new long[mArrayLength]; + } + mValues = new long[mArrayLength]; + mDelta = new long[mArrayLength]; + } + + public void setEnabled(boolean enabled, long timestampMs) { + if (enabled == mEnabled) { + return; + } + + if (!enabled) { + setState(mCurrentState, timestampMs); + mEnabled = false; + } else { + if (timestampMs < mLastUpdateTimestampMs) { + timestampMs = mLastUpdateTimestampMs; + } + + if (mLastStateChangeTimestampMs >= 0) { + mLastStateChangeTimestampMs = timestampMs; + } + mEnabled = true; + } + } + + public void setState(int state, long timestampMs) { + if (mEnabled && mLastStateChangeTimestampMs >= 0 && mLastUpdateTimestampMs >= 0) { + if (timestampMs < mLastUpdateTimestampMs) { + timestampMs = mLastUpdateTimestampMs; + } + + if (timestampMs >= mLastStateChangeTimestampMs) { + mStates[mCurrentState].mTimeInStateSinceUpdate += + timestampMs - mLastStateChangeTimestampMs; + } else { + for (int i = 0; i < mStateCount; i++) { + mStates[i].mTimeInStateSinceUpdate = 0; + } + } + } + mCurrentState = state; + mLastStateChangeTimestampMs = timestampMs; + } + + public void updateValue(long[] values, long timestampMs) { + if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) { + if (timestampMs < mLastStateChangeTimestampMs) { + timestampMs = mLastStateChangeTimestampMs; + } + + setState(mCurrentState, timestampMs); + + if (mLastUpdateTimestampMs >= 0) { + if (timestampMs > mLastUpdateTimestampMs) { + if (delta(mValues, values, mDelta)) { + long timeSinceUpdate = timestampMs - mLastUpdateTimestampMs; + for (int i = 0; i < mStateCount; i++) { + long timeInState = mStates[i].mTimeInStateSinceUpdate; + if (timeInState > 0) { + add(mStates[i].mCounter, mDelta, timeInState, timeSinceUpdate); + mStates[i].mTimeInStateSinceUpdate = 0; + } + } + } else { + throw new RuntimeException(); + } + } else if (timestampMs < mLastUpdateTimestampMs) { + throw new RuntimeException(); + } + } + } + System.arraycopy(values, 0, mValues, 0, mArrayLength); + mLastUpdateTimestampMs = timestampMs; + } + + public void incrementValues(long[] delta, long timestampMs) { + long[] values = Arrays.copyOf(mValues, mValues.length); + for (int i = 0; i < mArrayLength; i++) { + values[i] += delta[i]; + } + updateValue(values, timestampMs); + } + + public void getValues(long[] values, int state) { + System.arraycopy(mStates[state].mCounter, 0, values, 0, mArrayLength); + } + + public void reset() { + mLastStateChangeTimestampMs = -1; + mLastUpdateTimestampMs = -1; + for (int i = 0; i < mStateCount; i++) { + mStates[i].mTimeInStateSinceUpdate = 0; + Arrays.fill(mStates[i].mCounter, 0); + } + } + + public void writeToParcel(Parcel parcel) { + parcel.writeInt(mStateCount); + parcel.writeInt(mArrayLength); + for (int i = 0; i < mStateCount; i++) { + parcel.writeLongArray(mStates[i].mCounter); + } + } + + public void initFromParcel(Parcel parcel) { + try { + for (int i = 0; i < mStateCount; i++) { + parcel.readLongArray(mStates[i].mCounter); + } + } catch (Exception e) { + throw new BadParcelableException(e); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("["); + for (int state = 0; state < mStateCount; state++) { + if (state != 0) { + sb.append(", "); + } + sb.append(state).append(": {"); + for (int i = 0; i < mStates[state].mCounter.length; i++) { + if (i != 0) { + sb.append(", "); + } + sb.append(mStates[state].mCounter[i]); + } + sb.append("}"); + } + sb.append("]"); + if (mLastUpdateTimestampMs >= 0) { + sb.append(" updated: ").append(mLastUpdateTimestampMs); + } + if (mLastStateChangeTimestampMs >= 0) { + sb.append(" currentState: ").append(mCurrentState); + if (mLastStateChangeTimestampMs > mLastUpdateTimestampMs) { + sb.append(" stateChanged: ").append(mLastStateChangeTimestampMs); + } + } else { + sb.append(" currentState: none"); + } + return sb.toString(); + } + + private boolean delta(long[] values1, long[] values2, long[] delta) { + if (delta.length != mArrayLength) { + throw new RuntimeException(); + } + + boolean is_delta_valid = true; + for (int i = 0; i < mArrayLength; i++) { + if (values2[i] >= values1[i]) { + delta[i] = values2[i] - values1[i]; + } else { + delta[i] = 0; + is_delta_valid = false; + } + } + + return is_delta_valid; + } + + private void add(long[] counter, long[] delta, long numerator, long denominator) { + if (numerator != denominator) { + for (int i = 0; i < mArrayLength; i++) { + counter[i] += delta[i] * numerator / denominator; + } + } else { + for (int i = 0; i < mArrayLength; i++) { + counter[i] += delta[i]; + } + } + } + } + + public static class LongArrayContainer_host { + private static final HashMap<Long, long[]> sInstances = new HashMap<>(); + private static long sNextId = 1; + + public static long native_init(int arrayLength) { + long[] array = new long[arrayLength]; + long instanceId = sNextId++; + sInstances.put(instanceId, array); + return instanceId; + } + + static long[] getInstance(long instanceId) { + return sInstances.get(instanceId); + } + + public static void native_setValues(long instanceId, long[] values) { + System.arraycopy(values, 0, getInstance(instanceId), 0, values.length); + } + + public static void native_getValues(long instanceId, long[] values) { + System.arraycopy(getInstance(instanceId), 0, values, 0, values.length); + } + + public static boolean native_combineValues(long instanceId, long[] array, int[] indexMap) { + long[] values = getInstance(instanceId); + + boolean nonZero = false; + Arrays.fill(array, 0); + + for (int i = 0; i < values.length; i++) { + int index = indexMap[i]; + if (index < 0 || index >= array.length) { + throw new IndexOutOfBoundsException("Index " + index + " is out of bounds: [0, " + + (array.length - 1) + "]"); + } + if (values[i] != 0) { + array[index] += values[i]; + nonZero = true; + } + } + return nonZero; + } + } + + private static final HashMap<Long, LongArrayMultiStateCounterRavenwood> sInstances = + new HashMap<>(); + private static long sNextId = 1; + + public static long native_init(int stateCount, int arrayLength) { + LongArrayMultiStateCounterRavenwood instance = new LongArrayMultiStateCounterRavenwood( + stateCount, arrayLength); + long instanceId = sNextId++; + sInstances.put(instanceId, instance); + return instanceId; + } + + private static LongArrayMultiStateCounterRavenwood getInstance(long instanceId) { + return sInstances.get(instanceId); + } + + public static void native_setEnabled(long instanceId, boolean enabled, + long timestampMs) { + getInstance(instanceId).setEnabled(enabled, timestampMs); + } + + public static int native_getStateCount(long instanceId) { + return getInstance(instanceId).mStateCount; + } + + public static int native_getArrayLength(long instanceId) { + return getInstance(instanceId).mArrayLength; + } + + public static void native_updateValues(long instanceId, long containerInstanceId, + long timestampMs) { + getInstance(instanceId).updateValue( + LongArrayContainer_host.getInstance(containerInstanceId), timestampMs); + } + + public static void native_setState(long instanceId, int state, long timestampMs) { + getInstance(instanceId).setState(state, timestampMs); + } + + public static void native_incrementValues(long instanceId, long containerInstanceId, + long timestampMs) { + getInstance(instanceId).incrementValues( + LongArrayContainer_host.getInstance(containerInstanceId), timestampMs); + } + + public static void native_getCounts(long instanceId, long containerInstanceId, int state) { + getInstance(instanceId).getValues(LongArrayContainer_host.getInstance(containerInstanceId), + state); + } + + public static void native_reset(long instanceId) { + getInstance(instanceId).reset(); + } + + public static void native_writeToParcel(long instanceId, Parcel parcel, int flags) { + getInstance(instanceId).writeToParcel(parcel); + } + + public static long native_initFromParcel(Parcel parcel) { + int stateCount = parcel.readInt(); + if (stateCount < 0 || stateCount > 0xEFFF) { + throw new BadParcelableException("stateCount out of range"); + } + // LongArrayMultiStateCounter.cpp uses AParcel, which throws on out-of-data. + if (parcel.dataPosition() >= parcel.dataSize()) { + throw new RuntimeException("Bad parcel"); + } + int arrayLength = parcel.readInt(); + if (parcel.dataPosition() >= parcel.dataSize()) { + throw new RuntimeException("Bad parcel"); + } + long instanceId = native_init(stateCount, arrayLength); + getInstance(instanceId).initFromParcel(parcel); + if (parcel.dataPosition() > parcel.dataSize()) { + throw new RuntimeException("Bad parcel"); + } + return instanceId; + } + + public static String native_toString(long instanceId) { + return getInstance(instanceId).toString(); + } +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt index 8354d985abfd..8ca4732f57c4 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt @@ -23,12 +23,16 @@ import com.android.hoststubgen.asm.ClassNodes class AndroidHeuristicsFilter( private val classes: ClassNodes, val aidlPolicy: FilterPolicyWithReason?, + val featureFlagsPolicy: FilterPolicyWithReason?, fallback: OutputFilter ) : DelegatingFilter(fallback) { override fun getPolicyForClass(className: String): FilterPolicyWithReason { if (aidlPolicy != null && classes.isAidlClass(className)) { return aidlPolicy } + if (featureFlagsPolicy != null && classes.isFeatureFlagsClass(className)) { + return featureFlagsPolicy + } return super.getPolicyForClass(className) } } @@ -40,4 +44,16 @@ private fun ClassNodes.isAidlClass(className: String): Boolean { return hasClass(className) && hasClass("$className\$Stub") && hasClass("$className\$Stub\$Proxy") -}
\ No newline at end of file +} + +/** + * @return if a given class "seems like" an feature flags class. + */ +private fun ClassNodes.isFeatureFlagsClass(className: String): Boolean { + // Matches template classes defined here: + // https://cs.android.com/android/platform/superproject/+/master:build/make/tools/aconfig/templates/ + return className.endsWith("/Flags") + || className.endsWith("/FeatureFlags") + || className.endsWith("/FeatureFlagsImpl") + || className.endsWith("/FakeFeatureFlagsImpl"); +} diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index b4354ba84e16..d38a6e34e09f 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt @@ -62,7 +62,8 @@ fun createFilterFromTextPolicyFile( var lineNo = 0 - var aidlPolicy: FilterPolicy? = null + var aidlPolicy: FilterPolicyWithReason? = null + var featureFlagsPolicy: FilterPolicyWithReason? = null try { BufferedReader(FileReader(filename)).use { reader -> @@ -130,7 +131,15 @@ fun createFilterFromTextPolicyFile( throw ParseException( "Policy for AIDL classes already defined") } - aidlPolicy = policy + aidlPolicy = policy.withReason("$FILTER_REASON (AIDL)") + } + SpecialClass.FeatureFlags -> { + if (featureFlagsPolicy != null) { + throw ParseException( + "Policy for feature flags already defined") + } + featureFlagsPolicy = + policy.withReason("$FILTER_REASON (feature flags)") } } } @@ -196,10 +205,10 @@ fun createFilterFromTextPolicyFile( } var ret: OutputFilter = imf - aidlPolicy?.let { policy -> + if (aidlPolicy != null || featureFlagsPolicy != null) { log.d("AndroidHeuristicsFilter enabled") ret = AndroidHeuristicsFilter( - classes, policy.withReason("$FILTER_REASON (AIDL)"), imf) + classes, aidlPolicy, featureFlagsPolicy, imf) } return ret } @@ -208,6 +217,7 @@ fun createFilterFromTextPolicyFile( private enum class SpecialClass { NotSpecial, Aidl, + FeatureFlags, } private fun resolveSpecialClass(className: String): SpecialClass { @@ -216,6 +226,7 @@ private fun resolveSpecialClass(className: String): SpecialClass { } when (className.lowercase()) { ":aidl" -> return SpecialClass.Aidl + ":feature_flags" -> return SpecialClass.FeatureFlags } throw ParseException("Invalid special class name \"$className\"") } |