diff options
829 files changed, 17357 insertions, 14485 deletions
diff --git a/AconfigFlags.bp b/AconfigFlags.bp index c6ce799f0a24..0080a8d63bfc 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -81,6 +81,7 @@ aconfig_declarations_group { "android.view.inputmethod.flags-aconfig-java", "android.webkit.flags-aconfig-java", "android.widget.flags-aconfig-java", + "android.xr.flags-aconfig-java", "art_exported_aconfig_flags_lib", "backstage_power_flags_lib", "backup_flags_lib", @@ -791,6 +792,12 @@ java_aconfig_library { ], } +cc_aconfig_library { + name: "android.permission.flags-aconfig-cc", + aconfig_declarations: "android.permission.flags-aconfig", + host_supported: true, +} + // SQLite aconfig_declarations { name: "android.database.sqlite-aconfig", @@ -893,6 +900,20 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// XR +aconfig_declarations { + name: "android.xr.flags-aconfig", + package: "android.xr", + container: "system", + srcs: ["core/java/android/content/pm/xr.aconfig"], +} + +java_aconfig_library { + name: "android.xr.flags-aconfig-java", + aconfig_declarations: "android.xr.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // android.app aconfig_declarations { name: "android.app.flags-aconfig", diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 11c5b51e23ae..98e53ab97872 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -82,3 +82,10 @@ flag { description: "Applies the normal quota policy to FGS jobs" bug: "341201311" } + +flag { + name: "adjust_quota_default_constants" + namespace: "backstage_power" + description: "Adjust quota default parameters" + bug: "347058927" +}
\ No newline at end of file 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 03a3a0d51891..885bad5e31c8 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 @@ -394,13 +394,13 @@ public final class QuotaController extends StateController { * minutes to run its jobs. */ private final long[] mBucketPeriodsMs = new long[]{ - QcConstants.DEFAULT_WINDOW_SIZE_ACTIVE_MS, - QcConstants.DEFAULT_WINDOW_SIZE_WORKING_MS, - QcConstants.DEFAULT_WINDOW_SIZE_FREQUENT_MS, + QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS, + QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS, + QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS, QcConstants.DEFAULT_WINDOW_SIZE_RARE_MS, 0, // NEVER QcConstants.DEFAULT_WINDOW_SIZE_RESTRICTED_MS, - QcConstants.DEFAULT_WINDOW_SIZE_EXEMPTED_MS + QcConstants.DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS }; /** The maximum period any bucket can have. */ @@ -454,7 +454,7 @@ public final class QuotaController extends StateController { */ private final long[] mEJLimitsMs = new long[]{ QcConstants.DEFAULT_EJ_LIMIT_ACTIVE_MS, - QcConstants.DEFAULT_EJ_LIMIT_WORKING_MS, + QcConstants.DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS, QcConstants.DEFAULT_EJ_LIMIT_FREQUENT_MS, QcConstants.DEFAULT_EJ_LIMIT_RARE_MS, 0, // NEVER @@ -476,7 +476,8 @@ public final class QuotaController extends StateController { /** * Length of time used to split an app's top time into chunks. */ - private long mEJTopAppTimeChunkSizeMs = QcConstants.DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; + private long mEJTopAppTimeChunkSizeMs = + QcConstants.DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; /** * How much EJ quota to give back to an app based on the number of top app time chunks it had. @@ -486,7 +487,7 @@ public final class QuotaController extends StateController { /** * How much EJ quota to give back to an app based on each non-top user interaction. */ - private long mEJRewardInteractionMs = QcConstants.DEFAULT_EJ_REWARD_INTERACTION_MS; + private long mEJRewardInteractionMs = QcConstants.DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS; /** * How much EJ quota to give back to an app based on each notification seen event. @@ -498,14 +499,6 @@ public final class QuotaController extends StateController { private long mEJGracePeriodTopAppMs = QcConstants.DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; - private long mQuotaBumpAdditionalDurationMs = - QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS; - private int mQuotaBumpAdditionalJobCount = QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT; - private int mQuotaBumpAdditionalSessionCount = - QcConstants.DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT; - private long mQuotaBumpWindowSizeMs = QcConstants.DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS; - private int mQuotaBumpLimit = QcConstants.DEFAULT_QUOTA_BUMP_LIMIT; - /** * List of system apps with the {@link android.Manifest.permission#INSTALL_PACKAGES} permission * granted for each user. @@ -578,6 +571,8 @@ public final class QuotaController extends StateController { } catch (RemoteException e) { // ignored; both services live in system_server } + + processQuotaConstantsAdjustment(); } @Override @@ -1095,7 +1090,7 @@ public final class QuotaController extends StateController { // essentially run until they reach the maximum limit. if (stats.windowSizeMs == mAllowedTimePerPeriodMs[standbyBucket]) { return calculateTimeUntilQuotaConsumedLocked( - events, startMaxElapsed, maxExecutionTimeRemainingMs, false); + events, startMaxElapsed, maxExecutionTimeRemainingMs); } // Need to check both max time and period time in case one is less than the other. @@ -1104,9 +1099,9 @@ public final class QuotaController extends StateController { // bucket value. return Math.min( calculateTimeUntilQuotaConsumedLocked( - events, startMaxElapsed, maxExecutionTimeRemainingMs, false), + events, startMaxElapsed, maxExecutionTimeRemainingMs), calculateTimeUntilQuotaConsumedLocked( - events, startWindowElapsed, allowedTimeRemainingMs, true)); + events, startWindowElapsed, allowedTimeRemainingMs)); } /** @@ -1116,36 +1111,12 @@ public final class QuotaController extends StateController { * @param deadSpaceMs How much time can be allowed to count towards the quota */ private long calculateTimeUntilQuotaConsumedLocked(@NonNull List<TimedEvent> sessions, - final long windowStartElapsed, long deadSpaceMs, boolean allowQuotaBumps) { + final long windowStartElapsed, long deadSpaceMs) { long timeUntilQuotaConsumedMs = 0; long start = windowStartElapsed; - int numQuotaBumps = 0; - final long quotaBumpWindowStartElapsed = - sElapsedRealtimeClock.millis() - mQuotaBumpWindowSizeMs; final int numSessions = sessions.size(); - if (allowQuotaBumps) { - for (int i = numSessions - 1; i >= 0; --i) { - TimedEvent event = sessions.get(i); - - if (event instanceof QuotaBump) { - if (event.getEndTimeElapsed() >= quotaBumpWindowStartElapsed - && numQuotaBumps++ < mQuotaBumpLimit) { - deadSpaceMs += mQuotaBumpAdditionalDurationMs; - } else { - break; - } - } - } - } for (int i = 0; i < numSessions; ++i) { - TimedEvent event = sessions.get(i); - - if (event instanceof QuotaBump) { - continue; - } - - TimingSession session = (TimingSession) event; - + TimingSession session = (TimingSession) sessions.get(i); if (session.endTimeElapsed < windowStartElapsed) { // Outside of window. Ignore. continue; @@ -1330,41 +1301,15 @@ public final class QuotaController extends StateController { final long startWindowElapsed = nowElapsed - stats.windowSizeMs; final long startMaxElapsed = nowElapsed - MAX_PERIOD_MS; int sessionCountInWindow = 0; - int numQuotaBumps = 0; - final long quotaBumpWindowStartElapsed = nowElapsed - mQuotaBumpWindowSizeMs; // The minimum time between the start time and the beginning of the events that were // looked at --> how much time the stats will be valid for. long emptyTimeMs = Long.MAX_VALUE; // Sessions are non-overlapping and in order of occurrence, so iterating backwards will get // the most recent ones. final int loopStart = events.size() - 1; - // Process QuotaBumps first to ensure the limits are properly adjusted. - for (int i = loopStart; i >= 0; --i) { - TimedEvent event = events.get(i); - - if (event.getEndTimeElapsed() < quotaBumpWindowStartElapsed - || numQuotaBumps >= mQuotaBumpLimit) { - break; - } - - if (event instanceof QuotaBump) { - stats.allowedTimePerPeriodMs += mQuotaBumpAdditionalDurationMs; - stats.jobCountLimit += mQuotaBumpAdditionalJobCount; - stats.sessionCountLimit += mQuotaBumpAdditionalSessionCount; - emptyTimeMs = Math.min(emptyTimeMs, - event.getEndTimeElapsed() - quotaBumpWindowStartElapsed); - numQuotaBumps++; - } - } TimingSession lastSeenTimingSession = null; for (int i = loopStart; i >= 0; --i) { - TimedEvent event = events.get(i); - - if (event instanceof QuotaBump) { - continue; - } - - TimingSession session = (TimingSession) event; + TimingSession session = (TimingSession) events.get(i); // Window management. if (startWindowElapsed < session.endTimeElapsed) { @@ -1469,6 +1414,13 @@ public final class QuotaController extends StateController { } } + void processQuotaConstantsAdjustment() { + if (Flags.adjustQuotaDefaultConstants()) { + mQcConstants.adjustDefaultBucketWindowSizes(); + mQcConstants.adjustDefaultEjLimits(); + } + } + @VisibleForTesting void incrementJobCountLocked(final int userId, @NonNull final String packageName, int count) { final long now = sElapsedRealtimeClock.millis(); @@ -2058,28 +2010,6 @@ public final class QuotaController extends StateController { } @VisibleForTesting - static final class QuotaBump implements TimedEvent { - // Event timestamp in elapsed realtime timebase. - public final long eventTimeElapsed; - - QuotaBump(long eventElapsed) { - this.eventTimeElapsed = eventElapsed; - } - - @Override - public long getEndTimeElapsed() { - return eventTimeElapsed; - } - - @Override - public void dump(IndentingPrintWriter pw) { - pw.print("Quota bump @ "); - pw.print(eventTimeElapsed); - pw.println(); - } - } - - @VisibleForTesting static final class ShrinkableDebits { /** The amount of quota remaining. Can be negative if limit changes. */ private long mDebitTally; @@ -2528,21 +2458,6 @@ public final class QuotaController extends StateController { updateStandbyBucket(userId, packageName, bucketIndex); }); } - - @Override - public void triggerTemporaryQuotaBump(String packageName, @UserIdInt int userId) { - synchronized (mLock) { - List<TimedEvent> events = mTimingEvents.get(userId, packageName); - if (events == null || events.size() == 0) { - // If the app hasn't run any jobs, there's no point giving it a quota bump. - return; - } - events.add(new QuotaBump(sElapsedRealtimeClock.millis())); - invalidateAllExecutionStatsLocked(userId, packageName); - } - // Update jobs out of band. - mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget(); - } } @VisibleForTesting @@ -3019,7 +2934,6 @@ public final class QuotaController extends StateController { mQcConstants.mRateLimitingConstantsUpdated = false; mQcConstants.mExecutionPeriodConstantsUpdated = false; mQcConstants.mEJLimitConstantsUpdated = false; - mQcConstants.mQuotaBumpConstantsUpdated = false; } @Override @@ -3046,7 +2960,6 @@ public final class QuotaController extends StateController { private boolean mRateLimitingConstantsUpdated = false; private boolean mExecutionPeriodConstantsUpdated = false; private boolean mEJLimitConstantsUpdated = false; - private boolean mQuotaBumpConstantsUpdated = false; /** Prefix to use with all constant keys in order to "sub-namespace" the keys. */ private static final String QC_CONSTANT_PREFIX = "qc_"; @@ -3194,21 +3107,6 @@ public final class QuotaController extends StateController { @VisibleForTesting static final String KEY_EJ_GRACE_PERIOD_TOP_APP_MS = QC_CONSTANT_PREFIX + "ej_grace_period_top_app_ms"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS = - QC_CONSTANT_PREFIX + "quota_bump_additional_duration_ms"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = - QC_CONSTANT_PREFIX + "quota_bump_additional_job_count"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = - QC_CONSTANT_PREFIX + "quota_bump_additional_session_count"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_WINDOW_SIZE_MS = - QC_CONSTANT_PREFIX + "quota_bump_window_size_ms"; - @VisibleForTesting - static final String KEY_QUOTA_BUMP_LIMIT = - QC_CONSTANT_PREFIX + "quota_bump_limit"; private static final long DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS = 10 * 60 * 1000L; // 10 minutes @@ -3224,14 +3122,28 @@ public final class QuotaController extends StateController { 10 * 60 * 1000L; // 10 minutes private static final long DEFAULT_IN_QUOTA_BUFFER_MS = 30 * 1000L; // 30 seconds - private static final long DEFAULT_WINDOW_SIZE_EXEMPTED_MS = + // Legacy default window size for EXEMPTED bucket + private static final long DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS; // EXEMPT apps can run jobs at any time - private static final long DEFAULT_WINDOW_SIZE_ACTIVE_MS = + // Legacy default window size for ACTIVE bucket + private static final long DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS = DEFAULT_ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; // ACTIVE apps can run jobs at any time - private static final long DEFAULT_WINDOW_SIZE_WORKING_MS = + // Legacy default window size for WORKING bucket + private static final long DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS = 2 * 60 * 60 * 1000L; // 2 hours - private static final long DEFAULT_WINDOW_SIZE_FREQUENT_MS = + // Legacy default window size for FREQUENT bucket + private static final long DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS = 8 * 60 * 60 * 1000L; // 8 hours + + private static final long DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS = + 20 * 60 * 1000L; // 20 minutes. + private static final long DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS = + 30 * 60 * 1000L; // 30 minutes. + private static final long DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS = + 4 * 60 * 60 * 1000L; // 4 hours + private static final long DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS = + 12 * 60 * 60 * 1000L; // 12 hours + private static final long DEFAULT_WINDOW_SIZE_RARE_MS = 24 * 60 * 60 * 1000L; // 24 hours private static final long DEFAULT_WINDOW_SIZE_RESTRICTED_MS = @@ -3245,9 +3157,9 @@ public final class QuotaController extends StateController { 75; // 75/window = 450/hr = 1/session private static final int DEFAULT_MAX_JOB_COUNT_ACTIVE = DEFAULT_MAX_JOB_COUNT_EXEMPTED; private static final int DEFAULT_MAX_JOB_COUNT_WORKING = // 120/window = 60/hr = 12/session - (int) (60.0 * DEFAULT_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS); + (int) (60.0 * DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS / HOUR_IN_MILLIS); private static final int DEFAULT_MAX_JOB_COUNT_FREQUENT = // 200/window = 25/hr = 25/session - (int) (25.0 * DEFAULT_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS); + (int) (25.0 * DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS / HOUR_IN_MILLIS); private static final int DEFAULT_MAX_JOB_COUNT_RARE = // 48/window = 2/hr = 16/session (int) (2.0 * DEFAULT_WINDOW_SIZE_RARE_MS / HOUR_IN_MILLIS); private static final int DEFAULT_MAX_JOB_COUNT_RESTRICTED = 10; @@ -3268,24 +3180,24 @@ public final class QuotaController extends StateController { // TODO(267949143): set a different limit for headless system apps private static final long DEFAULT_EJ_LIMIT_EXEMPTED_MS = 60 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_ACTIVE_MS = 30 * MINUTE_IN_MILLIS; - private static final long DEFAULT_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS; + private static final long DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_ACTIVE_MS; + private static final long DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS = 15 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_FREQUENT_MS = 10 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_RARE_MS = DEFAULT_EJ_LIMIT_FREQUENT_MS; private static final long DEFAULT_EJ_LIMIT_RESTRICTED_MS = 5 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_ADDITION_SPECIAL_MS = 15 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_LIMIT_ADDITION_INSTALLER_MS = 30 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_WINDOW_SIZE_MS = 24 * HOUR_IN_MILLIS; - private static final long DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = 30 * SECOND_IN_MILLIS; + private static final long DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = + 30 * SECOND_IN_MILLIS; + private static final long DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS = + 5 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_REWARD_TOP_APP_MS = 10 * SECOND_IN_MILLIS; - private static final long DEFAULT_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS; + private static final long DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS = 15 * SECOND_IN_MILLIS; + private static final long DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS = 5 * SECOND_IN_MILLIS; private static final long DEFAULT_EJ_REWARD_NOTIFICATION_SEEN_MS = 0; private static final long DEFAULT_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS = 3 * MINUTE_IN_MILLIS; private static final long DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS = 1 * MINUTE_IN_MILLIS; - private static final long DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS = 1 * MINUTE_IN_MILLIS; - private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT = 2; - private static final int DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = 1; - private static final long DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS = 8 * HOUR_IN_MILLIS; - private static final int DEFAULT_QUOTA_BUMP_LIMIT = 8; /** * How much time each app in the exempted bucket will have to run jobs within their standby @@ -3332,28 +3244,28 @@ public final class QuotaController extends StateController { * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS} within the past * WINDOW_SIZE_MS. */ - public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_WINDOW_SIZE_EXEMPTED_MS; + public long WINDOW_SIZE_EXEMPTED_MS = DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_ACTIVE_MS} within the past * WINDOW_SIZE_MS. */ - public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_WINDOW_SIZE_ACTIVE_MS; + public long WINDOW_SIZE_ACTIVE_MS = DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_WORKING_MS} within the past * WINDOW_SIZE_MS. */ - public long WINDOW_SIZE_WORKING_MS = DEFAULT_WINDOW_SIZE_WORKING_MS; + public long WINDOW_SIZE_WORKING_MS = DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are * expected to run only {@link #ALLOWED_TIME_PER_PERIOD_FREQUENT_MS} within the past * WINDOW_SIZE_MS. */ - public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_WINDOW_SIZE_FREQUENT_MS; + public long WINDOW_SIZE_FREQUENT_MS = DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS; /** * The quota window size of the particular standby bucket. Apps in this standby bucket are @@ -3514,7 +3426,7 @@ public final class QuotaController extends StateController { * standby bucket can only have expedited job sessions totalling EJ_LIMIT (without factoring * in any rewards or free EJs). */ - public long EJ_LIMIT_WORKING_MS = DEFAULT_EJ_LIMIT_WORKING_MS; + public long EJ_LIMIT_WORKING_MS = DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS; /** * The total expedited job session limit of the particular standby bucket. Apps in this @@ -3558,7 +3470,7 @@ public final class QuotaController extends StateController { /** * Length of time used to split an app's top time into chunks. */ - public long EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; + public long EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; /** * How much EJ quota to give back to an app based on the number of top app time chunks it @@ -3569,7 +3481,7 @@ public final class QuotaController extends StateController { /** * How much EJ quota to give back to an app based on each non-top user interaction. */ - public long EJ_REWARD_INTERACTION_MS = DEFAULT_EJ_REWARD_INTERACTION_MS; + public long EJ_REWARD_INTERACTION_MS = DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS; /** * How much EJ quota to give back to an app based on each notification seen event. @@ -3587,32 +3499,51 @@ public final class QuotaController extends StateController { */ public long EJ_GRACE_PERIOD_TOP_APP_MS = DEFAULT_EJ_GRACE_PERIOD_TOP_APP_MS; - /** - * How much additional session duration to give an app for each accepted quota bump. - */ - public long QUOTA_BUMP_ADDITIONAL_DURATION_MS = DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS; + void adjustDefaultBucketWindowSizes() { + WINDOW_SIZE_EXEMPTED_MS = DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS; + WINDOW_SIZE_ACTIVE_MS = DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS; + WINDOW_SIZE_WORKING_MS = DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS; + WINDOW_SIZE_FREQUENT_MS = DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS; - /** - * How many additional regular jobs to give an app for each accepted quota bump. - */ - public int QUOTA_BUMP_ADDITIONAL_JOB_COUNT = DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT; + mBucketPeriodsMs[EXEMPTED_INDEX] = Math.max( + mAllowedTimePerPeriodMs[EXEMPTED_INDEX], + Math.min(MAX_PERIOD_MS, WINDOW_SIZE_EXEMPTED_MS)); + mBucketPeriodsMs[ACTIVE_INDEX] = Math.max( + mAllowedTimePerPeriodMs[ACTIVE_INDEX], + Math.min(MAX_PERIOD_MS, WINDOW_SIZE_ACTIVE_MS)); + mBucketPeriodsMs[WORKING_INDEX] = Math.max( + mAllowedTimePerPeriodMs[WORKING_INDEX], + Math.min(MAX_PERIOD_MS, WINDOW_SIZE_WORKING_MS)); + mBucketPeriodsMs[FREQUENT_INDEX] = Math.max( + mAllowedTimePerPeriodMs[FREQUENT_INDEX], + Math.min(MAX_PERIOD_MS, WINDOW_SIZE_FREQUENT_MS)); + } - /** - * How many additional sessions to give an app for each accepted quota bump. - */ - public int QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = - DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT; + void adjustDefaultEjLimits() { + EJ_LIMIT_WORKING_MS = DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS; + EJ_TOP_APP_TIME_CHUNK_SIZE_MS = DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS; + EJ_REWARD_INTERACTION_MS = DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS; - /** - * The rolling window size within which to accept and apply quota bump events. - */ - public long QUOTA_BUMP_WINDOW_SIZE_MS = DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS; + // The limit must be in the range [15 minutes, active limit]. + mEJLimitsMs[WORKING_INDEX] = Math.max(15 * MINUTE_IN_MILLIS, + Math.min(mEJLimitsMs[ACTIVE_INDEX], EJ_LIMIT_WORKING_MS)); - /** - * The maximum number of quota bumps to accept and apply within the - * {@link #QUOTA_BUMP_WINDOW_SIZE_MS window}. - */ - public int QUOTA_BUMP_LIMIT = DEFAULT_QUOTA_BUMP_LIMIT; + // Limit interaction reward to be in the range [5 seconds, 15 minutes] per event. + mEJRewardInteractionMs = Math.min(15 * MINUTE_IN_MILLIS, + Math.max(5 * SECOND_IN_MILLIS, EJ_REWARD_INTERACTION_MS)); + + // Limit chunking to be in the range [1 millisecond, 15 minutes] per event. + long newChunkSizeMs = Math.min(15 * MINUTE_IN_MILLIS, + Math.max(1, EJ_TOP_APP_TIME_CHUNK_SIZE_MS)); + mEJTopAppTimeChunkSizeMs = newChunkSizeMs; + if (mEJTopAppTimeChunkSizeMs < mEJRewardTopAppMs) { + // Not making chunk sizes and top rewards to be the upper/lower + // limits of the other to allow trying different policies. Just log + // the discrepancy. + Slog.w(TAG, "EJ top app time chunk less than reward: " + + mEJTopAppTimeChunkSizeMs + " vs " + mEJRewardTopAppMs); + } + } public void processConstantLocked(@NonNull DeviceConfig.Properties properties, @NonNull String key) { @@ -3650,14 +3581,6 @@ public final class QuotaController extends StateController { updateEJLimitConstantsLocked(); break; - case KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS: - case KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT: - case KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT: - case KEY_QUOTA_BUMP_WINDOW_SIZE_MS: - case KEY_QUOTA_BUMP_LIMIT: - updateQuotaBumpConstantsLocked(); - break; - case KEY_MAX_JOB_COUNT_EXEMPTED: MAX_JOB_COUNT_EXEMPTED = properties.getInt(key, DEFAULT_MAX_JOB_COUNT_EXEMPTED); int newExemptedMaxJobCount = @@ -3790,7 +3713,9 @@ public final class QuotaController extends StateController { case KEY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS: // We don't need to re-evaluate execution stats or constraint status for this. EJ_TOP_APP_TIME_CHUNK_SIZE_MS = - properties.getLong(key, DEFAULT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS); + properties.getLong(key, Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_EJ_TOP_APP_TIME_CHUNK_SIZE_MS : + DEFAULT_LEGACY_EJ_TOP_APP_TIME_CHUNK_SIZE_MS); // Limit chunking to be in the range [1 millisecond, 15 minutes] per event. long newChunkSizeMs = Math.min(15 * MINUTE_IN_MILLIS, Math.max(1, EJ_TOP_APP_TIME_CHUNK_SIZE_MS)); @@ -3826,7 +3751,9 @@ public final class QuotaController extends StateController { case KEY_EJ_REWARD_INTERACTION_MS: // We don't need to re-evaluate execution stats or constraint status for this. EJ_REWARD_INTERACTION_MS = - properties.getLong(key, DEFAULT_EJ_REWARD_INTERACTION_MS); + properties.getLong(key, Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_EJ_REWARD_INTERACTION_MS : + DEFAULT_LEGACY_EJ_REWARD_INTERACTION_MS); // Limit interaction reward to be in the range [5 seconds, 15 minutes] per // event. mEJRewardInteractionMs = Math.min(15 * MINUTE_IN_MILLIS, @@ -3900,14 +3827,23 @@ public final class QuotaController extends StateController { MAX_EXECUTION_TIME_MS = properties.getLong(KEY_MAX_EXECUTION_TIME_MS, DEFAULT_MAX_EXECUTION_TIME_MS); WINDOW_SIZE_EXEMPTED_MS = properties.getLong(KEY_WINDOW_SIZE_EXEMPTED_MS, - DEFAULT_WINDOW_SIZE_EXEMPTED_MS); + Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_WINDOW_SIZE_EXEMPTED_MS : + DEFAULT_LEGACY_WINDOW_SIZE_EXEMPTED_MS); WINDOW_SIZE_ACTIVE_MS = properties.getLong(KEY_WINDOW_SIZE_ACTIVE_MS, - DEFAULT_WINDOW_SIZE_ACTIVE_MS); + Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_WINDOW_SIZE_ACTIVE_MS : + DEFAULT_LEGACY_WINDOW_SIZE_ACTIVE_MS); WINDOW_SIZE_WORKING_MS = - properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, DEFAULT_WINDOW_SIZE_WORKING_MS); + properties.getLong(KEY_WINDOW_SIZE_WORKING_MS, + Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_WINDOW_SIZE_WORKING_MS : + DEFAULT_LEGACY_WINDOW_SIZE_WORKING_MS); WINDOW_SIZE_FREQUENT_MS = properties.getLong(KEY_WINDOW_SIZE_FREQUENT_MS, - DEFAULT_WINDOW_SIZE_FREQUENT_MS); + Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_WINDOW_SIZE_FREQUENT_MS : + DEFAULT_LEGACY_WINDOW_SIZE_FREQUENT_MS); WINDOW_SIZE_RARE_MS = properties.getLong(KEY_WINDOW_SIZE_RARE_MS, DEFAULT_WINDOW_SIZE_RARE_MS); WINDOW_SIZE_RESTRICTED_MS = @@ -4078,7 +4014,9 @@ public final class QuotaController extends StateController { EJ_LIMIT_ACTIVE_MS = properties.getLong( KEY_EJ_LIMIT_ACTIVE_MS, DEFAULT_EJ_LIMIT_ACTIVE_MS); EJ_LIMIT_WORKING_MS = properties.getLong( - KEY_EJ_LIMIT_WORKING_MS, DEFAULT_EJ_LIMIT_WORKING_MS); + KEY_EJ_LIMIT_WORKING_MS, Flags.adjustQuotaDefaultConstants() + ? DEFAULT_CURRENT_EJ_LIMIT_WORKING_MS : + DEFAULT_LEGACY_EJ_LIMIT_WORKING_MS); EJ_LIMIT_FREQUENT_MS = properties.getLong( KEY_EJ_LIMIT_FREQUENT_MS, DEFAULT_EJ_LIMIT_FREQUENT_MS); EJ_LIMIT_RARE_MS = properties.getLong( @@ -4156,65 +4094,6 @@ public final class QuotaController extends StateController { } } - private void updateQuotaBumpConstantsLocked() { - if (mQuotaBumpConstantsUpdated) { - return; - } - mQuotaBumpConstantsUpdated = true; - - // Query the values as an atomic set. - final DeviceConfig.Properties properties = DeviceConfig.getProperties( - DeviceConfig.NAMESPACE_JOB_SCHEDULER, - KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, - KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, - KEY_QUOTA_BUMP_WINDOW_SIZE_MS, KEY_QUOTA_BUMP_LIMIT); - QUOTA_BUMP_ADDITIONAL_DURATION_MS = properties.getLong( - KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, - DEFAULT_QUOTA_BUMP_ADDITIONAL_DURATION_MS); - QUOTA_BUMP_ADDITIONAL_JOB_COUNT = properties.getInt( - KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, DEFAULT_QUOTA_BUMP_ADDITIONAL_JOB_COUNT); - QUOTA_BUMP_ADDITIONAL_SESSION_COUNT = properties.getInt( - KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, - DEFAULT_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT); - QUOTA_BUMP_WINDOW_SIZE_MS = properties.getLong( - KEY_QUOTA_BUMP_WINDOW_SIZE_MS, DEFAULT_QUOTA_BUMP_WINDOW_SIZE_MS); - QUOTA_BUMP_LIMIT = properties.getInt( - KEY_QUOTA_BUMP_LIMIT, DEFAULT_QUOTA_BUMP_LIMIT); - - // The window must be in the range [1 hour, 24 hours]. - long newWindowSizeMs = Math.max(HOUR_IN_MILLIS, - Math.min(MAX_PERIOD_MS, QUOTA_BUMP_WINDOW_SIZE_MS)); - if (mQuotaBumpWindowSizeMs != newWindowSizeMs) { - mQuotaBumpWindowSizeMs = newWindowSizeMs; - mShouldReevaluateConstraints = true; - } - // The limit must be nonnegative. - int newLimit = Math.max(0, QUOTA_BUMP_LIMIT); - if (mQuotaBumpLimit != newLimit) { - mQuotaBumpLimit = newLimit; - mShouldReevaluateConstraints = true; - } - // The job count must be nonnegative. - int newJobAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_JOB_COUNT); - if (mQuotaBumpAdditionalJobCount != newJobAddition) { - mQuotaBumpAdditionalJobCount = newJobAddition; - mShouldReevaluateConstraints = true; - } - // The session count must be nonnegative. - int newSessionAddition = Math.max(0, QUOTA_BUMP_ADDITIONAL_SESSION_COUNT); - if (mQuotaBumpAdditionalSessionCount != newSessionAddition) { - mQuotaBumpAdditionalSessionCount = newSessionAddition; - mShouldReevaluateConstraints = true; - } - // The additional duration must be in the range [0, 10 minutes]. - long newAdditionalDuration = Math.max(0, - Math.min(10 * MINUTE_IN_MILLIS, QUOTA_BUMP_ADDITIONAL_DURATION_MS)); - if (mQuotaBumpAdditionalDurationMs != newAdditionalDuration) { - mQuotaBumpAdditionalDurationMs = newAdditionalDuration; - mShouldReevaluateConstraints = true; - } - } - private void dump(IndentingPrintWriter pw) { pw.println(); pw.println("QuotaController:"); @@ -4277,15 +4156,6 @@ public final class QuotaController extends StateController { EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS).println(); pw.print(KEY_EJ_GRACE_PERIOD_TOP_APP_MS, EJ_GRACE_PERIOD_TOP_APP_MS).println(); - pw.print(KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, - QUOTA_BUMP_ADDITIONAL_DURATION_MS).println(); - pw.print(KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, - QUOTA_BUMP_ADDITIONAL_JOB_COUNT).println(); - pw.print(KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, - QUOTA_BUMP_ADDITIONAL_SESSION_COUNT).println(); - pw.print(KEY_QUOTA_BUMP_WINDOW_SIZE_MS, QUOTA_BUMP_WINDOW_SIZE_MS).println(); - pw.print(KEY_QUOTA_BUMP_LIMIT, QUOTA_BUMP_LIMIT).println(); - pw.decreaseIndent(); } @@ -4503,37 +4373,19 @@ public final class QuotaController extends StateController { return mQcConstants; } - @VisibleForTesting - long getQuotaBumpAdditionDurationMs() { - return mQuotaBumpAdditionalDurationMs; - } - - @VisibleForTesting - int getQuotaBumpAdditionJobCount() { - return mQuotaBumpAdditionalJobCount; - } - - @VisibleForTesting - int getQuotaBumpAdditionSessionCount() { - return mQuotaBumpAdditionalSessionCount; - } - - @VisibleForTesting - int getQuotaBumpLimit() { - return mQuotaBumpLimit; - } - - @VisibleForTesting - long getQuotaBumpWindowSizeMs() { - return mQuotaBumpWindowSizeMs; - } - //////////////////////////// DATA DUMP ////////////////////////////// @NeverCompile // Avoid size overhead of debugging code. @Override public void dumpControllerStateLocked(final IndentingPrintWriter pw, final Predicate<JobStatus> predicate) { + pw.println("Flags: "); + pw.println(" " + Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS + + ": " + Flags.adjustQuotaDefaultConstants()); + pw.println(" " + Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS + + ": " + Flags.enforceQuotaPolicyToFgsJobs()); + pw.println(); + pw.println("Current elapsed time: " + sElapsedRealtimeClock.millis()); pw.println(); diff --git a/core/api/current.txt b/core/api/current.txt index 035f82388e6e..14a91c9033c5 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -8781,7 +8781,6 @@ package android.app.admin { package android.app.appfunctions { @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager { - method @Deprecated @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); method @RequiresPermission(anyOf={"android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED", "android.permission.EXECUTE_APP_FUNCTIONS"}, conditional=true) public void executeAppFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); @@ -8793,9 +8792,7 @@ package android.app.appfunctions { @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); - method @Deprecated @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); - method @MainThread public void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); + method @MainThread public abstract void onExecuteFunction(@NonNull android.app.appfunctions.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<android.app.appfunctions.ExecuteAppFunctionResponse>); field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } @@ -32880,6 +32877,41 @@ package android.os { } @FlaggedApi("android.sdk.major_minor_versioning_scheme") public static class Build.VERSION_CODES_FULL { + field public static final int BASE = 100000; // 0x186a0 + field public static final int BASE_1_1 = 200000; // 0x30d40 + field public static final int CUPCAKE = 300000; // 0x493e0 + field public static final int DONUT = 400000; // 0x61a80 + field public static final int ECLAIR = 500000; // 0x7a120 + field public static final int ECLAIR_0_1 = 600000; // 0x927c0 + field public static final int ECLAIR_MR1 = 700000; // 0xaae60 + field public static final int FROYO = 800000; // 0xc3500 + field public static final int GINGERBREAD = 900000; // 0xdbba0 + field public static final int GINGERBREAD_MR1 = 1000000; // 0xf4240 + field public static final int HONEYCOMB = 1100000; // 0x10c8e0 + field public static final int HONEYCOMB_MR1 = 1200000; // 0x124f80 + field public static final int HONEYCOMB_MR2 = 1300000; // 0x13d620 + field public static final int ICE_CREAM_SANDWICH = 1400000; // 0x155cc0 + field public static final int ICE_CREAM_SANDWICH_MR1 = 1500000; // 0x16e360 + field public static final int JELLY_BEAN = 1600000; // 0x186a00 + field public static final int JELLY_BEAN_MR1 = 1700000; // 0x19f0a0 + field public static final int JELLY_BEAN_MR2 = 1800000; // 0x1b7740 + field public static final int KITKAT = 1900000; // 0x1cfde0 + field public static final int KITKAT_WATCH = 2000000; // 0x1e8480 + field public static final int LOLLIPOP = 2100000; // 0x200b20 + field public static final int LOLLIPOP_MR1 = 2200000; // 0x2191c0 + field public static final int M = 2300000; // 0x231860 + field public static final int N = 2400000; // 0x249f00 + field public static final int N_MR1 = 2500000; // 0x2625a0 + field public static final int O = 2600000; // 0x27ac40 + field public static final int O_MR1 = 2700000; // 0x2932e0 + field public static final int P = 2800000; // 0x2ab980 + field public static final int Q = 2900000; // 0x2c4020 + field public static final int R = 3000000; // 0x2dc6c0 + field public static final int S = 3100000; // 0x2f4d60 + field public static final int S_V2 = 3200000; // 0x30d400 + field public static final int TIRAMISU = 3300000; // 0x325aa0 + field public static final int UPSIDE_DOWN_CAKE = 3400000; // 0x33e140 + field public static final int VANILLA_ICE_CREAM = 3500000; // 0x3567e0 } public final class Bundle extends android.os.BaseBundle implements java.lang.Cloneable android.os.Parcelable { @@ -33933,12 +33965,9 @@ package android.os { public class RemoteCallbackList<E extends android.os.IInterface> { ctor public RemoteCallbackList(); method public int beginBroadcast(); - method @FlaggedApi("android.os.binder_frozen_state_change_callback") public void broadcast(@NonNull java.util.function.Consumer<E>); method public void finishBroadcast(); method public Object getBroadcastCookie(int); method public E getBroadcastItem(int); - method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getFrozenCalleePolicy(); - method @FlaggedApi("android.os.binder_frozen_state_change_callback") public int getMaxQueueSize(); method public Object getRegisteredCallbackCookie(int); method public int getRegisteredCallbackCount(); method public E getRegisteredCallbackItem(int); @@ -33948,16 +33977,6 @@ package android.os { method public boolean register(E); method public boolean register(E, Object); method public boolean unregister(E); - field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_DROP = 3; // 0x3 - field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_ENQUEUE_ALL = 1; // 0x1 - field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT = 2; // 0x2 - field @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final int FROZEN_CALLEE_POLICY_UNSET = 0; // 0x0 - } - - @FlaggedApi("android.os.binder_frozen_state_change_callback") public static final class RemoteCallbackList.Builder<E extends android.os.IInterface> { - ctor public RemoteCallbackList.Builder(int); - method @NonNull public android.os.RemoteCallbackList<E> build(); - method @NonNull public android.os.RemoteCallbackList.Builder setMaxQueueSize(int); } public class RemoteException extends android.util.AndroidException { @@ -46367,7 +46386,7 @@ package android.telephony { field public static final String ACTION_MULTI_SIM_CONFIG_CHANGED = "android.telephony.action.MULTI_SIM_CONFIG_CHANGED"; field public static final String ACTION_NETWORK_COUNTRY_CHANGED = "android.telephony.action.NETWORK_COUNTRY_CHANGED"; field @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE) public static final String ACTION_PHONE_STATE_CHANGED = "android.intent.action.PHONE_STATE"; - field @FlaggedApi("com.android.internal.telephony.flags.reset_mobile_network_settings") public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS"; + field public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS"; field public static final String ACTION_RESPOND_VIA_MESSAGE = "android.intent.action.RESPOND_VIA_MESSAGE"; field public static final String ACTION_SECRET_CODE = "android.telephony.action.SECRET_CODE"; field public static final String ACTION_SHOW_VOICEMAIL_NOTIFICATION = "android.telephony.action.SHOW_VOICEMAIL_NOTIFICATION"; @@ -56601,7 +56620,7 @@ package android.view.inputmethod { method public java.util.Map<android.view.inputmethod.InputMethodInfo,java.util.List<android.view.inputmethod.InputMethodSubtype>> getShortcutInputMethodsAndSubtypes(); method @Deprecated public void hideSoftInputFromInputMethod(android.os.IBinder, int); method public boolean hideSoftInputFromWindow(android.os.IBinder, int); - method public boolean hideSoftInputFromWindow(android.os.IBinder, int, android.os.ResultReceiver); + method @Deprecated public boolean hideSoftInputFromWindow(android.os.IBinder, int, android.os.ResultReceiver); method @Deprecated public void hideStatusIcon(android.os.IBinder); method public void invalidateInput(@NonNull android.view.View); method public boolean isAcceptingText(); @@ -56625,7 +56644,7 @@ package android.view.inputmethod { method public void showInputMethodAndSubtypeEnabler(@Nullable String); method public void showInputMethodPicker(); method public boolean showSoftInput(android.view.View, int); - method public boolean showSoftInput(android.view.View, int, android.os.ResultReceiver); + method @Deprecated public boolean showSoftInput(android.view.View, int, android.os.ResultReceiver); method @Deprecated public void showSoftInputFromInputMethod(android.os.IBinder, int); method @Deprecated public void showStatusIcon(android.os.IBinder, String, @DrawableRes int); method @FlaggedApi("android.view.inputmethod.connectionless_handwriting") public void startConnectionlessStylusHandwriting(@NonNull android.view.View, @Nullable android.view.inputmethod.CursorAnchorInfo, @NonNull java.util.concurrent.Executor, @NonNull android.view.inputmethod.ConnectionlessHandwritingCallback); diff --git a/core/api/removed.txt b/core/api/removed.txt index 3c7c0d6e6ea1..a3cab291a1de 100644 --- a/core/api/removed.txt +++ b/core/api/removed.txt @@ -82,12 +82,12 @@ package android.database { package android.graphics { @Deprecated public class AvoidXfermode extends android.graphics.Xfermode { - ctor public AvoidXfermode(int, int, android.graphics.AvoidXfermode.Mode); + ctor @Deprecated public AvoidXfermode(int, int, android.graphics.AvoidXfermode.Mode); } - public enum AvoidXfermode.Mode { - enum_constant public static final android.graphics.AvoidXfermode.Mode AVOID; - enum_constant public static final android.graphics.AvoidXfermode.Mode TARGET; + @Deprecated public enum AvoidXfermode.Mode { + enum_constant @Deprecated public static final android.graphics.AvoidXfermode.Mode AVOID; + enum_constant @Deprecated public static final android.graphics.AvoidXfermode.Mode TARGET; } public class Canvas { @@ -102,9 +102,9 @@ package android.graphics { } @Deprecated public class LayerRasterizer extends android.graphics.Rasterizer { - ctor public LayerRasterizer(); - method public void addLayer(android.graphics.Paint, float, float); - method public void addLayer(android.graphics.Paint); + ctor @Deprecated public LayerRasterizer(); + method @Deprecated public void addLayer(android.graphics.Paint, float, float); + method @Deprecated public void addLayer(android.graphics.Paint); } public class Paint { @@ -118,7 +118,7 @@ package android.graphics { } @Deprecated public class PixelXorXfermode extends android.graphics.Xfermode { - ctor public PixelXorXfermode(int); + ctor @Deprecated public PixelXorXfermode(int); } public class Rasterizer { @@ -170,14 +170,14 @@ package android.media.tv { package android.net { @Deprecated public class NetworkBadging { - method @NonNull public static android.graphics.drawable.Drawable getWifiIcon(@IntRange(from=0, to=4) int, int, @Nullable android.content.res.Resources.Theme); - field public static final int BADGING_4K = 30; // 0x1e - field public static final int BADGING_HD = 20; // 0x14 - field public static final int BADGING_NONE = 0; // 0x0 - field public static final int BADGING_SD = 10; // 0xa + method @Deprecated @NonNull public static android.graphics.drawable.Drawable getWifiIcon(@IntRange(from=0, to=4) int, int, @Nullable android.content.res.Resources.Theme); + field @Deprecated public static final int BADGING_4K = 30; // 0x1e + field @Deprecated public static final int BADGING_HD = 20; // 0x14 + field @Deprecated public static final int BADGING_NONE = 0; // 0x0 + field @Deprecated public static final int BADGING_SD = 10; // 0xa } - @IntDef({0x0, 0xa, 0x14, 0x1e}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NetworkBadging.Badging { + @Deprecated @IntDef({0x0, 0xa, 0x14, 0x1e}) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface NetworkBadging.Badging { } public final class Proxy { @@ -304,14 +304,14 @@ package android.provider { @Deprecated public static final class ContactsContract.RawContacts.StreamItems implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemsColumns { field @Deprecated public static final String CONTENT_DIRECTORY = "stream_items"; - field public static final String _COUNT = "_count"; - field public static final String _ID = "_id"; + field @Deprecated public static final String _COUNT = "_count"; + field @Deprecated public static final String _ID = "_id"; } @Deprecated public static final class ContactsContract.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns { field @Deprecated public static final String PHOTO = "photo"; - field public static final String _COUNT = "_count"; - field public static final String _ID = "_id"; + field @Deprecated public static final String _COUNT = "_count"; + field @Deprecated public static final String _ID = "_id"; } @Deprecated protected static interface ContactsContract.StreamItemPhotosColumns { @@ -332,16 +332,16 @@ package android.provider { field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item"; field @Deprecated public static final android.net.Uri CONTENT_URI; field @Deprecated public static final String MAX_ITEMS = "max_items"; - field public static final String _COUNT = "_count"; - field public static final String _ID = "_id"; + field @Deprecated public static final String _COUNT = "_count"; + field @Deprecated public static final String _ID = "_id"; } @Deprecated public static final class ContactsContract.StreamItems.StreamItemPhotos implements android.provider.BaseColumns android.provider.ContactsContract.StreamItemPhotosColumns { field @Deprecated public static final String CONTENT_DIRECTORY = "photo"; field @Deprecated public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/stream_item_photo"; field @Deprecated public static final String CONTENT_TYPE = "vnd.android.cursor.dir/stream_item_photo"; - field public static final String _COUNT = "_count"; - field public static final String _ID = "_id"; + field @Deprecated public static final String _COUNT = "_count"; + field @Deprecated public static final String _ID = "_id"; } @Deprecated protected static interface ContactsContract.StreamItemsColumns { @@ -447,14 +447,14 @@ package android.text.style { package android.util { @Deprecated public class FloatMath { - method public static float ceil(float); - method public static float cos(float); - method public static float exp(float); - method public static float floor(float); - method public static float hypot(float, float); - method public static float pow(float, float); - method public static float sin(float); - method public static float sqrt(float); + method @Deprecated public static float ceil(float); + method @Deprecated public static float cos(float); + method @Deprecated public static float exp(float); + method @Deprecated public static float floor(float); + method @Deprecated public static float hypot(float, float); + method @Deprecated public static float pow(float, float); + method @Deprecated public static float sin(float); + method @Deprecated public static float sqrt(float); } } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index fa4fc43c3418..349d06ca8ad6 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -4170,9 +4170,11 @@ package android.content.pm { } public class PackageInstaller { + method @FlaggedApi("android.content.pm.verification_service") @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public final int getVerificationPolicy(); method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @FlaggedApi("android.content.pm.read_install_info") @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException; method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean); + method @FlaggedApi("android.content.pm.verification_service") @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public final boolean setVerificationPolicy(int); field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL"; field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL"; field public static final int DATA_LOADER_TYPE_INCREMENTAL = 2; // 0x2 @@ -4183,12 +4185,20 @@ package android.content.pm { field @FlaggedApi("android.content.pm.archiving") public static final String EXTRA_DELETE_FLAGS = "android.content.pm.extra.DELETE_FLAGS"; field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS"; field @Deprecated public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH"; + field @FlaggedApi("android.content.pm.verification_service") public static final String EXTRA_VERIFICATION_FAILURE_REASON = "android.content.pm.extra.VERIFICATION_FAILURE_REASON"; field public static final int LOCATION_DATA_APP = 0; // 0x0 field public static final int LOCATION_MEDIA_DATA = 2; // 0x2 field public static final int LOCATION_MEDIA_OBB = 1; // 0x1 field public static final int REASON_CONFIRM_PACKAGE_CHANGE = 0; // 0x0 field public static final int REASON_OWNERSHIP_CHANGED = 1; // 0x1 field public static final int REASON_REMIND_OWNERSHIP = 2; // 0x2 + field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE = 1; // 0x1 + field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED = 2; // 0x2 + field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_FAILED_REASON_UNKNOWN = 0; // 0x0 + field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_POLICY_BLOCK_FAIL_CLOSED = 3; // 0x3 + field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_POLICY_BLOCK_FAIL_OPEN = 1; // 0x1 + field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_POLICY_BLOCK_FAIL_WARN = 2; // 0x2 + field @FlaggedApi("android.content.pm.verification_service") public static final int VERIFICATION_POLICY_NONE = 0; // 0x0 } public static class PackageInstaller.InstallInfo { @@ -4635,12 +4645,13 @@ package android.content.pm.verify.pkg { method @NonNull public android.content.pm.SigningInfo getSigningInfo(); method @NonNull public android.net.Uri getStagedPackageUri(); method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public long getTimeoutTime(); + method public int getVerificationPolicy(); method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus); method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationComplete(@NonNull android.content.pm.verify.pkg.VerificationStatus, @NonNull android.os.PersistableBundle); method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public void reportVerificationIncomplete(int); + method @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) public boolean setVerificationPolicy(int); method public void writeToParcel(@NonNull android.os.Parcel, int); field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.pkg.VerificationSession> CREATOR; - field public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2; // 0x2 field public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; // 0x1 field public static final int VERIFICATION_INCOMPLETE_UNKNOWN = 0; // 0x0 } diff --git a/core/api/system-removed.txt b/core/api/system-removed.txt index bbfa0ec3f3c2..78b9994e8fa1 100644 --- a/core/api/system-removed.txt +++ b/core/api/system-removed.txt @@ -7,8 +7,8 @@ package android.app { } @Deprecated public abstract static class AppOpsManager.AppOpsCollector extends android.app.AppOpsManager.OnOpNotedCallback { - ctor public AppOpsManager.AppOpsCollector(); - method @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor(); + ctor @Deprecated public AppOpsManager.AppOpsCollector(); + method @Deprecated @NonNull public java.util.concurrent.Executor getAsyncNotedExecutor(); } public class Notification implements android.os.Parcelable { @@ -207,7 +207,7 @@ package android.service.translation { @Deprecated public static interface TranslationService.OnTranslationResultCallback { method @Deprecated public void onError(); - method public void onTranslationSuccess(@NonNull android.view.translation.TranslationResponse); + method @Deprecated public void onTranslationSuccess(@NonNull android.view.translation.TranslationResponse); } } @@ -261,64 +261,64 @@ package android.telephony.ims { } @Deprecated public final class SipDelegateImsConfiguration implements android.os.Parcelable { - method public boolean containsKey(@NonNull String); - method @NonNull public android.os.PersistableBundle copyBundle(); - method public int describeContents(); - method public boolean getBoolean(@NonNull String, boolean); - method public int getInt(@NonNull String, int); - method @Nullable public String getString(@NonNull String); - method public long getVersion(); - method public void writeToParcel(@NonNull android.os.Parcel, int); - field @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SipDelegateImsConfiguration> CREATOR; - field public static final String IPTYPE_IPV4 = "IPV4"; - field public static final String IPTYPE_IPV6 = "IPV6"; - field public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = "sip_config_auhentication_header_string"; - field public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = "sip_config_authentication_nonce_string"; - field public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING = "sip_config_cellular_network_info_header_string"; - field public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string"; - field public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string"; - field public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string"; - field public static final String KEY_SIP_CONFIG_IS_COMPACT_FORM_ENABLED_BOOL = "sip_config_is_compact_form_enabled_bool"; - field public static final String KEY_SIP_CONFIG_IS_GRUU_ENABLED_BOOL = "sip_config_is_gruu_enabled_bool"; - field public static final String KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL = "sip_config_is_ipsec_enabled_bool"; - field public static final String KEY_SIP_CONFIG_IS_KEEPALIVE_ENABLED_BOOL = "sip_config_is_keepalive_enabled_bool"; - field public static final String KEY_SIP_CONFIG_IS_NAT_ENABLED_BOOL = "sip_config_is_nat_enabled_bool"; - field public static final String KEY_SIP_CONFIG_MAX_PAYLOAD_SIZE_ON_UDP_INT = "sip_config_udp_max_payload_size_int"; - field public static final String KEY_SIP_CONFIG_PATH_HEADER_STRING = "sip_config_path_header_string"; - field public static final String KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING = "sip_config_p_access_network_info_header_string"; - field public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING = "sip_config_p_associated_uri_header_string"; - field public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING = "sip_config_p_last_access_network_info_header_string"; - field public static final String KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING = "sip_config_security_verify_header_string"; - field public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_IPADDRESS_STRING = "sip_config_server_default_ipaddress_string"; - field public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_PORT_INT = "sip_config_server_default_port_int"; - field public static final String KEY_SIP_CONFIG_SERVER_IPSEC_CLIENT_PORT_INT = "sip_config_server_ipsec_client_port_int"; - field public static final String KEY_SIP_CONFIG_SERVER_IPSEC_OLD_CLIENT_PORT_INT = "sip_config_server_ipsec_old_client_port_int"; - field public static final String KEY_SIP_CONFIG_SERVER_IPSEC_SERVER_PORT_INT = "sip_config_server_ipsec_server_port_int"; - field public static final String KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING = "sip_config_service_route_header_string"; - field public static final String KEY_SIP_CONFIG_TRANSPORT_TYPE_STRING = "sip_config_protocol_type_string"; - field public static final String KEY_SIP_CONFIG_UE_DEFAULT_IPADDRESS_STRING = "sip_config_ue_default_ipaddress_string"; - field public static final String KEY_SIP_CONFIG_UE_DEFAULT_PORT_INT = "sip_config_ue_default_port_int"; - field public static final String KEY_SIP_CONFIG_UE_IPSEC_CLIENT_PORT_INT = "sip_config_ue_ipsec_client_port_int"; - field public static final String KEY_SIP_CONFIG_UE_IPSEC_OLD_CLIENT_PORT_INT = "sip_config_ue_ipsec_old_client_port_int"; - field public static final String KEY_SIP_CONFIG_UE_IPSEC_SERVER_PORT_INT = "sip_config_ue_ipsec_server_port_int"; - field public static final String KEY_SIP_CONFIG_UE_PRIVATE_USER_ID_STRING = "sip_config_ue_private_user_id_string"; - field public static final String KEY_SIP_CONFIG_UE_PUBLIC_GRUU_STRING = "sip_config_ue_public_gruu_string"; - field public static final String KEY_SIP_CONFIG_UE_PUBLIC_IPADDRESS_WITH_NAT_STRING = "sip_config_ue_public_ipaddress_with_nat_string"; - field public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = "sip_config_ue_public_port_with_nat_int"; - field public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = "sip_config_ue_public_user_id_string"; - field public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = "sip_config_uri_user_part_string"; - field public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING = "sip_config_sip_user_agent_header_string"; - field public static final String SIP_TRANSPORT_TCP = "TCP"; - field public static final String SIP_TRANSPORT_UDP = "UDP"; - } - - public static final class SipDelegateImsConfiguration.Builder { - ctor public SipDelegateImsConfiguration.Builder(int); - ctor public SipDelegateImsConfiguration.Builder(@NonNull android.telephony.ims.SipDelegateImsConfiguration); - method @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addBoolean(@NonNull String, boolean); - method @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addInt(@NonNull String, int); - method @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addString(@NonNull String, @NonNull String); - method @NonNull public android.telephony.ims.SipDelegateImsConfiguration build(); + method @Deprecated public boolean containsKey(@NonNull String); + method @Deprecated @NonNull public android.os.PersistableBundle copyBundle(); + method @Deprecated public int describeContents(); + method @Deprecated public boolean getBoolean(@NonNull String, boolean); + method @Deprecated public int getInt(@NonNull String, int); + method @Deprecated @Nullable public String getString(@NonNull String); + method @Deprecated public long getVersion(); + method @Deprecated public void writeToParcel(@NonNull android.os.Parcel, int); + field @Deprecated @NonNull public static final android.os.Parcelable.Creator<android.telephony.ims.SipDelegateImsConfiguration> CREATOR; + field @Deprecated public static final String IPTYPE_IPV4 = "IPV4"; + field @Deprecated public static final String IPTYPE_IPV6 = "IPV6"; + field @Deprecated public static final String KEY_SIP_CONFIG_AUTHENTICATION_HEADER_STRING = "sip_config_auhentication_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_AUTHENTICATION_NONCE_STRING = "sip_config_authentication_nonce_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_CELLULAR_NETWORK_INFO_HEADER_STRING = "sip_config_cellular_network_info_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_HOME_DOMAIN_STRING = "sip_config_home_domain_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_IMEI_STRING = "sip_config_imei_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_IPTYPE_STRING = "sip_config_iptype_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_COMPACT_FORM_ENABLED_BOOL = "sip_config_is_compact_form_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_GRUU_ENABLED_BOOL = "sip_config_is_gruu_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_IPSEC_ENABLED_BOOL = "sip_config_is_ipsec_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_KEEPALIVE_ENABLED_BOOL = "sip_config_is_keepalive_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_IS_NAT_ENABLED_BOOL = "sip_config_is_nat_enabled_bool"; + field @Deprecated public static final String KEY_SIP_CONFIG_MAX_PAYLOAD_SIZE_ON_UDP_INT = "sip_config_udp_max_payload_size_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_PATH_HEADER_STRING = "sip_config_path_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_P_ACCESS_NETWORK_INFO_HEADER_STRING = "sip_config_p_access_network_info_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_P_ASSOCIATED_URI_HEADER_STRING = "sip_config_p_associated_uri_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_P_LAST_ACCESS_NETWORK_INFO_HEADER_STRING = "sip_config_p_last_access_network_info_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_SECURITY_VERIFY_HEADER_STRING = "sip_config_security_verify_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_IPADDRESS_STRING = "sip_config_server_default_ipaddress_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_DEFAULT_PORT_INT = "sip_config_server_default_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_IPSEC_CLIENT_PORT_INT = "sip_config_server_ipsec_client_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_IPSEC_OLD_CLIENT_PORT_INT = "sip_config_server_ipsec_old_client_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVER_IPSEC_SERVER_PORT_INT = "sip_config_server_ipsec_server_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING = "sip_config_service_route_header_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_TRANSPORT_TYPE_STRING = "sip_config_protocol_type_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_DEFAULT_IPADDRESS_STRING = "sip_config_ue_default_ipaddress_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_DEFAULT_PORT_INT = "sip_config_ue_default_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_IPSEC_CLIENT_PORT_INT = "sip_config_ue_ipsec_client_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_IPSEC_OLD_CLIENT_PORT_INT = "sip_config_ue_ipsec_old_client_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_IPSEC_SERVER_PORT_INT = "sip_config_ue_ipsec_server_port_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PRIVATE_USER_ID_STRING = "sip_config_ue_private_user_id_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PUBLIC_GRUU_STRING = "sip_config_ue_public_gruu_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PUBLIC_IPADDRESS_WITH_NAT_STRING = "sip_config_ue_public_ipaddress_with_nat_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PUBLIC_PORT_WITH_NAT_INT = "sip_config_ue_public_port_with_nat_int"; + field @Deprecated public static final String KEY_SIP_CONFIG_UE_PUBLIC_USER_ID_STRING = "sip_config_ue_public_user_id_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_URI_USER_PART_STRING = "sip_config_uri_user_part_string"; + field @Deprecated public static final String KEY_SIP_CONFIG_USER_AGENT_HEADER_STRING = "sip_config_sip_user_agent_header_string"; + field @Deprecated public static final String SIP_TRANSPORT_TCP = "TCP"; + field @Deprecated public static final String SIP_TRANSPORT_UDP = "UDP"; + } + + @Deprecated public static final class SipDelegateImsConfiguration.Builder { + ctor @Deprecated public SipDelegateImsConfiguration.Builder(int); + ctor @Deprecated public SipDelegateImsConfiguration.Builder(@NonNull android.telephony.ims.SipDelegateImsConfiguration); + method @Deprecated @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addBoolean(@NonNull String, boolean); + method @Deprecated @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addInt(@NonNull String, int); + method @Deprecated @NonNull public android.telephony.ims.SipDelegateImsConfiguration.Builder addString(@NonNull String, @NonNull String); + method @Deprecated @NonNull public android.telephony.ims.SipDelegateImsConfiguration build(); } } diff --git a/core/api/test-current.txt b/core/api/test-current.txt index c79fc510c25a..1ff8c510b6bf 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1101,6 +1101,7 @@ package android.content.pm { field public static final long OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT = 265452344L; // 0xfd27b38L field public static final long OVERRIDE_USE_DISPLAY_LANDSCAPE_NATURAL_ORIENTATION = 255940284L; // 0xf4156bcL field public static final int RESIZE_MODE_RESIZEABLE = 2; // 0x2 + field public static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415L; // 0x15498ba7L } public class ApplicationInfo extends android.content.pm.PackageItemInfo implements android.os.Parcelable { diff --git a/core/java/Android.bp b/core/java/Android.bp index 1265de1ebb15..d12e1bf77e17 100644 --- a/core/java/Android.bp +++ b/core/java/Android.bp @@ -19,6 +19,7 @@ filegroup { srcs: [ "**/*.java", "**/*.aidl", + ":systemfeatures-gen-srcs", ":framework-nfc-non-updatable-sources", ":messagequeue-gen", ":ranging_stack_mock_initializer", @@ -214,6 +215,9 @@ filegroup { aidl_interface { name: "android.os.hintmanager_aidl", + defaults: [ + "android.hardware.power-aidl", + ], srcs: [ "android/os/IHintManager.aidl", "android/os/IHintSession.aidl", @@ -231,9 +235,6 @@ aidl_interface { enabled: true, }, }, - imports: [ - "android.hardware.power-V5", - ], } aidl_library { @@ -665,3 +666,29 @@ java_library { } // protolog end + +// Whether to enable read-only system feature codegen. +gen_readonly_feature_apis = select(release_flag("RELEASE_USE_SYSTEM_FEATURE_BUILD_FLAGS"), { + true: "true", + false: "false", + default: "false", +}) + +// Generates com.android.internal.pm.RoSystemFeatures, optionally compiling in +// details about fixed system features defined by build flags. When disabled, +// the APIs are simply passthrough stubs with no meaningful side effects. +genrule { + name: "systemfeatures-gen-srcs", + cmd: "$(location systemfeatures-gen-tool) com.android.internal.pm.RoSystemFeatures " + + // --readonly=false (default) makes the codegen an effective no-op passthrough API. + " --readonly=" + gen_readonly_feature_apis + + // For now, only export "android.hardware.type.*" system features APIs. + // TODO(b/203143243): Use an intermediate soong var that aggregates all declared + // RELEASE_SYSTEM_FEATURE_* declarations into a single arg. + " --feature-apis=AUTOMOTIVE,WATCH,TELEVISION,EMBEDDED,PC" + + " > $(out)", + out: [ + "RoSystemFeatures.java", + ], + tools: ["systemfeatures-gen-tool"], +} diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 7273e64846c0..36fc65a76d53 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1031,7 +1031,9 @@ public class ActivityManager { | PROCESS_CAPABILITY_FOREGROUND_AUDIO_CONTROL; /** - * All implicit capabilities. There are capabilities that process automatically have. + * All implicit capabilities. This capability set is currently only used for processes under + * active instrumentation. The intent is to allow CTS tests to always have these capabilities + * so that every test doesn't need to launch FGS. * @hide */ @TestApi diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java index 3bd121a4a19b..f80121d0c9b6 100644 --- a/core/java/android/app/ActivityManagerInternal.java +++ b/core/java/android/app/ActivityManagerInternal.java @@ -1328,7 +1328,8 @@ public abstract class ActivityManagerInternal { * Add a creator token for all embedded intents (stored as extra) of the given intent. * * @param intent The given intent + * @param creatorPackage the package name of the creator app. * @hide */ - public abstract void addCreatorToken(Intent intent); + public abstract void addCreatorToken(Intent intent, String creatorPackage); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 99625ac20e60..e7f4dbc24022 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -7346,6 +7346,8 @@ public final class ActivityThread extends ClientTransactionHandler } } + VMDebug.setUserId(UserHandle.myUserId()); + VMDebug.addApplication(data.appInfo.packageName); // send up app name; do this *before* waiting for debugger Process.setArgV0(data.processName); android.ddm.DdmHandleAppName.setAppName(data.processName, @@ -7868,9 +7870,20 @@ public final class ActivityThread extends ClientTransactionHandler file.getParentFile().mkdirs(); Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024); } + + if (ii.packageName != null) { + VMDebug.addApplication(ii.packageName); + } } private void handleFinishInstrumentationWithoutRestart() { + LoadedApk loadedApk = getApplication().mLoadedApk; + // Only remove instrumentation app if this was not a self-testing app. + if (mInstrumentationPackageName != null && loadedApk != null && !mInstrumentationPackageName + .equals(loadedApk.mPackageName)) { + VMDebug.removeApplication(mInstrumentationPackageName); + } + mInstrumentation.onDestroy(); mInstrumentationPackageName = null; mInstrumentationAppDir = null; @@ -8904,6 +8917,11 @@ public final class ActivityThread extends ClientTransactionHandler return false; } + void addApplication(@NonNull Application app) { + mAllApplications.add(app); + VMDebug.addApplication(app.mLoadedApk.mPackageName); + } + @Override public boolean isInDensityCompatMode() { return mDensityCompatMode; diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index 1df8f63aa402..1e45d6fd1674 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -1478,7 +1478,7 @@ public final class LoadedApk { + " package " + mPackageName + ": " + e.toString(), e); } } - mActivityThread.mAllApplications.add(app); + mActivityThread.addApplication(app); mApplication = app; if (!allowDuplicateInstances) { synchronized (sApplications) { diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 8b33417e0a79..4ef5b5163fef 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -11296,7 +11296,11 @@ public class Notification implements Parcelable * @see Segment */ public @NonNull ProgressStyle setProgressSegments(@NonNull List<Segment> progressSegments) { - mProgressSegments = new ArrayList<>(progressSegments.size()); + if (mProgressSegments == null) { + mProgressSegments = new ArrayList<>(); + } + mProgressSegments.clear(); + mProgressSegments.addAll(progressSegments); return this; } diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS index d363e19bcc19..ba71afb49629 100644 --- a/core/java/android/app/OWNERS +++ b/core/java/android/app/OWNERS @@ -97,6 +97,7 @@ per-file InstantAppResolveInfo.aidl = file:/services/core/java/com/android/serve # Performance per-file PropertyInvalidatedCache.java = file:/PERFORMANCE_OWNERS +per-file performance.aconfig = file:/PERFORMANCE_OWNERS # Pinner per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java index 439d988e2588..dca433696fe7 100644 --- a/core/java/android/app/appfunctions/AppFunctionManager.java +++ b/core/java/android/app/appfunctions/AppFunctionManager.java @@ -110,40 +110,6 @@ public final class AppFunctionManager { * * @param request the request to execute the app function * @param executor the executor to run the callback - * @param callback the callback to receive the function execution result. if the calling app - * does not own the app function or does not have {@code - * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code - * android.permission.EXECUTE_APP_FUNCTIONS}, the execution result will contain {@code - * ExecuteAppFunctionResponse.RESULT_DENIED}. - * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor, - * CancellationSignal, Consumer)} instead. This method will be removed once usage references - * are updated. - */ - @RequiresPermission( - anyOf = { - Manifest.permission.EXECUTE_APP_FUNCTIONS_TRUSTED, - Manifest.permission.EXECUTE_APP_FUNCTIONS - }, - conditional = true) - @UserHandleAware - @Deprecated - public void executeAppFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - executeAppFunction(request, executor, new CancellationSignal(), callback); - } - - /** - * Executes the app function. - * - * <p>Note: Applications can execute functions they define. To execute functions defined in - * another component, apps would need to have {@code - * android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED} or {@code - * android.permission.EXECUTE_APP_FUNCTIONS}. - * - * @param request the request to execute the app function - * @param executor the executor to run the callback * @param cancellationSignal the cancellation signal to cancel the execution. * @param callback the callback to receive the function execution result. if the calling app * does not own the app function or does not have {@code diff --git a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java index 08ecced234a9..06d95f5270c3 100644 --- a/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java +++ b/core/java/android/app/appfunctions/AppFunctionRuntimeMetadata.java @@ -137,8 +137,10 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { .TOKENIZER_TYPE_VERBATIM) .build()) .addProperty( - new AppSearchSchema.BooleanPropertyConfig.Builder(PROPERTY_ENABLED) + new AppSearchSchema.LongPropertyConfig.Builder(PROPERTY_ENABLED) .setCardinality(AppSearchSchema.PropertyConfig.CARDINALITY_OPTIONAL) + .setIndexingType( + AppSearchSchema.LongPropertyConfig.INDEXING_TYPE_RANGE) .build()) .addProperty( new AppSearchSchema.StringPropertyConfig.Builder( @@ -212,19 +214,14 @@ public class AppFunctionRuntimeMetadata extends GenericDocument { } /** - * Sets an indicator specifying if the function is enabled or not. This would override the - * default enabled state in the static metadata ({@link - * AppFunctionStaticMetadataHelper#STATIC_PROPERTY_ENABLED_BY_DEFAULT}). Sets this to null - * to clear the override. - * TODO(369683073) Replace the tristate Boolean with IntDef EnabledState. + * Sets an indicator specifying the function enabled state. */ @NonNull public Builder setEnabled(@EnabledState int enabledState) { if (enabledState != APP_FUNCTION_STATE_DEFAULT && enabledState != APP_FUNCTION_STATE_ENABLED && enabledState != APP_FUNCTION_STATE_DISABLED) { - throw new IllegalArgumentException( - "Value of EnabledState is unsupported."); + throw new IllegalArgumentException("Value of EnabledState is unsupported."); } setPropertyLong(PROPERTY_ENABLED, enabledState); return this; diff --git a/core/java/android/app/appfunctions/AppFunctionService.java b/core/java/android/app/appfunctions/AppFunctionService.java index ceca850a1037..63d187aa11ef 100644 --- a/core/java/android/app/appfunctions/AppFunctionService.java +++ b/core/java/android/app/appfunctions/AppFunctionService.java @@ -158,74 +158,6 @@ public abstract class AppFunctionService extends Service { * thread and dispatch the result with the given callback. You should always report back the * result using the callback, no matter if the execution was successful or not. * - * @param request The function execution request. - * @param callback A callback to report back the result. - * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, - * Consumer)} instead. This method will be removed once usage references are updated. - */ - @MainThread - @Deprecated - public void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - Log.w( - "AppFunctionService", - "Calling deprecated default implementation of onExecuteFunction"); - } - - /** - * Called by the system to execute a specific app function. - * - * <p>This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - * <p>To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * - * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel - * the execution of function if requested by the system. - * - * @param request The function execution request. - * @param cancellationSignal A signal to cancel the execution. - * @param callback A callback to report back the result. - * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, - * CancellationSignal, Consumer)} instead. This method will be removed once usage references - * are updated. - */ - @MainThread - @Deprecated - public void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - onExecuteFunction(request, callback); - } - - /** - * Called by the system to execute a specific app function. - * - * <p>This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - * <p>To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * * <p>This method also accepts a {@link CancellationSignal} that the app should listen to cancel * the execution of function if requested by the system. * @@ -235,11 +167,9 @@ public abstract class AppFunctionService extends Service { * @param callback A callback to report back the result. */ @MainThread - public void onExecuteFunction( + public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - onExecuteFunction(request, cancellationSignal, callback); - } + @NonNull Consumer<ExecuteAppFunctionResponse> callback); } diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig index 11e885055162..37fa9a26b91c 100644 --- a/core/java/android/app/notification.aconfig +++ b/core/java/android/app/notification.aconfig @@ -6,6 +6,14 @@ container: "system" # flag relates to. flag { + name: "notifications_redesign_app_icons" + namespace: "systemui" + description: "Notifications Redesign: Use app icons in notification rows (not to be confused with" + " notifications_use_app_icons, notifications_use_app_icon_in_row which are just experiments)." + bug: "371174789" +} + +flag { name: "modes_api" is_exported: true namespace: "systemui" diff --git a/core/java/android/app/usage/UsageEventsQuery.java b/core/java/android/app/usage/UsageEventsQuery.java index c0f13ca557e2..ecf4cd115fef 100644 --- a/core/java/android/app/usage/UsageEventsQuery.java +++ b/core/java/android/app/usage/UsageEventsQuery.java @@ -75,7 +75,7 @@ public final class UsageEventsQuery implements Parcelable { } /** - * Returns the exclusive timpstamp to indicate the end of the range of events. + * Returns the exclusive timestamp to indicate the end of the range of events. * Defined in terms of "Unix time", see {@link java.lang.System#currentTimeMillis}. */ public @CurrentTimeMillisLong long getEndTimeMillis() { diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java index 7f30d7cccb57..124973489dd1 100644 --- a/core/java/android/companion/AssociationInfo.java +++ b/core/java/android/companion/AssociationInfo.java @@ -287,7 +287,7 @@ public final class AssociationInfo implements Parcelable { /** * Get the device icon of the associated device. The device icon represents the device type. * - * @return the device icon, or {@code null} if no device icon is has been set for the + * @return the device icon, or {@code null} if no device icon has been set for the * associated device. * * @see AssociationRequest.Builder#setDeviceIcon(Icon) diff --git a/core/java/android/companion/AssociationRequest.java b/core/java/android/companion/AssociationRequest.java index 41a6791d8a7b..f368935a74c8 100644 --- a/core/java/android/companion/AssociationRequest.java +++ b/core/java/android/companion/AssociationRequest.java @@ -475,8 +475,8 @@ public final class AssociationRequest implements Parcelable { } /** - * Set the device icon for the self-managed device and this icon will be - * displayed in the self-managed association dialog. + * Set the device icon for the self-managed device and to display the icon in the + * self-managed association dialog. * * @throws IllegalArgumentException if the icon is not exactly 24dp by 24dp * or if it is {@link Icon#TYPE_URI} or {@link Icon#TYPE_URI_ADAPTIVE_BITMAP}. diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java index dfad6de4ba16..4472c3d13d7c 100644 --- a/core/java/android/companion/CompanionDeviceManager.java +++ b/core/java/android/companion/CompanionDeviceManager.java @@ -478,6 +478,15 @@ public final class CompanionDeviceManager { Objects.requireNonNull(callback, "Callback cannot be null"); handler = Handler.mainIfNull(handler); + if (Flags.associationDeviceIcon()) { + final Icon deviceIcon = request.getDeviceIcon(); + + if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) { + throw new IllegalArgumentException("The size of the device icon must be " + + "24dp x 24dp to ensure proper display"); + } + } + try { mService.associate(request, new AssociationRequestCallbackProxy(handler, callback), mContext.getOpPackageName(), mContext.getUserId()); @@ -542,11 +551,13 @@ public final class CompanionDeviceManager { Objects.requireNonNull(executor, "Executor cannot be null"); Objects.requireNonNull(callback, "Callback cannot be null"); - final Icon deviceIcon = request.getDeviceIcon(); + if (Flags.associationDeviceIcon()) { + final Icon deviceIcon = request.getDeviceIcon(); - if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) { - throw new IllegalArgumentException("The size of the device icon must be 24dp x 24dp to" - + "ensure proper display"); + if (deviceIcon != null && !isValidIcon(deviceIcon, mContext)) { + throw new IllegalArgumentException("The size of the device icon must be " + + "24dp x 24dp to ensure proper display"); + } } try { diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 91f7a8bae163..628435da3a37 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -5658,6 +5658,14 @@ public abstract class Context { public static final String BINARY_TRANSPARENCY_SERVICE = "transparency"; /** + * System service name for ForensicService. + * The service manages the forensic info on device. + * @hide + */ + @FlaggedApi(android.security.Flags.FLAG_AFL_API) + public static final String FORENSIC_SERVICE = "forensic"; + + /** * System service name for the DeviceIdleManager. * @see #getSystemService(String) * @hide diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 0bb0027fb0c3..f71952849872 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -888,6 +888,22 @@ public class Intent implements Parcelable, Cloneable { public static final String ACTION_ACTIVITY_RECOGNIZER = "android.intent.action.ACTIVITY_RECOGNIZER"; + /** @hide */ + public static void maybeMarkAsMissingCreatorToken(Object object) { + if (object instanceof Intent intent) { + maybeMarkAsMissingCreatorTokenInternal(intent); + } + } + + private static void maybeMarkAsMissingCreatorTokenInternal(Intent intent) { + boolean isForeign = (intent.mLocalFlags & LOCAL_FLAG_FROM_PARCEL) != 0; + boolean isWithoutTrustedCreatorToken = + (intent.mLocalFlags & Intent.LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT) == 0; + if (isForeign && isWithoutTrustedCreatorToken) { + intent.addExtendedFlags(EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN); + } + } + /** * Represents a shortcut/live folder icon resource. * @@ -7684,10 +7700,8 @@ public class Intent implements Parcelable, Cloneable { /** * This flag indicates the creator token of this intent has been verified. - * - * @hide */ - public static final int LOCAL_FLAG_CREATOR_TOKEN_VERIFIED = 1 << 6; + private static final int LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT = 1 << 6; /** @hide */ @IntDef(flag = true, prefix = { "EXTENDED_FLAG_" }, value = { @@ -12243,6 +12257,30 @@ public class Intent implements Parcelable, Cloneable { } } + /** @hide */ + public void checkCreatorToken() { + if (mExtras == null) return; + if (mCreatorTokenInfo != null && mCreatorTokenInfo.mExtraIntentKeys != null) { + for (String key : mCreatorTokenInfo.mExtraIntentKeys) { + try { + Intent extraIntent = mExtras.getParcelable(key, Intent.class); + if (extraIntent == null) { + Log.w(TAG, "The key {" + key + + "} does not correspond to an intent in the bundle."); + continue; + } + extraIntent.mLocalFlags |= LOCAL_FLAG_TRUSTED_CREATOR_TOKEN_PRESENT; + } catch (Exception e) { + Log.e(TAG, "Failed to validate creator token. key: " + key + ".", e); + } + } + } + // mark the bundle as intent extras after calls to getParcelable. + // otherwise, the logic to mark missing token would run before + // mark trusted creator token present. + mExtras.setIsIntentExtra(); + } + public void writeToParcel(Parcel out, int flags) { out.writeString8(mAction); Uri.writeToParcel(out, mData); @@ -12730,6 +12768,7 @@ public class Intent implements Parcelable, Cloneable { } mLocalFlags |= localFlags; + checkCreatorToken(); // Special attribution fix-up logic for any BluetoothDevice extras // passed via Bluetooth intents diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 481e6b530162..ce52825ddb73 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -21,10 +21,12 @@ import android.annotation.FloatRange; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.SuppressLint; import android.annotation.TestApi; import android.app.Activity; import android.compat.annotation.ChangeId; import android.compat.annotation.Disabled; +import android.compat.annotation.EnabledAfter; import android.compat.annotation.EnabledSince; import android.compat.annotation.Overridable; import android.compat.annotation.UnsupportedAppUsage; @@ -1204,6 +1206,18 @@ public class ActivityInfo extends ComponentInfo implements Parcelable { public @interface SizeChangesSupportMode {} /** + * This change id makes the restriction of fixed orientation, aspect ratio, and resizability + * of the app to be ignored, which means making the app fill the given available area. + * @hide + */ + @ChangeId + @Overridable + @TestApi + @SuppressLint("UnflaggedApi") // @TestApi without associated public API. + @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) + public static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415L; // buganizer id + + /** * This change id enables compat policy that ignores app requested orientation in * response to an app calling {@link android.app.Activity#setRequestedOrientation}. See * com.android.server.wm.LetterboxUiController#shouldIgnoreRequestedOrientation for diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl index 451c0e5e079a..c911326ccffd 100644 --- a/core/java/android/content/pm/IPackageInstaller.aidl +++ b/core/java/android/content/pm/IPackageInstaller.aidl @@ -93,4 +93,10 @@ interface IPackageInstaller { @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})") void reportUnarchivalStatus(int unarchiveId, int status, long requiredStorageBytes, in PendingIntent userActionIntent, in UserHandle userHandle); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)") + int getVerificationPolicy(); + + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)") + boolean setVerificationPolicy(int policy); } diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index c673d5846d5d..cba7bc912666 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -62,6 +62,8 @@ import android.content.pm.parsing.PackageLite; import android.content.pm.parsing.result.ParseResult; import android.content.pm.parsing.result.ParseTypeImpl; import android.content.pm.verify.domain.DomainSet; +import android.content.pm.verify.pkg.VerificationSession; +import android.content.pm.verify.pkg.VerificationStatus; import android.graphics.Bitmap; import android.icu.util.ULocale; import android.net.Uri; @@ -418,6 +420,21 @@ public class PackageInstaller { public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS"; /** + * When verification is blocked as part of the installation, additional reason for the block + * will be provided to the installer with a {@link VerificationFailedReason} as part of the + * installation result returned via the {@link IntentSender} in + * {@link Session#commit(IntentSender)}. This extra is provided only when the installation has + * failed. Installers can use this extra to check if the installation failure was caused by a + * verification failure. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + public static final String EXTRA_VERIFICATION_FAILURE_REASON = + "android.content.pm.extra.VERIFICATION_FAILURE_REASON"; + + /** * Streaming installation pending. * Caller should make sure DataLoader is able to prepare image and reinitiate the operation. * @@ -760,6 +777,90 @@ public class PackageInstaller { @Retention(RetentionPolicy.SOURCE) public @interface UnarchivalStatus {} + /** + * Verification failed because of unknown reasons, such as when the verifier times out or cannot + * be connected. It can also corresponds to the status of + * {@link VerificationSession#VERIFICATION_INCOMPLETE_UNKNOWN} reported by the verifier via + * {@link VerificationSession#reportVerificationIncomplete(int)}. + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + public static final int VERIFICATION_FAILED_REASON_UNKNOWN = 0; + + /** + * Verification failed because the network is unavailable. This corresponds to the status of + * {@link VerificationSession#VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE} reported by the + * verifier via {@link VerificationSession#reportVerificationIncomplete(int)}. + * + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + public static final int VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE = 1; + + /** + * Verification failed because the package is blocked, as reported by the verifier via + * {@link VerificationSession#reportVerificationComplete(VerificationStatus)} or + * {@link VerificationSession#reportVerificationComplete(VerificationStatus, PersistableBundle)} + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + public static final int VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED = 2; + + /** + * @hide + */ + @IntDef(value = { + VERIFICATION_FAILED_REASON_UNKNOWN, + VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE, + VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED, + }) + public @interface VerificationFailedReason { + } + + /** + * Do not block installs, regardless of verification status. + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + public static final int VERIFICATION_POLICY_NONE = 0; // platform default + /** + * Only block installations on {@link #VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED}. + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + public static final int VERIFICATION_POLICY_BLOCK_FAIL_OPEN = 1; + /** + * Only block installations on {@link #VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED} and ask the + * user if they'd like to install anyway when the verification is blocked for other reason. + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + public static final int VERIFICATION_POLICY_BLOCK_FAIL_WARN = 2; + /** + * Block installations whose verification status is blocked for any reason. + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + public static final int VERIFICATION_POLICY_BLOCK_FAIL_CLOSED = 3; + /** + * @hide + */ + @IntDef(value = { + VERIFICATION_POLICY_NONE, + VERIFICATION_POLICY_BLOCK_FAIL_OPEN, + VERIFICATION_POLICY_BLOCK_FAIL_WARN, + VERIFICATION_POLICY_BLOCK_FAIL_CLOSED, + }) + @Retention(RetentionPolicy.SOURCE) + public @interface VerificationPolicy { + } /** Default set of checksums - includes all available checksums. * @see Session#requestChecksums */ @@ -1503,6 +1604,40 @@ public class PackageInstaller { } /** + * Return the current verification enforcement policy. This may only be called by the + * package currently set by the system as the verifier agent. + * @hide + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) + public final @VerificationPolicy int getVerificationPolicy() { + try { + return mInstaller.getVerificationPolicy(); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Set the current verification enforcement policy which will be applied to all the future + * installation sessions. This may only be called by the package currently set by the system as + * the verifier agent. + * @hide + * @return whether the new policy was successfully set. + */ + @FlaggedApi(Flags.FLAG_VERIFICATION_SERVICE) + @SystemApi + @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) + public final boolean setVerificationPolicy(@VerificationPolicy int policy) { + try { + return mInstaller.setVerificationPolicy(policy); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * An installation that is being actively staged. For an install to succeed, * all existing and new packages must have identical package names, version * codes, and signing certificates. diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig index c7d7dc1eb0de..5b38942d468d 100644 --- a/core/java/android/content/pm/flags.aconfig +++ b/core/java/android/content/pm/flags.aconfig @@ -326,3 +326,19 @@ flag { bug: "360129103" is_fixed_read_only: true } + +flag { + name: "include_feature_flags_in_package_cacher" + namespace: "package_manager_service" + description: "Include feature flag status when determining hits or misses in PackageCacher." + bug: "364771256" + is_fixed_read_only: true +} + +flag { + name: "reduce_broadcasts_for_component_state_changes" + namespace: "package_manager_service" + description: "Feature flag to limit sending of the PACKAGE_CHANGED broadcast to only the system and the application itself during component state changes." + bug: "292261144" + is_fixed_read_only: true +} diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig index 7de7131fc2ad..fa3bc9e16a6b 100644 --- a/core/java/android/content/pm/multiuser.aconfig +++ b/core/java/android/content/pm/multiuser.aconfig @@ -211,6 +211,18 @@ flag { } flag { + name: "property_invalidated_cache_bypass_mismatched_uids" + namespace: "multiuser" + description: "Bypass the cache when the process UID does not match the binder UID." + bug: "373752556" + metadata { + purpose: PURPOSE_BUGFIX + } + is_fixed_read_only: true +} + + +flag { name: "cache_profile_parent_read_only" namespace: "multiuser" description: "Cache getProfileParent to avoid unnecessary binder calls" diff --git a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl index 7a9484abd1b1..036c1e69cb0d 100644 --- a/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl +++ b/core/java/android/content/pm/verify/pkg/IVerificationSessionInterface.aidl @@ -25,4 +25,6 @@ interface IVerificationSessionInterface { long getTimeoutTime(int verificationId); @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)") long extendTimeRemaining(int verificationId, long additionalMs); + @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT)") + boolean setVerificationPolicy(int verificationId, int policy); }
\ No newline at end of file diff --git a/core/java/android/content/pm/verify/pkg/VerificationSession.java b/core/java/android/content/pm/verify/pkg/VerificationSession.java index 70b4a022f521..f393be829aed 100644 --- a/core/java/android/content/pm/verify/pkg/VerificationSession.java +++ b/core/java/android/content/pm/verify/pkg/VerificationSession.java @@ -22,6 +22,7 @@ import android.annotation.NonNull; import android.annotation.RequiresPermission; import android.annotation.SystemApi; import android.content.pm.Flags; +import android.content.pm.PackageInstaller; import android.content.pm.SharedLibraryInfo; import android.content.pm.SigningInfo; import android.net.Uri; @@ -52,17 +53,12 @@ public final class VerificationSession implements Parcelable { * The verification cannot be completed because the network is unavailable. */ public static final int VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE = 1; - /** - * The verification cannot be completed because the network is limited. - */ - public static final int VERIFICATION_INCOMPLETE_NETWORK_LIMITED = 2; /** * @hide */ @IntDef(prefix = {"VERIFICATION_INCOMPLETE_"}, value = { VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE, - VERIFICATION_INCOMPLETE_NETWORK_LIMITED, VERIFICATION_INCOMPLETE_UNKNOWN, }) @Retention(RetentionPolicy.SOURCE) @@ -85,6 +81,15 @@ public final class VerificationSession implements Parcelable { private final IVerificationSessionInterface mSession; @NonNull private final IVerificationSessionCallback mCallback; + /** + * The current policy that is active for the session. It might not be + * the same as the original policy that was initially assigned for this verification session, + * because the active policy can be overridden by {@link #setVerificationPolicy(int)}. + * <p>To improve the latency, store the original policy value and any changes made to it, + * so that {@link #getVerificationPolicy()} does not need to make a binder call to retrieve the + * currently active policy.</p> + */ + private volatile @PackageInstaller.VerificationPolicy int mVerificationPolicy; /** * Constructor used by the system to describe the details of a verification session. @@ -94,6 +99,7 @@ public final class VerificationSession implements Parcelable { @NonNull Uri stagedPackageUri, @NonNull SigningInfo signingInfo, @NonNull List<SharedLibraryInfo> declaredLibraries, @NonNull PersistableBundle extensionParams, + @PackageInstaller.VerificationPolicy int defaultPolicy, @NonNull IVerificationSessionInterface session, @NonNull IVerificationSessionCallback callback) { mId = id; @@ -103,6 +109,7 @@ public final class VerificationSession implements Parcelable { mSigningInfo = signingInfo; mDeclaredLibraries = declaredLibraries; mExtensionParams = extensionParams; + mVerificationPolicy = defaultPolicy; mSession = session; mCallback = callback; } @@ -144,7 +151,7 @@ public final class VerificationSession implements Parcelable { /** * Returns a mapping of any shared libraries declared in the manifest - * to the {@link SharedLibraryInfo#Type} that is declared. This will be an empty + * to the {@link SharedLibraryInfo.Type} that is declared. This will be an empty * map if no shared libraries are declared by the package. */ @NonNull @@ -174,6 +181,39 @@ public final class VerificationSession implements Parcelable { } /** + * Return the current policy that is active for this session. + * <p>If the policy for this session has been changed by {@link #setVerificationPolicy}, + * the return value of this method is the current policy that is active for this session. + * Otherwise, the return value is the same as the initial policy that was assigned to the + * session when it was first created.</p> + */ + public @PackageInstaller.VerificationPolicy int getVerificationPolicy() { + return mVerificationPolicy; + } + + /** + * Override the verification policy for this session. + * @return True if the override was successful, False otherwise. + */ + @RequiresPermission(android.Manifest.permission.VERIFICATION_AGENT) + public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) { + if (mVerificationPolicy == policy) { + // No effective policy change + return true; + } + try { + if (mSession.setVerificationPolicy(mId, policy)) { + mVerificationPolicy = policy; + return true; + } else { + return false; + } + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** * Extend the timeout for this session by the provided additionalMs to * fetch relevant information over the network or wait for the network. * This may be called multiple times. If the request would bypass any max @@ -239,6 +279,7 @@ public final class VerificationSession implements Parcelable { mSigningInfo = SigningInfo.CREATOR.createFromParcel(in); mDeclaredLibraries = in.createTypedArrayList(SharedLibraryInfo.CREATOR); mExtensionParams = in.readPersistableBundle(getClass().getClassLoader()); + mVerificationPolicy = in.readInt(); mSession = IVerificationSessionInterface.Stub.asInterface(in.readStrongBinder()); mCallback = IVerificationSessionCallback.Stub.asInterface(in.readStrongBinder()); } @@ -257,6 +298,7 @@ public final class VerificationSession implements Parcelable { mSigningInfo.writeToParcel(dest, flags); dest.writeTypedList(mDeclaredLibraries); dest.writePersistableBundle(mExtensionParams); + dest.writeInt(mVerificationPolicy); dest.writeStrongBinder(mSession.asBinder()); dest.writeStrongBinder(mCallback.asBinder()); } diff --git a/core/java/android/content/pm/xr.aconfig b/core/java/android/content/pm/xr.aconfig new file mode 100644 index 000000000000..61835c162c49 --- /dev/null +++ b/core/java/android/content/pm/xr.aconfig @@ -0,0 +1,9 @@ +package: "android.xr" +container: "system" + +flag { + namespace: "xr" + name: "xr_manifest_entries" + description: "Adds manifest entries used by Android XR" + bug: "364416355" +}
\ No newline at end of file diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig index 99eeca98bce0..f03a4a954842 100644 --- a/core/java/android/hardware/input/input_framework.aconfig +++ b/core/java/android/hardware/input/input_framework.aconfig @@ -142,3 +142,10 @@ flag { description: "Controls whether the connected mice's primary buttons, left and right, can be swapped." bug: "352598211" } + +flag { + name: "keyboard_a11y_shortcut_control" + namespace: "input" + description: "Adds shortcuts to toggle and control a11y features" + bug: "373458181" +} diff --git a/core/java/android/hardware/radio/RadioAlert.aidl b/core/java/android/hardware/radio/RadioAlert.aidl new file mode 100644 index 000000000000..17f4fc7e9a13 --- /dev/null +++ b/core/java/android/hardware/radio/RadioAlert.aidl @@ -0,0 +1,20 @@ +/** + * Copyright (C) 2024 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.hardware.radio; + +/** @hide */ +parcelable RadioAlert;
\ No newline at end of file diff --git a/core/java/android/hardware/radio/RadioAlert.java b/core/java/android/hardware/radio/RadioAlert.java new file mode 100644 index 000000000000..b55dcd82ef7b --- /dev/null +++ b/core/java/android/hardware/radio/RadioAlert.java @@ -0,0 +1,505 @@ +/** + * Copyright (C) 2024 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.hardware.radio; + +import android.annotation.FlaggedApi; +import android.annotation.Nullable; +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * Emergency Alert Message + * + * <p>Alert message can be sent from a radio station of technologies such as HD radio to + * the radio users for some emergency events (see ITU-T X.1303 bis for more info). + * @hide + */ +@FlaggedApi(Flags.FLAG_HD_RADIO_EMERGENCY_ALERT_SYSTEM) +public final class RadioAlert implements Parcelable { + + public static final class Geocode implements Parcelable { + + private final String mValueName; + private final String mValue; + + /** + * Constructor of geocode. + * + * @param valueName Name of geocode value + * @param value Value of geocode + * @hide + */ + public Geocode(@NonNull String valueName, @NonNull String value) { + mValueName = Objects.requireNonNull(valueName, "Geocode value name can not be null"); + mValue = Objects.requireNonNull(value, "Geocode value can not be null"); + } + + private Geocode(Parcel in) { + mValueName = in.readString8(); + mValue = in.readString8(); + } + + public static final @NonNull Creator<Geocode> CREATOR = new Creator<Geocode>() { + @Override + public Geocode createFromParcel(Parcel in) { + return new Geocode(in); + } + + @Override + public Geocode[] newArray(int size) { + return new Geocode[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeString8(mValueName); + dest.writeString8(mValue); + } + + @NonNull + @Override + public String toString() { + return "Gecode [valueName=" + mValueName + ", value=" + mValue + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mValueName, mValue); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Geocode other)) { + return false; + } + + return Objects.equals(mValueName, other.mValueName) + && Objects.equals(mValue, other.mValue); + } + } + + public static final class Coordinate implements Parcelable { + private final double mLatitude; + private final double mLongitude; + + /** + * Constructor of coordinate. + * + * @param latitude Latitude of the coordinate + * @param longitude Longitude of the coordinate + * @hide + */ + public Coordinate(double latitude, double longitude) { + if (latitude < -90.0 || latitude > 90.0) { + throw new IllegalArgumentException("Latitude value should be between -90 and 90"); + } + if (longitude < -180.0 || longitude > 180.0) { + throw new IllegalArgumentException( + "Longitude value should be between -180 and 180"); + } + mLatitude = latitude; + mLongitude = longitude; + } + + private Coordinate(Parcel in) { + mLatitude = in.readDouble(); + mLongitude = in.readDouble(); + } + + public static final @NonNull Creator<Coordinate> CREATOR = new Creator<Coordinate>() { + @Override + public Coordinate createFromParcel(Parcel in) { + return new Coordinate(in); + } + + @Override + public Coordinate[] newArray(int size) { + return new Coordinate[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeDouble(mLatitude); + dest.writeDouble(mLongitude); + } + + @NonNull + @Override + public String toString() { + return "Coordinate [latitude=" + mLatitude + ", longitude=" + mLongitude + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mLatitude, mLongitude); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Coordinate other)) { + return false; + } + return mLatitude == other.mLatitude && mLongitude == other.mLongitude; + } + } + + public static final class Polygon implements Parcelable { + + private final List<Coordinate> mCoordinates; + + /** + * Constructor of polygon. + * + * @param coordinates Coordinates the polygon is composed of + * @hide + */ + public Polygon(@NonNull List<Coordinate> coordinates) { + Objects.requireNonNull(coordinates, "Coordinates can not be null"); + if (coordinates.size() < 4) { + throw new IllegalArgumentException("Number of coordinates must be at least 4"); + } + if (!coordinates.get(0).equals(coordinates.get(coordinates.size() - 1))) { + throw new IllegalArgumentException( + "The last and first coordinates must be the same"); + } + mCoordinates = coordinates; + } + + private Polygon(Parcel in) { + mCoordinates = new ArrayList<>(); + in.readTypedList(mCoordinates, Coordinate.CREATOR); + } + + public static final @NonNull Creator<Polygon> CREATOR = new Creator<Polygon>() { + @Override + public Polygon createFromParcel(Parcel in) { + return new Polygon(in); + } + + @Override + public Polygon[] newArray(int size) { + return new Polygon[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedList(mCoordinates); + } + + @NonNull + @Override + public String toString() { + return "Polygon [coordinates=" + mCoordinates + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mCoordinates); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof Polygon other)) { + return false; + } + return mCoordinates.equals(other.mCoordinates); + } + } + + public static final class AlertArea implements Parcelable { + + private final List<Polygon> mPolygons; + private final List<Geocode> mGeocodes; + + /** + * Constructor of alert area. + * + * @param polygons Polygons used in alert area + * @param geocodes Geocodes used in alert area + * @hide + */ + public AlertArea(@NonNull List<Polygon> polygons, @NonNull List<Geocode> geocodes) { + mPolygons = Objects.requireNonNull(polygons, "Polygons can not be null"); + mGeocodes = Objects.requireNonNull(geocodes, "Geocodes can not be null"); + } + + private AlertArea(Parcel in) { + mPolygons = new ArrayList<>(); + mGeocodes = new ArrayList<>(); + in.readTypedList(mPolygons, Polygon.CREATOR); + in.readTypedList(mGeocodes, Geocode.CREATOR); + } + + public static final @NonNull Creator<AlertArea> CREATOR = new Creator<AlertArea>() { + @Override + public AlertArea createFromParcel(Parcel in) { + return new AlertArea(in); + } + + @Override + public AlertArea[] newArray(int size) { + return new AlertArea[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeTypedList(mPolygons); + dest.writeTypedList(mGeocodes); + } + + @NonNull + @Override + public String toString() { + return "AlertArea [polygons=" + mPolygons + ", geocodes=" + mGeocodes + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mPolygons, mGeocodes); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AlertArea other)) { + return false; + } + + return mPolygons.equals(other.mPolygons) && mGeocodes.equals(other.mGeocodes); + } + } + + public static final class AlertInfo implements Parcelable { + + private final List<Integer> mCategoryList; + private final int mUrgency; + private final int mSeverity; + private final int mCertainty; + private final String mTextualMessage; + private final List<AlertArea> mAreaList; + + /** + * Constructor for alert info. + * + * @param categoryList Array of categories of the subject event of the alert message + * @param urgency The urgency of the subject event of the alert message + * @param severity The severity of the subject event of the alert message + * @param certainty The certainty of the subject event of the alert message + * @param textualMessage Textual descriptions of the subject event + * @param areaList The array of geographic areas to which the alert info segment in which + * it appears applies + * @hide + */ + public AlertInfo(@NonNull List<Integer> categoryList, int urgency, + int severity, int certainty, + String textualMessage, @NonNull List<AlertArea> areaList) { + mCategoryList = Objects.requireNonNull(categoryList, "Category list can not be null"); + mUrgency = urgency; + mSeverity = severity; + mCertainty = certainty; + mTextualMessage = textualMessage; + mAreaList = Objects.requireNonNull(areaList, "Area list can not be null"); + } + + private AlertInfo(Parcel in) { + mCategoryList = in.readArrayList(Integer.class.getClassLoader(), Integer.class); + mUrgency = in.readInt(); + mSeverity = in.readInt(); + mCertainty = in.readInt(); + mTextualMessage = in.readString8(); + mAreaList = new ArrayList<>(); + in.readTypedList(mAreaList, AlertArea.CREATOR); + } + + public static final @NonNull Creator<AlertInfo> CREATOR = new Creator<AlertInfo>() { + @Override + public AlertInfo createFromParcel(Parcel in) { + return new AlertInfo(in); + } + + @Override + public AlertInfo[] newArray(int size) { + return new AlertInfo[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeList(mCategoryList); + dest.writeInt(mUrgency); + dest.writeInt(mSeverity); + dest.writeInt(mCertainty); + dest.writeString8(mTextualMessage); + dest.writeTypedList(mAreaList); + } + + @NonNull + @Override + public String toString() { + return "AlertInfo [categoryList=" + mCategoryList + ", urgency=" + mUrgency + + ", severity=" + mSeverity + ", certainty=" + mCertainty + + ", textualMessage=" + mTextualMessage + ", areaList=" + mAreaList + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mCategoryList, mUrgency, mSeverity, mCertainty, mTextualMessage, + mAreaList); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof AlertInfo other)) { + return false; + } + + return mCategoryList.equals(other.mCategoryList) && mUrgency == other.mUrgency + && mSeverity == other.mSeverity && mCertainty == other.mCertainty + && mTextualMessage.equals(other.mTextualMessage) + && mAreaList.equals(other.mAreaList); + } + } + + private final int mStatus; + private final int mMessageType; + private final List<AlertInfo> mInfoList; + private final int mScope; + + /** + * Constructor of radio alert message. + * + * @param status Status of alert message + * @param messageType Message type of alert message + * @param infoList List of alert info + * @param scope Scope of alert message + * @hide + */ + public RadioAlert(int status, int messageType, + @NonNull List<AlertInfo> infoList, int scope) { + mStatus = status; + mMessageType = messageType; + mInfoList = Objects.requireNonNull(infoList, "Alert info list can not be null"); + mScope = scope; + } + + private RadioAlert(Parcel in) { + mStatus = in.readInt(); + mMessageType = in.readInt(); + mInfoList = in.readParcelableList(new ArrayList<>(), AlertInfo.class.getClassLoader(), + AlertInfo.class); + mScope = in.readInt(); + } + + @Override + public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mStatus); + dest.writeInt(mMessageType); + dest.writeParcelableList(mInfoList, /* flags= */ 0); + dest.writeInt(mScope); + } + + @Override + public int describeContents() { + return 0; + } + + @NonNull + @Override + public String toString() { + return "RadioAlert [status=" + mStatus + ", messageType=" + mMessageType + + ", infoList= " + mInfoList + ", scope=" + mScope + "]"; + } + + @Override + public int hashCode() { + return Objects.hash(mStatus, mMessageType, mInfoList, mScope); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof RadioAlert other)) { + return false; + } + + return mStatus == other.mStatus && mMessageType == other.mMessageType + && mInfoList.equals(other.mInfoList) && mScope == other.mScope; + } + + public static final @NonNull Creator<RadioAlert> CREATOR = new Creator<RadioAlert>() { + @Override + public RadioAlert createFromParcel(Parcel in) { + return new RadioAlert(in); + } + + @Override + public RadioAlert[] newArray(int size) { + return new RadioAlert[size]; + } + }; +} diff --git a/core/java/android/os/BaseBundle.java b/core/java/android/os/BaseBundle.java index 49ab15a40a8e..50121278f0e6 100644 --- a/core/java/android/os/BaseBundle.java +++ b/core/java/android/os/BaseBundle.java @@ -21,6 +21,7 @@ import static java.util.Objects.requireNonNull; import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; +import android.content.Intent; import android.util.ArrayMap; import android.util.Log; import android.util.MathUtils; @@ -401,6 +402,9 @@ public class BaseBundle { synchronized (this) { object = unwrapLazyValueFromMapLocked(i, clazz, itemTypes); } + if ((mFlags & Bundle.FLAG_VERIFY_TOKENS_PRESENT) != 0) { + Intent.maybeMarkAsMissingCreatorToken(object); + } } return (clazz != null) ? clazz.cast(object) : (T) object; } diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index d3ba73ed1421..a89483394611 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -1278,6 +1278,41 @@ public class Build { /** @hide */ @IntDef(value = { + VERSION_CODES_FULL.BASE, + VERSION_CODES_FULL.BASE_1_1, + VERSION_CODES_FULL.CUPCAKE, + VERSION_CODES_FULL.DONUT, + VERSION_CODES_FULL.ECLAIR, + VERSION_CODES_FULL.ECLAIR_0_1, + VERSION_CODES_FULL.ECLAIR_MR1, + VERSION_CODES_FULL.FROYO, + VERSION_CODES_FULL.GINGERBREAD, + VERSION_CODES_FULL.GINGERBREAD_MR1, + VERSION_CODES_FULL.HONEYCOMB, + VERSION_CODES_FULL.HONEYCOMB_MR1, + VERSION_CODES_FULL.HONEYCOMB_MR2, + VERSION_CODES_FULL.ICE_CREAM_SANDWICH, + VERSION_CODES_FULL.ICE_CREAM_SANDWICH_MR1, + VERSION_CODES_FULL.JELLY_BEAN, + VERSION_CODES_FULL.JELLY_BEAN_MR1, + VERSION_CODES_FULL.JELLY_BEAN_MR2, + VERSION_CODES_FULL.KITKAT, + VERSION_CODES_FULL.KITKAT_WATCH, + VERSION_CODES_FULL.LOLLIPOP, + VERSION_CODES_FULL.LOLLIPOP_MR1, + VERSION_CODES_FULL.M, + VERSION_CODES_FULL.N, + VERSION_CODES_FULL.N_MR1, + VERSION_CODES_FULL.O, + VERSION_CODES_FULL.O_MR1, + VERSION_CODES_FULL.P, + VERSION_CODES_FULL.Q, + VERSION_CODES_FULL.R, + VERSION_CODES_FULL.S, + VERSION_CODES_FULL.S_V2, + VERSION_CODES_FULL.TIRAMISU, + VERSION_CODES_FULL.UPSIDE_DOWN_CAKE, + VERSION_CODES_FULL.VANILLA_ICE_CREAM, }) @Retention(RetentionPolicy.SOURCE) public @interface SdkIntFull {} @@ -1299,6 +1334,186 @@ public class Build { // Use the last 5 digits for the minor version. This allows the // minor version to be set to CUR_DEVELOPMENT. private static final int SDK_INT_MULTIPLIER = 100000; + + /** + * Android 1.0. + */ + public static final int BASE = VERSION_CODES.BASE * SDK_INT_MULTIPLIER; + + /** + * Android 2.0. + */ + public static final int BASE_1_1 = VERSION_CODES.BASE_1_1 * SDK_INT_MULTIPLIER; + + /** + * Android 3.0. + */ + public static final int CUPCAKE = VERSION_CODES.CUPCAKE * SDK_INT_MULTIPLIER; + + /** + * Android 4.0. + */ + public static final int DONUT = VERSION_CODES.DONUT * SDK_INT_MULTIPLIER; + + /** + * Android 5.0. + */ + public static final int ECLAIR = VERSION_CODES.ECLAIR * SDK_INT_MULTIPLIER; + + /** + * Android 6.0. + */ + public static final int ECLAIR_0_1 = VERSION_CODES.ECLAIR_0_1 * SDK_INT_MULTIPLIER; + + /** + * Android 7.0. + */ + public static final int ECLAIR_MR1 = VERSION_CODES.ECLAIR_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 8.0. + */ + public static final int FROYO = VERSION_CODES.FROYO * SDK_INT_MULTIPLIER; + + /** + * Android 9.0. + */ + public static final int GINGERBREAD = VERSION_CODES.GINGERBREAD * SDK_INT_MULTIPLIER; + + /** + * Android 10.0. + */ + public static final int GINGERBREAD_MR1 = + VERSION_CODES.GINGERBREAD_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 11.0. + */ + public static final int HONEYCOMB = VERSION_CODES.HONEYCOMB * SDK_INT_MULTIPLIER; + + /** + * Android 12.0. + */ + public static final int HONEYCOMB_MR1 = VERSION_CODES.HONEYCOMB_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 13.0. + */ + public static final int HONEYCOMB_MR2 = VERSION_CODES.HONEYCOMB_MR2 * SDK_INT_MULTIPLIER; + + /** + * Android 14.0. + */ + public static final int ICE_CREAM_SANDWICH = + VERSION_CODES.ICE_CREAM_SANDWICH * SDK_INT_MULTIPLIER; + + /** + * Android 15.0. + */ + public static final int ICE_CREAM_SANDWICH_MR1 = + VERSION_CODES.ICE_CREAM_SANDWICH_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 16.0. + */ + public static final int JELLY_BEAN = VERSION_CODES.JELLY_BEAN * SDK_INT_MULTIPLIER; + + /** + * Android 17.0. + */ + public static final int JELLY_BEAN_MR1 = VERSION_CODES.JELLY_BEAN_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 18.0. + */ + public static final int JELLY_BEAN_MR2 = VERSION_CODES.JELLY_BEAN_MR2 * SDK_INT_MULTIPLIER; + + /** + * Android 19.0. + */ + public static final int KITKAT = VERSION_CODES.KITKAT * SDK_INT_MULTIPLIER; + + /** + * Android 20.0. + */ + public static final int KITKAT_WATCH = VERSION_CODES.KITKAT_WATCH * SDK_INT_MULTIPLIER; + + /** + * Android 21.0. + */ + public static final int LOLLIPOP = VERSION_CODES.LOLLIPOP * SDK_INT_MULTIPLIER; + + /** + * Android 22.0. + */ + public static final int LOLLIPOP_MR1 = VERSION_CODES.LOLLIPOP_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 23.0. + */ + public static final int M = VERSION_CODES.M * SDK_INT_MULTIPLIER; + + /** + * Android 24.0. + */ + public static final int N = VERSION_CODES.N * SDK_INT_MULTIPLIER; + + /** + * Android 25.0. + */ + public static final int N_MR1 = VERSION_CODES.N_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 26.0. + */ + public static final int O = VERSION_CODES.O * SDK_INT_MULTIPLIER; + + /** + * Android 27.0. + */ + public static final int O_MR1 = VERSION_CODES.O_MR1 * SDK_INT_MULTIPLIER; + + /** + * Android 28.0. + */ + public static final int P = VERSION_CODES.P * SDK_INT_MULTIPLIER; + + /** + * Android 29.0. + */ + public static final int Q = VERSION_CODES.Q * SDK_INT_MULTIPLIER; + + /** + * Android 30.0. + */ + public static final int R = VERSION_CODES.R * SDK_INT_MULTIPLIER; + + /** + * Android 31.0. + */ + public static final int S = VERSION_CODES.S * SDK_INT_MULTIPLIER; + + /** + * Android 32.0. + */ + public static final int S_V2 = VERSION_CODES.S_V2 * SDK_INT_MULTIPLIER; + + /** + * Android 33.0. + */ + public static final int TIRAMISU = VERSION_CODES.TIRAMISU * SDK_INT_MULTIPLIER; + + /** + * Android 34.0. + */ + public static final int UPSIDE_DOWN_CAKE = + VERSION_CODES.UPSIDE_DOWN_CAKE * SDK_INT_MULTIPLIER; + + /** + * Android 35.0. + */ + public static final int VANILLA_ICE_CREAM = + VERSION_CODES.VANILLA_ICE_CREAM * SDK_INT_MULTIPLIER; } /** diff --git a/core/java/android/os/Bundle.java b/core/java/android/os/Bundle.java index ed4037c7d246..c18fb0c38b82 100644 --- a/core/java/android/os/Bundle.java +++ b/core/java/android/os/Bundle.java @@ -62,6 +62,12 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { @VisibleForTesting static final int FLAG_HAS_BINDERS = 1 << 12; + /** + * Indicates there may be intents with creator tokens contained in this bundle. When unparceled, + * they should be verified if tokens are missing or invalid. + */ + static final int FLAG_VERIFY_TOKENS_PRESENT = 1 << 13; + /** * Status when the Bundle can <b>assert</b> that the underlying Parcel DOES NOT contain @@ -274,6 +280,11 @@ public final class Bundle extends BaseBundle implements Cloneable, Parcelable { return orig; } + /** {@hide} */ + public void setIsIntentExtra() { + mFlags |= FLAG_VERIFY_TOKENS_PRESENT; + } + /** * Mark if this Bundle is okay to "defuse." That is, it's okay for system * processes to ignore any {@link BadParcelableException} encountered when diff --git a/core/java/android/os/CombinedVibration.java b/core/java/android/os/CombinedVibration.java index 77d6cb762e06..f1d3957cc919 100644 --- a/core/java/android/os/CombinedVibration.java +++ b/core/java/android/os/CombinedVibration.java @@ -17,6 +17,7 @@ package android.os; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.os.vibrator.Flags; import android.util.SparseArray; @@ -28,6 +29,7 @@ import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.StringJoiner; +import java.util.function.Function; /** * A CombinedVibration describes a combination of haptic effects to be performed by one or more @@ -114,6 +116,17 @@ public abstract class CombinedVibration implements Parcelable { public abstract long getDuration(); /** + * Gets the estimated duration of the combined vibration in milliseconds. + * + * <p>For effects with hardware-dependent constants (e.g. primitive compositions), this returns + * the estimated duration based on the {@link VibratorInfo}. For all other effects this will + * return the same as {@link #getDuration()}. + * + * @hide + */ + public abstract long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos); + + /** * Returns true if this effect could represent a touch haptic feedback. * * <p>It is strongly recommended that an instance of {@link VibrationAttributes} is specified @@ -383,6 +396,23 @@ public abstract class CombinedVibration implements Parcelable { /** @hide */ @Override + public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) { + if (vibratorInfos == null) { + return getDuration(); + } + long maxDuration = 0; + for (int i = 0; i < vibratorInfos.size(); i++) { + long duration = mEffect.getDuration(vibratorInfos.valueAt(i)); + if ((duration == Long.MAX_VALUE) || (duration < 0)) { + return duration; + } + maxDuration = Math.max(maxDuration, duration); + } + return maxDuration; + } + + /** @hide */ + @Override public boolean isHapticFeedbackCandidate() { return mEffect.isHapticFeedbackCandidate(); } @@ -531,10 +561,27 @@ public abstract class CombinedVibration implements Parcelable { @Override public long getDuration() { + return getDuration(idx -> mEffects.valueAt(idx).getDuration()); + } + + /** @hide */ + @Override + public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) { + if (vibratorInfos == null) { + return getDuration(); + } + return getDuration(idx -> { + VibrationEffect effect = mEffects.valueAt(idx); + VibratorInfo info = vibratorInfos.get(mEffects.keyAt(idx)); + return effect.getDuration(info); + }); + } + + private long getDuration(Function<Integer, Long> durationFn) { long maxDuration = Long.MIN_VALUE; boolean hasUnknownStep = false; for (int i = 0; i < mEffects.size(); i++) { - long duration = mEffects.valueAt(i).getDuration(); + long duration = durationFn.apply(i); if (duration == Long.MAX_VALUE) { // If any duration is repeating, this combination duration is also repeating. return duration; @@ -750,12 +797,21 @@ public abstract class CombinedVibration implements Parcelable { @Override public long getDuration() { + return getDuration(CombinedVibration::getDuration); + } + + /** @hide */ + @Override + public long getDuration(@Nullable SparseArray<VibratorInfo> vibratorInfos) { + return getDuration(effect -> effect.getDuration(vibratorInfos)); + } + + private long getDuration(Function<CombinedVibration, Long> durationFn) { boolean hasUnknownStep = false; long durations = 0; final int effectCount = mEffects.size(); for (int i = 0; i < effectCount; i++) { - CombinedVibration effect = mEffects.get(i); - long duration = effect.getDuration(); + long duration = durationFn.apply(mEffects.get(i)); if (duration == Long.MAX_VALUE) { // If any duration is repeating, this combination duration is also repeating. return duration; diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java index a55398ac9752..ef1e6c9405f3 100644 --- a/core/java/android/os/Debug.java +++ b/core/java/android/os/Debug.java @@ -114,6 +114,7 @@ public final class Debug "opengl-tracing", "view-hierarchy", "support_boot_stages", + "app_info", }; /** @@ -1016,14 +1017,14 @@ public final class Debug // send VM_START. System.out.println("Waiting for debugger first packet"); - mWaiting = true; + setWaitingForDebugger(true); while (!isDebuggerConnected()) { try { Thread.sleep(100); } catch (InterruptedException ie) { } } - mWaiting = false; + setWaitingForDebugger(false); System.out.println("Debug.suspendAllAndSentVmStart"); VMDebug.suspendAllAndSendVmStart(); @@ -1049,12 +1050,12 @@ public final class Debug Chunk waitChunk = new Chunk(ChunkHandler.type("WAIT"), data, 0, 1); DdmServer.sendChunk(waitChunk); - mWaiting = true; + setWaitingForDebugger(true); while (!isDebuggerConnected()) { try { Thread.sleep(SPIN_DELAY); } catch (InterruptedException ie) {} } - mWaiting = false; + setWaitingForDebugger(false); System.out.println("Debugger has connected"); @@ -1112,6 +1113,16 @@ public final class Debug } /** + * Set whether the app is waiting for a debugger to connect + * + * @hide + */ + private static void setWaitingForDebugger(boolean waiting) { + mWaiting = waiting; + VMDebug.setWaitingForDebugger(waiting); + } + + /** * Returns an array of strings that identify Framework features. This is * used by DDMS to determine what sorts of operations the Framework can * perform. diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 346ee7ca4f87..71d29af6cf02 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -41,6 +41,7 @@ import com.android.internal.os.SomeArgs; import com.android.internal.util.Preconditions; import com.android.sdksandbox.flags.Flags; +import dalvik.system.VMDebug; import dalvik.system.VMRuntime; import libcore.io.IoUtils; @@ -1410,6 +1411,7 @@ public class Process { public static void setArgV0(@NonNull String text) { sArgV0 = text; setArgV0Native(text); + VMDebug.setCurrentProcessName(text); } private static native void setArgV0Native(String text); diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index f82c8221e4f9..769cbdd9886d 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -16,18 +16,11 @@ package android.os; -import android.annotation.FlaggedApi; -import android.annotation.IntDef; -import android.annotation.NonNull; import android.compat.annotation.UnsupportedAppUsage; import android.util.ArrayMap; import android.util.Slog; import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.Queue; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -37,7 +30,7 @@ import java.util.function.Consumer; * {@link android.app.Service} to its clients. In particular, this: * * <ul> - * <li> Keeps track of a set of registered {@link IInterface} objects, + * <li> Keeps track of a set of registered {@link IInterface} callbacks, * taking care to identify them through their underlying unique {@link IBinder} * (by calling {@link IInterface#asBinder IInterface.asBinder()}. * <li> Attaches a {@link IBinder.DeathRecipient IBinder.DeathRecipient} to @@ -54,7 +47,7 @@ import java.util.function.Consumer; * the registered clients, use {@link #beginBroadcast}, * {@link #getBroadcastItem}, and {@link #finishBroadcast}. * - * <p>If a registered interface's process goes away, this class will take + * <p>If a registered callback's process goes away, this class will take * care of automatically removing it from the list. If you want to do * additional work in this situation, you can create a subclass that * implements the {@link #onCallbackDied} method. @@ -63,310 +56,78 @@ import java.util.function.Consumer; public class RemoteCallbackList<E extends IInterface> { private static final String TAG = "RemoteCallbackList"; - private static final int DEFAULT_MAX_QUEUE_SIZE = 1000; - - - /** - * @hide - */ - @IntDef(prefix = {"FROZEN_CALLEE_POLICY_"}, value = { - FROZEN_CALLEE_POLICY_UNSET, - FROZEN_CALLEE_POLICY_ENQUEUE_ALL, - FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT, - FROZEN_CALLEE_POLICY_DROP, - }) - @Retention(RetentionPolicy.SOURCE) - @interface FrozenCalleePolicy { - } - - /** - * Callbacks are invoked immediately regardless of the frozen state of the target process. - * - * Not recommended. Only exists for backward-compatibility. This represents the behavior up to - * SDK 35. Starting with SDK 36, clients should set a policy to govern callback invocations when - * recipients are frozen. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final int FROZEN_CALLEE_POLICY_UNSET = 0; - - /** - * When the callback recipient's process is frozen, callbacks are enqueued so they're invoked - * after the recipient is unfrozen. - * - * This is commonly used when the recipient wants to receive all callbacks without losing any - * history, e.g. the recipient maintains a running count of events that occurred. - * - * Queued callbacks are invoked in the order they were originally broadcasted. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final int FROZEN_CALLEE_POLICY_ENQUEUE_ALL = 1; - - /** - * When the callback recipient's process is frozen, only the most recent callback is enqueued, - * which is later invoked after the recipient is unfrozen. - * - * This can be used when only the most recent state matters, for instance when clients are - * listening to screen brightness changes. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final int FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT = 2; - - /** - * When the callback recipient's process is frozen, callbacks are suppressed as if they never - * happened. - * - * This could be useful in the case where the recipient wishes to react to callbacks only when - * they occur while the recipient is not frozen. For example, certain network events are only - * worth responding to if the response can be immediate. Another example is recipients having - * another way of getting the latest state once it's unfrozen. Therefore there is no need to - * save callbacks that happened while the recipient was frozen. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final int FROZEN_CALLEE_POLICY_DROP = 3; - @UnsupportedAppUsage - /*package*/ ArrayMap<IBinder, Interface> mInterfaces = new ArrayMap<IBinder, Interface>(); + /*package*/ ArrayMap<IBinder, Callback> mCallbacks + = new ArrayMap<IBinder, Callback>(); private Object[] mActiveBroadcast; private int mBroadcastCount = -1; private boolean mKilled = false; private StringBuilder mRecentCallers; - private final @FrozenCalleePolicy int mFrozenCalleePolicy; - private final int mMaxQueueSize; - - private final class Interface implements IBinder.DeathRecipient, - IBinder.FrozenStateChangeCallback { - final IBinder mBinder; - final E mInterface; + private final class Callback implements IBinder.DeathRecipient { + final E mCallback; final Object mCookie; - final Queue<Consumer<E>> mCallbackQueue; - int mCurrentState = IBinder.FrozenStateChangeCallback.STATE_UNFROZEN; - Interface(E callbackInterface, Object cookie) { - mBinder = callbackInterface.asBinder(); - mInterface = callbackInterface; + Callback(E callback, Object cookie) { + mCallback = callback; mCookie = cookie; - mCallbackQueue = mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_ENQUEUE_ALL - || mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT - ? new ConcurrentLinkedQueue<>() : null; - } - - @Override - public synchronized void onFrozenStateChanged(@NonNull IBinder who, int state) { - if (state == STATE_UNFROZEN && mCallbackQueue != null) { - while (!mCallbackQueue.isEmpty()) { - Consumer<E> callback = mCallbackQueue.poll(); - callback.accept(mInterface); - } - } - mCurrentState = state; - } - - void addCallback(@NonNull Consumer<E> callback) { - if (mFrozenCalleePolicy == FROZEN_CALLEE_POLICY_UNSET) { - callback.accept(mInterface); - return; - } - synchronized (this) { - if (mCurrentState == STATE_UNFROZEN) { - callback.accept(mInterface); - return; - } - switch (mFrozenCalleePolicy) { - case FROZEN_CALLEE_POLICY_ENQUEUE_ALL: - if (mCallbackQueue.size() >= mMaxQueueSize) { - mCallbackQueue.poll(); - } - mCallbackQueue.offer(callback); - break; - case FROZEN_CALLEE_POLICY_ENQUEUE_MOST_RECENT: - mCallbackQueue.clear(); - mCallbackQueue.offer(callback); - break; - case FROZEN_CALLEE_POLICY_DROP: - // Do nothing. Just ignore the callback. - break; - case FROZEN_CALLEE_POLICY_UNSET: - // Do nothing. Should have returned at the start of the method. - break; - } - } - } - - public void maybeSubscribeToFrozenCallback() throws RemoteException { - if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { - mBinder.addFrozenStateChangeCallback(this); - } - } - - public void maybeUnsubscribeFromFrozenCallback() { - if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { - mBinder.removeFrozenStateChangeCallback(this); - } } public void binderDied() { - synchronized (mInterfaces) { - mInterfaces.remove(mBinder); - maybeUnsubscribeFromFrozenCallback(); - } - onCallbackDied(mInterface, mCookie); - } - } - - /** - * Builder for {@link RemoteCallbackList}. - * - * @param <E> The remote callback interface type. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public static final class Builder<E extends IInterface> { - private @FrozenCalleePolicy int mFrozenCalleePolicy; - private int mMaxQueueSize = DEFAULT_MAX_QUEUE_SIZE; - - /** - * Creates a Builder for {@link RemoteCallbackList}. - * - * @param frozenCalleePolicy When the callback recipient's process is frozen, this parameter - * specifies when/whether callbacks are invoked. It's important to choose a strategy that's - * right for the use case. Leaving the policy unset with {@link #FROZEN_CALLEE_POLICY_UNSET} - * is not recommended as it allows callbacks to be invoked while the recipient is frozen. - */ - public Builder(@FrozenCalleePolicy int frozenCalleePolicy) { - mFrozenCalleePolicy = frozenCalleePolicy; - } - - /** - * Sets the max queue size. - * - * @param maxQueueSize The max size limit on the queue that stores callbacks added when the - * recipient's process is frozen. Once the limit is reached, the oldest callback is dropped - * to keep the size under the limit. Should only be called for - * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}. - * - * @return This builder. - * @throws IllegalArgumentException if the maxQueueSize is not positive. - * @throws UnsupportedOperationException if frozenCalleePolicy is not - * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}. - */ - public @NonNull Builder setMaxQueueSize(int maxQueueSize) { - if (maxQueueSize <= 0) { - throw new IllegalArgumentException("maxQueueSize must be positive"); - } - if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_ENQUEUE_ALL) { - throw new UnsupportedOperationException( - "setMaxQueueSize can only be called for FROZEN_CALLEE_POLICY_ENQUEUE_ALL"); + synchronized (mCallbacks) { + mCallbacks.remove(mCallback.asBinder()); } - mMaxQueueSize = maxQueueSize; - return this; - } - - /** - * Builds and returns a {@link RemoteCallbackList}. - * - * @return The built {@link RemoteCallbackList} object. - */ - public @NonNull RemoteCallbackList<E> build() { - return new RemoteCallbackList<E>(mFrozenCalleePolicy, mMaxQueueSize); + onCallbackDied(mCallback, mCookie); } } /** - * Returns the frozen callee policy. - * - * @return The frozen callee policy. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public @FrozenCalleePolicy int getFrozenCalleePolicy() { - return mFrozenCalleePolicy; - } - - /** - * Returns the max queue size. - * - * @return The max queue size. - */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public int getMaxQueueSize() { - return mMaxQueueSize; - } - - /** - * Creates a RemoteCallbackList with {@link #FROZEN_CALLEE_POLICY_UNSET}. This is equivalent to - * <pre> - * new RemoteCallbackList.Build(RemoteCallbackList.FROZEN_CALLEE_POLICY_UNSET).build() - * </pre> - */ - public RemoteCallbackList() { - this(FROZEN_CALLEE_POLICY_UNSET, DEFAULT_MAX_QUEUE_SIZE); - } - - /** - * Creates a RemoteCallbackList with the specified frozen callee policy. - * - * @param frozenCalleePolicy When the callback recipient's process is frozen, this parameter - * specifies when/whether callbacks are invoked. It's important to choose a strategy that's - * right for the use case. Leaving the policy unset with {@link #FROZEN_CALLEE_POLICY_UNSET} - * is not recommended as it allows callbacks to be invoked while the recipient is frozen. - * - * @param maxQueueSize The max size limit on the queue that stores callbacks added when the - * recipient's process is frozen. Once the limit is reached, the oldest callbacks would be - * dropped to keep the size under limit. Ignored except for - * {@link #FROZEN_CALLEE_POLICY_ENQUEUE_ALL}. - */ - private RemoteCallbackList(@FrozenCalleePolicy int frozenCalleePolicy, int maxQueueSize) { - mFrozenCalleePolicy = frozenCalleePolicy; - mMaxQueueSize = maxQueueSize; - } - - /** * Simple version of {@link RemoteCallbackList#register(E, Object)} * that does not take a cookie object. */ - public boolean register(E callbackInterface) { - return register(callbackInterface, null); + public boolean register(E callback) { + return register(callback, null); } /** - * Add a new interface to the list. This interface will remain in the list + * Add a new callback to the list. This callback will remain in the list * until a corresponding call to {@link #unregister} or its hosting process - * goes away. If the interface was already registered (determined by - * checking to see if the {@link IInterface#asBinder callbackInterface.asBinder()} - * object is already in the list), then it will be replaced with the new interface. + * goes away. If the callback was already registered (determined by + * checking to see if the {@link IInterface#asBinder callback.asBinder()} + * object is already in the list), then it will be replaced with the new callback. * Registrations are not counted; a single call to {@link #unregister} - * will remove an interface after any number calls to register it. + * will remove a callback after any number calls to register it. * - * @param callbackInterface The callback interface to be added to the list. Must + * @param callback The callback interface to be added to the list. Must * not be null -- passing null here will cause a NullPointerException. * Most services will want to check for null before calling this with * an object given from a client, so that clients can't crash the * service with bad data. * * @param cookie Optional additional data to be associated with this - * interface. + * callback. * - * @return Returns true if the interface was successfully added to the list. + * @return Returns true if the callback was successfully added to the list. * Returns false if it was not added, either because {@link #kill} had - * previously been called or the interface's process has gone away. + * previously been called or the callback's process has gone away. * * @see #unregister * @see #kill * @see #onCallbackDied */ - public boolean register(E callbackInterface, Object cookie) { - synchronized (mInterfaces) { + public boolean register(E callback, Object cookie) { + synchronized (mCallbacks) { if (mKilled) { return false; } // Flag unusual case that could be caused by a leak. b/36778087 - logExcessiveInterfaces(); - IBinder binder = callbackInterface.asBinder(); + logExcessiveCallbacks(); + IBinder binder = callback.asBinder(); try { - Interface i = new Interface(callbackInterface, cookie); - unregister(callbackInterface); - binder.linkToDeath(i, 0); - i.maybeSubscribeToFrozenCallback(); - mInterfaces.put(binder, i); + Callback cb = new Callback(callback, cookie); + unregister(callback); + binder.linkToDeath(cb, 0); + mCallbacks.put(binder, cb); return true; } catch (RemoteException e) { return false; @@ -375,28 +136,27 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Remove from the list an interface that was previously added with + * Remove from the list a callback that was previously added with * {@link #register}. This uses the - * {@link IInterface#asBinder callbackInterface.asBinder()} object to correctly + * {@link IInterface#asBinder callback.asBinder()} object to correctly * find the previous registration. * Registrations are not counted; a single unregister call will remove - * an interface after any number calls to {@link #register} for it. + * a callback after any number calls to {@link #register} for it. * - * @param callbackInterface The interface to be removed from the list. Passing + * @param callback The callback to be removed from the list. Passing * null here will cause a NullPointerException, so you will generally want * to check for null before calling. * - * @return Returns true if the interface was found and unregistered. Returns - * false if the given interface was not found on the list. + * @return Returns true if the callback was found and unregistered. Returns + * false if the given callback was not found on the list. * * @see #register */ - public boolean unregister(E callbackInterface) { - synchronized (mInterfaces) { - Interface i = mInterfaces.remove(callbackInterface.asBinder()); - if (i != null) { - i.mInterface.asBinder().unlinkToDeath(i, 0); - i.maybeUnsubscribeFromFrozenCallback(); + public boolean unregister(E callback) { + synchronized (mCallbacks) { + Callback cb = mCallbacks.remove(callback.asBinder()); + if (cb != null) { + cb.mCallback.asBinder().unlinkToDeath(cb, 0); return true; } return false; @@ -404,21 +164,20 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Disable this interface list. All registered interfaces are unregistered, + * Disable this callback list. All registered callbacks are unregistered, * and the list is disabled so that future calls to {@link #register} will * fail. This should be used when a Service is stopping, to prevent clients - * from registering interfaces after it is stopped. + * from registering callbacks after it is stopped. * * @see #register */ public void kill() { - synchronized (mInterfaces) { - for (int cbi = mInterfaces.size() - 1; cbi >= 0; cbi--) { - Interface i = mInterfaces.valueAt(cbi); - i.mInterface.asBinder().unlinkToDeath(i, 0); - i.maybeUnsubscribeFromFrozenCallback(); + synchronized (mCallbacks) { + for (int cbi=mCallbacks.size()-1; cbi>=0; cbi--) { + Callback cb = mCallbacks.valueAt(cbi); + cb.mCallback.asBinder().unlinkToDeath(cb, 0); } - mInterfaces.clear(); + mCallbacks.clear(); mKilled = true; } } @@ -427,15 +186,15 @@ public class RemoteCallbackList<E extends IInterface> { * Old version of {@link #onCallbackDied(E, Object)} that * does not provide a cookie. */ - public void onCallbackDied(E callbackInterface) { + public void onCallbackDied(E callback) { } /** - * Called when the process hosting an interface in the list has gone away. + * Called when the process hosting a callback in the list has gone away. * The default implementation calls {@link #onCallbackDied(E)} * for backwards compatibility. * - * @param callbackInterface The interface whose process has died. Note that, since + * @param callback The callback whose process has died. Note that, since * its process has died, you can not make any calls on to this interface. * You can, however, retrieve its IBinder and compare it with another * IBinder to see if it is the same object. @@ -444,15 +203,13 @@ public class RemoteCallbackList<E extends IInterface> { * * @see #register */ - public void onCallbackDied(E callbackInterface, Object cookie) { - onCallbackDied(callbackInterface); + public void onCallbackDied(E callback, Object cookie) { + onCallbackDied(callback); } /** - * Use {@link #broadcast(Consumer)} instead to ensure proper handling of frozen processes. - * - * Prepare to start making calls to the currently registered interfaces. - * This creates a copy of the interface list, which you can retrieve items + * Prepare to start making calls to the currently registered callbacks. + * This creates a copy of the callback list, which you can retrieve items * from using {@link #getBroadcastItem}. Note that only one broadcast can * be active at a time, so you must be sure to always call this from the * same thread (usually by scheduling with {@link Handler}) or @@ -462,56 +219,44 @@ public class RemoteCallbackList<E extends IInterface> { * <p>A typical loop delivering a broadcast looks like this: * * <pre> - * int i = interfaces.beginBroadcast(); + * int i = callbacks.beginBroadcast(); * while (i > 0) { * i--; * try { - * interfaces.getBroadcastItem(i).somethingHappened(); + * callbacks.getBroadcastItem(i).somethingHappened(); * } catch (RemoteException e) { * // The RemoteCallbackList will take care of removing * // the dead object for us. * } * } - * interfaces.finishBroadcast();</pre> + * callbacks.finishBroadcast();</pre> * - * Note that this method is only supported for {@link #FROZEN_CALLEE_POLICY_UNSET}. For other - * policies use {@link #broadcast(Consumer)} instead. - * - * @return Returns the number of interfaces in the broadcast, to be used + * @return Returns the number of callbacks in the broadcast, to be used * with {@link #getBroadcastItem} to determine the range of indices you * can supply. * - * @throws UnsupportedOperationException if an frozen callee policy is set. - * * @see #getBroadcastItem * @see #finishBroadcast */ public int beginBroadcast() { - if (mFrozenCalleePolicy != FROZEN_CALLEE_POLICY_UNSET) { - throw new UnsupportedOperationException(); - } - return beginBroadcastInternal(); - } - - private int beginBroadcastInternal() { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mBroadcastCount > 0) { throw new IllegalStateException( "beginBroadcast() called while already in a broadcast"); } - final int n = mBroadcastCount = mInterfaces.size(); - if (n <= 0) { + final int N = mBroadcastCount = mCallbacks.size(); + if (N <= 0) { return 0; } Object[] active = mActiveBroadcast; - if (active == null || active.length < n) { - mActiveBroadcast = active = new Object[n]; + if (active == null || active.length < N) { + mActiveBroadcast = active = new Object[N]; } - for (int i = 0; i < n; i++) { - active[i] = mInterfaces.valueAt(i); + for (int i=0; i<N; i++) { + active[i] = mCallbacks.valueAt(i); } - return n; + return N; } } @@ -522,23 +267,24 @@ public class RemoteCallbackList<E extends IInterface> { * calling {@link #finishBroadcast}. * * <p>Note that it is possible for the process of one of the returned - * interfaces to go away before you call it, so you will need to catch + * callbacks to go away before you call it, so you will need to catch * {@link RemoteException} when calling on to the returned object. - * The interface list itself, however, will take care of unregistering + * The callback list itself, however, will take care of unregistering * these objects once it detects that it is no longer valid, so you can * handle such an exception by simply ignoring it. * - * @param index Which of the registered interfaces you would like to + * @param index Which of the registered callbacks you would like to * retrieve. Ranges from 0 to {@link #beginBroadcast}-1, inclusive. * - * @return Returns the interface that you can call. This will always be non-null. + * @return Returns the callback interface that you can call. This will + * always be non-null. * * @see #beginBroadcast */ public E getBroadcastItem(int index) { - return ((Interface) mActiveBroadcast[index]).mInterface; + return ((Callback)mActiveBroadcast[index]).mCallback; } - + /** * Retrieve the cookie associated with the item * returned by {@link #getBroadcastItem(int)}. @@ -546,7 +292,7 @@ public class RemoteCallbackList<E extends IInterface> { * @see #getBroadcastItem */ public Object getBroadcastCookie(int index) { - return ((Interface) mActiveBroadcast[index]).mCookie; + return ((Callback)mActiveBroadcast[index]).mCookie; } /** @@ -557,7 +303,7 @@ public class RemoteCallbackList<E extends IInterface> { * @see #beginBroadcast */ public void finishBroadcast() { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mBroadcastCount < 0) { throw new IllegalStateException( "finishBroadcast() called outside of a broadcast"); @@ -576,18 +322,16 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Performs {@code callback} on each registered interface. + * Performs {@code action} on each callback, calling + * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping * - * This is equivalent to #beginBroadcast, followed by iterating over the items using - * #getBroadcastItem and then @finishBroadcast, except that this method supports - * frozen callee policies. + * @hide */ - @FlaggedApi(Flags.FLAG_BINDER_FROZEN_STATE_CHANGE_CALLBACK) - public void broadcast(@NonNull Consumer<E> callback) { - int itemCount = beginBroadcastInternal(); + public void broadcast(Consumer<E> action) { + int itemCount = beginBroadcast(); try { for (int i = 0; i < itemCount; i++) { - ((Interface) mActiveBroadcast[i]).addCallback(callback); + action.accept(getBroadcastItem(i)); } } finally { finishBroadcast(); @@ -595,16 +339,16 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Performs {@code callback} for each cookie associated with an interface, calling + * Performs {@code action} for each cookie associated with a callback, calling * {@link #beginBroadcast()}/{@link #finishBroadcast()} before/after looping * * @hide */ - public <C> void broadcastForEachCookie(Consumer<C> callback) { + public <C> void broadcastForEachCookie(Consumer<C> action) { int itemCount = beginBroadcast(); try { for (int i = 0; i < itemCount; i++) { - callback.accept((C) getBroadcastCookie(i)); + action.accept((C) getBroadcastCookie(i)); } } finally { finishBroadcast(); @@ -612,16 +356,16 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Performs {@code callback} on each interface and associated cookie, calling {@link + * Performs {@code action} on each callback and associated cookie, calling {@link * #beginBroadcast()}/{@link #finishBroadcast()} before/after looping. * * @hide */ - public <C> void broadcast(BiConsumer<E, C> callback) { + public <C> void broadcast(BiConsumer<E, C> action) { int itemCount = beginBroadcast(); try { for (int i = 0; i < itemCount; i++) { - callback.accept(getBroadcastItem(i), (C) getBroadcastCookie(i)); + action.accept(getBroadcastItem(i), (C) getBroadcastCookie(i)); } } finally { finishBroadcast(); @@ -629,10 +373,10 @@ public class RemoteCallbackList<E extends IInterface> { } /** - * Returns the number of registered interfaces. Note that the number of registered - * interfaces may differ from the value returned by {@link #beginBroadcast()} since - * the former returns the number of interfaces registered at the time of the call - * and the second the number of interfaces to which the broadcast will be delivered. + * Returns the number of registered callbacks. Note that the number of registered + * callbacks may differ from the value returned by {@link #beginBroadcast()} since + * the former returns the number of callbacks registered at the time of the call + * and the second the number of callback to which the broadcast will be delivered. * <p> * This function is useful to decide whether to schedule a broadcast if this * requires doing some work which otherwise would not be performed. @@ -641,39 +385,39 @@ public class RemoteCallbackList<E extends IInterface> { * @return The size. */ public int getRegisteredCallbackCount() { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mKilled) { return 0; } - return mInterfaces.size(); + return mCallbacks.size(); } } /** - * Return a currently registered interface. Note that this is + * Return a currently registered callback. Note that this is * <em>not</em> the same as {@link #getBroadcastItem} and should not be used - * interchangeably with it. This method returns the registered interface at the given + * interchangeably with it. This method returns the registered callback at the given * index, not the current broadcast state. This means that it is not itself thread-safe: * any call to {@link #register} or {@link #unregister} will change these indices, so you * must do your own thread safety between these to protect from such changes. * - * @param index Index of which interface registration to return, from 0 to + * @param index Index of which callback registration to return, from 0 to * {@link #getRegisteredCallbackCount()} - 1. * - * @return Returns whatever interface is associated with this index, or null if + * @return Returns whatever callback is associated with this index, or null if * {@link #kill()} has been called. */ public E getRegisteredCallbackItem(int index) { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mKilled) { return null; } - return mInterfaces.valueAt(index).mInterface; + return mCallbacks.valueAt(index).mCallback; } } /** - * Return any cookie associated with a currently registered interface. Note that this is + * Return any cookie associated with a currently registered callback. Note that this is * <em>not</em> the same as {@link #getBroadcastCookie} and should not be used * interchangeably with it. This method returns the current cookie registered at the given * index, not the current broadcast state. This means that it is not itself thread-safe: @@ -687,25 +431,25 @@ public class RemoteCallbackList<E extends IInterface> { * {@link #kill()} has been called. */ public Object getRegisteredCallbackCookie(int index) { - synchronized (mInterfaces) { + synchronized (mCallbacks) { if (mKilled) { return null; } - return mInterfaces.valueAt(index).mCookie; + return mCallbacks.valueAt(index).mCookie; } } /** @hide */ public void dump(PrintWriter pw, String prefix) { - synchronized (mInterfaces) { - pw.print(prefix); pw.print("callbacks: "); pw.println(mInterfaces.size()); + synchronized (mCallbacks) { + pw.print(prefix); pw.print("callbacks: "); pw.println(mCallbacks.size()); pw.print(prefix); pw.print("killed: "); pw.println(mKilled); pw.print(prefix); pw.print("broadcasts count: "); pw.println(mBroadcastCount); } } - private void logExcessiveInterfaces() { - final long size = mInterfaces.size(); + private void logExcessiveCallbacks() { + final long size = mCallbacks.size(); final long TOO_MANY = 3000; final long MAX_CHARS = 1000; if (size >= TOO_MANY) { diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java index ffc58c537f2a..61dd11fd4122 100644 --- a/core/java/android/os/VibrationEffect.java +++ b/core/java/android/os/VibrationEffect.java @@ -55,6 +55,7 @@ import java.util.Locale; import java.util.Objects; import java.util.StringJoiner; import java.util.function.BiFunction; +import java.util.function.Function; /** * A VibrationEffect describes a haptic effect to be performed by a {@link Vibrator}. @@ -565,6 +566,19 @@ public abstract class VibrationEffect implements Parcelable { public abstract long getDuration(); /** + * Gets the estimated duration of the segment for given vibrator, in milliseconds. + * + * <p>For effects with hardware-dependent constants (e.g. primitive compositions), this returns + * the estimated duration based on the given {@link VibratorInfo}. For all other effects this + * will return the same as {@link #getDuration()}. + * + * @hide + */ + public long getDuration(@Nullable VibratorInfo vibratorInfo) { + return getDuration(); + } + + /** * Checks if a vibrator with a given {@link VibratorInfo} can play this effect as intended. * * <p>See {@link VibratorInfo#areVibrationFeaturesSupported(VibrationEffect)} for more @@ -904,13 +918,23 @@ public abstract class VibrationEffect implements Parcelable { @Override public long getDuration() { + return getDuration(VibrationEffectSegment::getDuration); + } + + /** @hide */ + @Override + public long getDuration(@Nullable VibratorInfo vibratorInfo) { + return getDuration(segment -> segment.getDuration(vibratorInfo)); + } + + private long getDuration(Function<VibrationEffectSegment, Long> durationFn) { if (mRepeatIndex >= 0) { return Long.MAX_VALUE; } int segmentCount = mSegments.size(); long totalDuration = 0; for (int i = 0; i < segmentCount; i++) { - long segmentDuration = mSegments.get(i).getDuration(); + long segmentDuration = durationFn.apply(mSegments.get(i)); if (segmentDuration < 0) { return segmentDuration; } diff --git a/core/java/android/os/vibrator/PrebakedSegment.java b/core/java/android/os/vibrator/PrebakedSegment.java index 39f841226e4e..b17e82a704bf 100644 --- a/core/java/android/os/vibrator/PrebakedSegment.java +++ b/core/java/android/os/vibrator/PrebakedSegment.java @@ -16,6 +16,17 @@ package android.os.vibrator; +import static android.os.VibrationEffect.Composition.PRIMITIVE_CLICK; +import static android.os.VibrationEffect.Composition.PRIMITIVE_THUD; +import static android.os.VibrationEffect.Composition.PRIMITIVE_TICK; +import static android.os.VibrationEffect.EFFECT_CLICK; +import static android.os.VibrationEffect.EFFECT_DOUBLE_CLICK; +import static android.os.VibrationEffect.EFFECT_HEAVY_CLICK; +import static android.os.VibrationEffect.EFFECT_POP; +import static android.os.VibrationEffect.EFFECT_TEXTURE_TICK; +import static android.os.VibrationEffect.EFFECT_THUD; +import static android.os.VibrationEffect.EFFECT_TICK; + import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.TestApi; @@ -78,6 +89,32 @@ public final class PrebakedSegment extends VibrationEffectSegment { /** @hide */ @Override + public long getDuration(@Nullable VibratorInfo vibratorInfo) { + if (vibratorInfo == null) { + return getDuration(); + } + return switch (mEffectId) { + case EFFECT_TICK, + EFFECT_CLICK, + EFFECT_HEAVY_CLICK -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_CLICK); + case EFFECT_TEXTURE_TICK -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_TICK); + case EFFECT_THUD -> estimateFromPrimitiveDuration(vibratorInfo, PRIMITIVE_THUD); + case EFFECT_DOUBLE_CLICK -> { + long clickDuration = vibratorInfo.getPrimitiveDuration(PRIMITIVE_CLICK); + yield clickDuration > 0 ? 2 * clickDuration : getDuration(); + } + default -> getDuration(); + }; + } + + private long estimateFromPrimitiveDuration(VibratorInfo vibratorInfo, int primitiveId) { + int duration = vibratorInfo.getPrimitiveDuration(primitiveId); + // Unsupported primitives should be ignored here. + return duration > 0 ? duration : getDuration(); + } + + /** @hide */ + @Override public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) { if (vibratorInfo.isEffectSupported(mEffectId) == Vibrator.VIBRATION_EFFECT_SUPPORT_YES) { return true; @@ -89,34 +126,30 @@ public final class PrebakedSegment extends VibrationEffectSegment { } // The vibrator does not have hardware support for the effect, but the effect has fallback // support. Check if a fallback will be available for the effect ID. - switch (mEffectId) { - case VibrationEffect.EFFECT_CLICK: - case VibrationEffect.EFFECT_DOUBLE_CLICK: - case VibrationEffect.EFFECT_HEAVY_CLICK: - case VibrationEffect.EFFECT_TICK: - // Any of these effects are always supported via some form of fallback. - return true; - default: - return false; - } + return switch (mEffectId) { + // Any of these effects are always supported via some form of fallback. + case EFFECT_CLICK, + EFFECT_DOUBLE_CLICK, + EFFECT_HEAVY_CLICK, + EFFECT_TICK -> true; + default -> false; + }; } /** @hide */ @Override public boolean isHapticFeedbackCandidate() { - switch (mEffectId) { - case VibrationEffect.EFFECT_CLICK: - case VibrationEffect.EFFECT_DOUBLE_CLICK: - case VibrationEffect.EFFECT_HEAVY_CLICK: - case VibrationEffect.EFFECT_POP: - case VibrationEffect.EFFECT_TEXTURE_TICK: - case VibrationEffect.EFFECT_THUD: - case VibrationEffect.EFFECT_TICK: - return true; - default: - // VibrationEffect.RINGTONES are not segments that could represent a haptic feedback - return false; - } + return switch (mEffectId) { + case EFFECT_CLICK, + EFFECT_DOUBLE_CLICK, + EFFECT_HEAVY_CLICK, + EFFECT_POP, + EFFECT_TEXTURE_TICK, + EFFECT_THUD, + EFFECT_TICK -> true; + // VibrationEffect.RINGTONES are not segments that could represent a haptic feedback + default -> false; + }; } /** @hide */ @@ -153,27 +186,25 @@ public final class PrebakedSegment extends VibrationEffectSegment { } private static boolean isValidEffectStrength(int strength) { - switch (strength) { - case VibrationEffect.EFFECT_STRENGTH_LIGHT: - case VibrationEffect.EFFECT_STRENGTH_MEDIUM: - case VibrationEffect.EFFECT_STRENGTH_STRONG: - return true; - default: - return false; - } + return switch (strength) { + case VibrationEffect.EFFECT_STRENGTH_LIGHT, + VibrationEffect.EFFECT_STRENGTH_MEDIUM, + VibrationEffect.EFFECT_STRENGTH_STRONG -> true; + default -> false; + }; } /** @hide */ @Override public void validate() { switch (mEffectId) { - case VibrationEffect.EFFECT_CLICK: - case VibrationEffect.EFFECT_DOUBLE_CLICK: - case VibrationEffect.EFFECT_HEAVY_CLICK: - case VibrationEffect.EFFECT_POP: - case VibrationEffect.EFFECT_TEXTURE_TICK: - case VibrationEffect.EFFECT_THUD: - case VibrationEffect.EFFECT_TICK: + case EFFECT_CLICK: + case EFFECT_DOUBLE_CLICK: + case EFFECT_HEAVY_CLICK: + case EFFECT_POP: + case EFFECT_TEXTURE_TICK: + case EFFECT_THUD: + case EFFECT_TICK: break; default: int[] ringtones = VibrationEffect.RINGTONES; diff --git a/core/java/android/os/vibrator/PrimitiveSegment.java b/core/java/android/os/vibrator/PrimitiveSegment.java index 3c84bcda639b..91653edd1ba5 100644 --- a/core/java/android/os/vibrator/PrimitiveSegment.java +++ b/core/java/android/os/vibrator/PrimitiveSegment.java @@ -77,6 +77,16 @@ public final class PrimitiveSegment extends VibrationEffectSegment { /** @hide */ @Override + public long getDuration(@Nullable VibratorInfo vibratorInfo) { + if (vibratorInfo == null) { + return getDuration(); + } + int duration = vibratorInfo.getPrimitiveDuration(mPrimitiveId); + return duration > 0 ? duration + mDelay : getDuration(); + } + + /** @hide */ + @Override public boolean areVibrationFeaturesSupported(@NonNull VibratorInfo vibratorInfo) { return vibratorInfo.isPrimitiveSupported(mPrimitiveId); } diff --git a/core/java/android/os/vibrator/VibrationConfig.java b/core/java/android/os/vibrator/VibrationConfig.java index e6e5a27bd731..88be96a4aef3 100644 --- a/core/java/android/os/vibrator/VibrationConfig.java +++ b/core/java/android/os/vibrator/VibrationConfig.java @@ -86,6 +86,7 @@ public class VibrationConfig { private final int mDefaultKeyboardVibrationIntensity; private final boolean mKeyboardVibrationSettingsSupported; + private final int mVibrationPipelineMaxDurationMs; /** @hide */ public VibrationConfig(@Nullable Resources resources) { @@ -106,6 +107,8 @@ public class VibrationConfig { com.android.internal.R.bool.config_ignoreVibrationsOnWirelessCharger); mKeyboardVibrationSettingsSupported = loadBoolean(resources, com.android.internal.R.bool.config_keyboardVibrationSettingsSupported); + mVibrationPipelineMaxDurationMs = loadInteger(resources, + com.android.internal.R.integer.config_vibrationPipelineMaxDuration, 0); mDefaultAlarmVibrationIntensity = loadDefaultIntensity(resources, com.android.internal.R.integer.config_defaultAlarmVibrationIntensity); @@ -221,6 +224,23 @@ public class VibrationConfig { } /** + * The max duration, in milliseconds, allowed for pipelining vibration requests. + * + * <p>If the ongoing vibration duration is shorter than this threshold then it should be allowed + * to finish before the next vibration can start. If the ongoing vibration is longer than this + * then it should be cancelled when it's superseded for the new one. + * + * @return the max duration allowed for vibration effect to finish before the next request, or + * zero to disable effect pipelining. + */ + public int getVibrationPipelineMaxDurationMs() { + if (mVibrationPipelineMaxDurationMs < 0) { + return 0; + } + return mVibrationPipelineMaxDurationMs; + } + + /** * Whether or not vibrations are ignored if the device is on a wireless charger. * * <p>This may be the case if vibration during wireless charging causes unwanted results, like diff --git a/core/java/android/os/vibrator/VibrationEffectSegment.java b/core/java/android/os/vibrator/VibrationEffectSegment.java index e1fb4e361008..dadc849dae0a 100644 --- a/core/java/android/os/vibrator/VibrationEffectSegment.java +++ b/core/java/android/os/vibrator/VibrationEffectSegment.java @@ -17,6 +17,7 @@ package android.os.vibrator; import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.os.Parcel; import android.os.Parcelable; @@ -58,10 +59,23 @@ public abstract class VibrationEffectSegment implements Parcelable { */ public abstract long getDuration(); - /** - * Checks if a given {@link Vibrator} can play this segment as intended. See - * {@link Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more information about - * what counts as supported by a vibrator, and what counts as not. + /** + * Gets the estimated duration of the segment for given vibrator, in milliseconds. + * + * <p>For segments with hardware-dependent constants (e.g. primitives), this returns the + * estimated duration based on the given {@link VibratorInfo}. For all other effects this will + * return the same as {@link #getDuration()}. + * + * @hide + */ + public long getDuration(@Nullable VibratorInfo vibratorInfo) { + return getDuration(); + } + + /** + * Checks if a given {@link android.os.Vibrator} can play this segment as intended. See + * {@link android.os.Vibrator#areVibrationFeaturesSupported(VibrationEffect)} for more + * information about what counts as supported by a vibrator, and what counts as not. * * @hide */ diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig index e3b1221b3004..7ceb948945fd 100644 --- a/core/java/android/os/vibrator/flags.aconfig +++ b/core/java/android/os/vibrator/flags.aconfig @@ -123,4 +123,25 @@ flag { metadata { purpose: PURPOSE_FEATURE } -}
\ No newline at end of file +} + +flag { + namespace: "haptics" + name: "primitive_composition_absolute_delay" + is_exported: true + description: "Enables functionality to create primitive compositions with absolute delays" + bug: "373357740" + metadata { + purpose: PURPOSE_FEATURE + } +} + +flag { + namespace: "haptics" + name: "vibration_pipeline_enabled" + description: "Enables functionality to pipeline vibration effects to avoid cancelling short vibrations" + bug: "344494220" + metadata { + purpose: PURPOSE_FEATURE + } +} diff --git a/core/java/android/security/forensic/IForensicService.aidl b/core/java/android/security/forensic/IForensicService.aidl new file mode 100644 index 000000000000..a944b18cb26d --- /dev/null +++ b/core/java/android/security/forensic/IForensicService.aidl @@ -0,0 +1,32 @@ +/* + * Copyright 2024 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.security.forensic; + +import android.security.forensic.IForensicServiceCommandCallback; +import android.security.forensic.IForensicServiceStateCallback; + +/** + * Binder interface to communicate with ForensicService. + * @hide + */ +interface IForensicService { + void monitorState(IForensicServiceStateCallback callback); + void makeVisible(IForensicServiceCommandCallback callback); + void makeInvisible(IForensicServiceCommandCallback callback); + void enable(IForensicServiceCommandCallback callback); + void disable(IForensicServiceCommandCallback callback); +} diff --git a/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl new file mode 100644 index 000000000000..7fa0c7f72690 --- /dev/null +++ b/core/java/android/security/forensic/IForensicServiceCommandCallback.aidl @@ -0,0 +1,33 @@ +/* + * Copyright 2024 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.security.forensic; + +/** + * @hide + */ + oneway interface IForensicServiceCommandCallback { + @Backing(type="int") + enum ErrorCode{ + UNKNOWN = 0, + PERMISSION_DENIED = 1, + INVALID_STATE_TRANSITION = 2, + BACKUP_TRANSPORT_UNAVAILABLE = 3, + DATA_SOURCE_UNAVAILABLE = 3, + } + void onSuccess(); + void onFailure(ErrorCode error); + } diff --git a/core/java/android/security/forensic/IForensicServiceStateCallback.aidl b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl new file mode 100644 index 000000000000..0cda35083ffd --- /dev/null +++ b/core/java/android/security/forensic/IForensicServiceStateCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 2024 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.security.forensic; + +/** + * @hide + */ + oneway interface IForensicServiceStateCallback { + @Backing(type="int") + enum State{ + UNKNOWN = 0, + INVISIBLE = 1, + VISIBLE = 2, + ENABLED = 3, + } + void onStateChange(State state); + } diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 0dec13ff0c02..e254bf3e016f 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -73,8 +73,11 @@ import java.util.Locale; public abstract class Layout { // These should match the constants in framework/base/libs/hwui/hwui/DrawTextFunctor.h - private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX = 4f; - private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0.2f; + private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX = 0f; + private static final float HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR = 0f; + private static final float HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP = 5f; + // since we're not using soft light yet, this needs to be much lower than the spec'd 0.8 + private static final float HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE = 0.5f; /** @hide */ @IntDef(prefix = { "BREAK_STRATEGY_" }, value = { @@ -1025,11 +1028,18 @@ public abstract class Layout { var padding = Math.max(HIGH_CONTRAST_TEXT_BORDER_WIDTH_MIN_PX, mPaint.getTextSize() * HIGH_CONTRAST_TEXT_BORDER_WIDTH_FACTOR); + var cornerRadius = mPaint.density * HIGH_CONTRAST_TEXT_BACKGROUND_CORNER_RADIUS_DP; + + // We set the alpha on the color itself instead of Paint.setAlpha(), because that function + // actually mutates the color in... *ehem* very strange ways. Also the color might get reset + // for various reasons, which also resets the alpha. + var white = Color.argb(HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE, 1f, 1f, 1f); + var black = Color.argb(HIGH_CONTRAST_TEXT_BACKGROUND_ALPHA_PERCENTAGE, 0f, 0f, 0f); var originalTextColor = mPaint.getColor(); var bgPaint = mWorkPlainPaint; bgPaint.reset(); - bgPaint.setColor(isHighContrastTextDark(originalTextColor) ? Color.WHITE : Color.BLACK); + bgPaint.setColor(isHighContrastTextDark(originalTextColor) ? white : black); bgPaint.setStyle(Paint.Style.FILL); int start = getLineStart(firstLine); @@ -1082,7 +1092,12 @@ public abstract class Layout { private void drawRect() { if (!mLineBackground.isEmpty()) { mLineBackground.inset(-padding, -padding); - canvas.drawRect(mLineBackground, bgPaint); + canvas.drawRoundRect( + mLineBackground, + cornerRadius, + cornerRadius, + bgPaint + ); } } @@ -1104,7 +1119,7 @@ public abstract class Layout { if (hasColorChanged) { mLastColor = textColor; - return isHighContrastTextDark(textColor) ? Color.WHITE : Color.BLACK; + return isHighContrastTextDark(textColor) ? white : black; } return bgPaint.getColor(); diff --git a/core/java/android/util/NtpTrustedTime.java b/core/java/android/util/NtpTrustedTime.java index 9f54d9fca24b..3adbd686cd2c 100644 --- a/core/java/android/util/NtpTrustedTime.java +++ b/core/java/android/util/NtpTrustedTime.java @@ -24,7 +24,7 @@ import android.content.Context; import android.content.res.Resources; import android.net.ConnectivityManager; import android.net.Network; -import android.net.NetworkCapabilities; +import android.net.NetworkInfo; import android.net.SntpClient; import android.os.Build; import android.os.SystemClock; @@ -687,16 +687,8 @@ public abstract class NtpTrustedTime implements TrustedTime { if (connectivityManager == null) { return false; } - final NetworkCapabilities networkCapabilities = - connectivityManager.getNetworkCapabilities(network); - if (networkCapabilities == null) { - if (LOGD) Log.d(TAG, "getNetwork: failed to get network capabilities"); - return false; - } - final boolean isConnectedToInternet = networkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_INTERNET) - && networkCapabilities.hasCapability( - NetworkCapabilities.NET_CAPABILITY_VALIDATED); + final NetworkInfo ni = connectivityManager.getNetworkInfo(network); + // This connectivity check is to avoid performing a DNS lookup for the time server on a // unconnected network. There are races to obtain time in Android when connectivity // changes, which means that forceRefresh() can be called by various components before @@ -706,8 +698,8 @@ public abstract class NtpTrustedTime implements TrustedTime { // A side effect of check is that tests that run a fake NTP server on the device itself // will only be able to use it if the active network is connected, even though loopback // addresses are actually reachable. - if (!isConnectedToInternet) { - if (LOGD) Log.d(TAG, "getNetwork: no internet connectivity"); + if (ni == null || !ni.isConnected()) { + if (LOGD) Log.d(TAG, "getNetwork: no connectivity"); return false; } return true; diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index d08873c56e6a..59c66532fe0b 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -22,6 +22,7 @@ import static android.view.InsetsControllerProto.CONTROL; import static android.view.InsetsControllerProto.STATE; import static android.view.InsetsSource.ID_IME; import static android.view.InsetsSource.ID_IME_CAPTION_BAR; +import static android.view.ViewProtoLogGroups.IME_INSETS_CONTROLLER; import static android.view.WindowInsets.Type.FIRST; import static android.view.WindowInsets.Type.LAST; import static android.view.WindowInsets.Type.all; @@ -69,6 +70,7 @@ import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.SoftInputShowHideReason; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.function.TriFunction; import java.io.PrintWriter; @@ -1920,6 +1922,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation final @InsetsType int requestedVisibleTypes = (mRequestedVisibleTypes & ~mask) | (visibleTypes & mask); if (mRequestedVisibleTypes != requestedVisibleTypes) { + ProtoLog.d(IME_INSETS_CONTROLLER, "Setting requestedVisibleTypes to %d (was %d)", + requestedVisibleTypes, mRequestedVisibleTypes); mRequestedVisibleTypes = requestedVisibleTypes; } } diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 83b4971c8621..eb59e21cd7e2 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -1734,9 +1734,7 @@ public class SurfaceView extends View implements ViewRootImpl.SurfaceChangedCall mRTLastReportedPosition.top /*positionTop*/, postScaleX, postScaleY); - mRTLastSetCrop.set((int) (clipLeft / postScaleX), (int) (clipTop / postScaleY), - (int) Math.ceil(clipRight / postScaleX), - (int) Math.ceil(clipBottom / postScaleY)); + mRTLastSetCrop.set(clipLeft, clipTop, clipRight, clipBottom); if (DEBUG_POSITION) { Log.d(TAG, String.format("Setting layer crop = [%d, %d, %d, %d] " + "from scale %f, %f", mRTLastSetCrop.left, diff --git a/core/java/android/view/ViewProtoLogGroups.java b/core/java/android/view/ViewProtoLogGroups.java new file mode 100644 index 000000000000..099f76189a50 --- /dev/null +++ b/core/java/android/view/ViewProtoLogGroups.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2024 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.view; + +import android.annotation.NonNull; +import android.view.inputmethod.Flags; + +import com.android.internal.protolog.ProtoLogGroup; + +import java.util.UUID; + +/** + * Defines logging groups for ProtoLog. + * + * This file is used by the ProtoLogTool to generate optimized logging code. All of its dependencies + * must be included in services.core.wm.protologgroups build target. + * + * @hide + */ +final class ViewProtoLogGroups { + final static ProtoLogGroup IME_INSETS_CONTROLLER = new ProtoLogGroup( + "IME_INSETS_CONTROLLER", "InsetsController", Flags.refactorInsetsController()); + + final static ProtoLogGroup[] ALL_GROUPS = { + IME_INSETS_CONTROLLER + }; +} + diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 3be9a821a463..1f17e8ec7b85 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -279,6 +279,7 @@ import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; import com.android.internal.policy.DecorView; import com.android.internal.policy.PhoneFallbackEventHandler; +import com.android.internal.protolog.ProtoLog; import com.android.internal.util.FastPrintWriter; import com.android.internal.view.BaseSurfaceHolder; import com.android.internal.view.RootViewSurfaceTaker; @@ -1282,6 +1283,8 @@ public final class ViewRootImpl implements ViewParent, mIsStylusPointerIconEnabled = InputSettings.isStylusPointerIconEnabled(mContext); + initializeProtoLogInProcess(); + String processorOverrideName = context.getResources().getString( R.string.config_inputEventCompatProcessorOverrideClassName); if (processorOverrideName.isEmpty()) { @@ -5949,7 +5952,34 @@ public final class ViewRootImpl implements ViewParent, // If no intersection, set bounds to empty. bounds.setEmpty(); } - return !bounds.isEmpty(); + + if (bounds.isEmpty()) { + return false; + } + + if (android.view.accessibility.Flags.focusRectMinSize()) { + adjustAccessibilityFocusedRectBoundsIfNeeded(bounds); + } + + return true; + } + + /** + * Adjusts accessibility focused rect bounds so that they are not invisible. + * + * <p>Focus bounds smaller than double the stroke width are very hard to see (or invisible). + * Expand the focus bounds if necessary to at least double the stroke width. + * @param bounds The bounds to adjust + */ + @VisibleForTesting + public void adjustAccessibilityFocusedRectBoundsIfNeeded(Rect bounds) { + final int minRectLength = mAccessibilityManager.getAccessibilityFocusStrokeWidth() * 2; + if (bounds.width() < minRectLength || bounds.height() < minRectLength) { + final float missingWidth = Math.max(0, minRectLength - bounds.width()); + final float missingHeight = Math.max(0, minRectLength - bounds.height()); + bounds.inset(-1 * (int) Math.ceil(missingWidth / 2), + -1 * (int) Math.ceil(missingHeight / 2)); + } } private Drawable getAccessibilityFocusedDrawable() { @@ -13403,4 +13433,13 @@ public final class ViewRootImpl implements ViewParent, mCurrentColorMode = colorMode; } + + private static boolean sProtoLogInitialized = false; + + private void initializeProtoLogInProcess() { + if (!sProtoLogInitialized) { + ProtoLog.init(ViewProtoLogGroups.ALL_GROUPS); + sProtoLogInitialized = true; + } + } } diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig index 8ffae845de1f..820a1fba11ad 100644 --- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig +++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig @@ -96,6 +96,16 @@ flag { flag { namespace: "accessibility" + name: "focus_rect_min_size" + description: "Ensures the a11y focus rect is big enough to be drawn as visible" + bug: "368667566" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + namespace: "accessibility" name: "force_invert_color" description: "Enable force force-dark for smart inversion and dark theme everywhere" bug: "282821643" diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 1e5c6d8177e1..47fc43735c4d 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2352,6 +2352,13 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note: * this does not return result of the request. For result use {@param resultReceiver} instead. + * + * @deprecated The {@link ResultReceiver} is not a reliable way of determining whether the + * Input Method is actually shown or hidden. If result is needed, use + * {@link android.view.WindowInsetsController#show} instead and set a + * {@link View.OnApplyWindowInsetsListener} and verify the provided {@link WindowInsets} for + * the visibility of IME. If result is not needed, use {@link #showSoftInput(View, int)} + * instead. */ public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) { return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT); @@ -2399,6 +2406,14 @@ public final class InputMethodManager { & WindowInsets.Type.ime()) == 0) { ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_NO_ONGOING_USER_ANIMATION); + if (resultReceiver != null) { + final boolean imeReqVisible = + (viewRootImpl.getInsetsController().getRequestedVisibleTypes() + & WindowInsets.Type.ime()) != 0; + resultReceiver.send( + imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : InputMethodManager.RESULT_SHOWN, null); + } // TODO(b/322992891) handle case of SHOW_IMPLICIT viewRootImpl.getInsetsController().show(WindowInsets.Type.ime(), false /* fromIme */, statsToken); @@ -2531,6 +2546,13 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. * @return {@code true} if a request was sent to system_server, {@code false} otherwise. Note: * this does not return result of the request. For result use {@param resultReceiver} instead. + * + * @deprecated The {@link ResultReceiver} is not a reliable way of determining whether the + * Input Method is actually shown or hidden. If result is needed, use + * {@link android.view.WindowInsetsController#hide} instead and set a + * {@link View.OnApplyWindowInsetsListener} and verify the provided {@link WindowInsets} for + * the visibility of IME. If result is not needed, use + * {@link #hideSoftInputFromView(View, int)} instead. */ public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags, ResultReceiver resultReceiver) { @@ -2569,6 +2591,14 @@ public final class InputMethodManager { // TODO(b/322992891) handle case of HIDE_IMPLICIT_ONLY final var viewRootImpl = servedView.getViewRootImpl(); if (viewRootImpl != null) { + if (resultReceiver != null) { + final boolean imeReqVisible = + (viewRootImpl.getInsetsController().getRequestedVisibleTypes() + & WindowInsets.Type.ime()) != 0; + resultReceiver.send( + !imeReqVisible ? InputMethodManager.RESULT_UNCHANGED_HIDDEN + : InputMethodManager.RESULT_HIDDEN, null); + } viewRootImpl.getInsetsController().hide(WindowInsets.Type.ime()); } return true; diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index bae8affcec47..aa4927ee9b9c 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -73,6 +73,17 @@ flag { } flag { + name: "consistent_get_current_input_method_info" + namespace: "input_method" + description: "Use BindingController as the source of truth in getCurrentInputMethodInfo" + bug: "355034523" + is_fixed_read_only: true + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "ime_switcher_revamp" is_exported: true namespace: "input_method" diff --git a/core/java/android/window/BackEvent.java b/core/java/android/window/BackEvent.java index 1c3f201c1471..23e572fcd577 100644 --- a/core/java/android/window/BackEvent.java +++ b/core/java/android/window/BackEvent.java @@ -156,6 +156,22 @@ public final class BackEvent { } @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof BackEvent)) { + return false; + } + final BackEvent that = (BackEvent) other; + return mTouchX == that.mTouchX + && mTouchY == that.mTouchY + && mProgress == that.mProgress + && mSwipeEdge == that.mSwipeEdge + && mFrameTime == that.mFrameTime; + } + + @Override public String toString() { return "BackEvent{" + "mTouchX=" + mTouchX diff --git a/core/java/android/window/DesktopModeFlags.java b/core/java/android/window/DesktopModeFlags.java index 8e35843e2193..05dc910b89de 100644 --- a/core/java/android/window/DesktopModeFlags.java +++ b/core/java/android/window/DesktopModeFlags.java @@ -64,7 +64,9 @@ public enum DesktopModeFlags { ENABLE_WINDOWING_EDGE_DRAG_RESIZE(Flags::enableWindowingEdgeDragResize, true), ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS( Flags::enableDesktopWindowingTaskbarRunningApps, true), + // TODO: b/369763947 - remove this once ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS is ramped up ENABLE_DESKTOP_WINDOWING_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false), + ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS(Flags::enableDesktopWindowingTransitions, false), ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS(Flags::enableDesktopWindowingExitTransitions, false), ENABLE_WINDOWING_TRANSITION_HANDLERS_OBSERVERS( Flags::enableWindowingTransitionHandlersObservers, false); diff --git a/core/java/android/window/TaskSnapshot.java b/core/java/android/window/TaskSnapshot.java index 20d1b3bd12ae..a37bef80ff04 100644 --- a/core/java/android/window/TaskSnapshot.java +++ b/core/java/android/window/TaskSnapshot.java @@ -83,13 +83,16 @@ public class TaskSnapshot implements Parcelable { public static final int REFERENCE_CACHE = 1 << 1; /** This snapshot object is being persistent. */ public static final int REFERENCE_PERSIST = 1 << 2; + /** This snapshot object is being used for content suggestion. */ + public static final int REFERENCE_CONTENT_SUGGESTION = 1 << 3; @IntDef(flag = true, prefix = { "REFERENCE_" }, value = { REFERENCE_BROADCAST, REFERENCE_CACHE, - REFERENCE_PERSIST + REFERENCE_PERSIST, + REFERENCE_CONTENT_SUGGESTION }) @Retention(RetentionPolicy.SOURCE) - @interface ReferenceFlags {} + public @interface ReferenceFlags {} public TaskSnapshot(long id, long captureTime, @NonNull ComponentName topActivityComponent, HardwareBuffer snapshot, diff --git a/core/java/android/window/WindowContainerTransaction.java b/core/java/android/window/WindowContainerTransaction.java index 8e495ec1dc40..34abf3114925 100644 --- a/core/java/android/window/WindowContainerTransaction.java +++ b/core/java/android/window/WindowContainerTransaction.java @@ -525,6 +525,22 @@ public final class WindowContainerTransaction implements Parcelable { } /** + * Disables or enables activities to be started in adjacent tasks (see + * {@link FLAG_ACTIVITY_LAUNCH_ADJACENT}) for the specified root of any child tasks. This + * differs from {@link #setLaunchAdjacentFlagRoot(WindowContainerToken)} which controls the + * preferred launch-adjacent target and allows for selectively setting which root tasks can + * support launch-adjacent. + * @hide + */ + @NonNull + public WindowContainerTransaction setDisableLaunchAdjacent( + @NonNull WindowContainerToken container, boolean disabled) { + mHierarchyOps.add(HierarchyOp.createForSetDisableLaunchAdjacent(container.asBinder(), + disabled)); + return this; + } + + /** * Starts a task by id. The task is expected to already exist (eg. as a recent task). * @param taskId Id of task to start. * @param options bundle containing ActivityOptions for the task's top activity. @@ -1488,6 +1504,7 @@ public final class WindowContainerTransaction implements Parcelable { public static final int HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION = 20; public static final int HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES = 21; public static final int HIERARCHY_OP_TYPE_SET_KEYGUARD_STATE = 22; + public static final int HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT = 23; // The following key(s) are for use with mLaunchOptions: // When launching a task (eg. from recents), this is the taskId to be launched. @@ -1556,6 +1573,8 @@ public final class WindowContainerTransaction implements Parcelable { private @InsetsType int mExcludeInsetsTypes; + private boolean mLaunchAdjacentDisabled; + public static HierarchyOp createForReparent( @NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) { return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT) @@ -1644,6 +1663,15 @@ public final class WindowContainerTransaction implements Parcelable { .build(); } + /** Create a hierarchy op for disabling launch adjacent. */ + public static HierarchyOp createForSetDisableLaunchAdjacent(IBinder container, + boolean disabled) { + return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT) + .setContainer(container) + .setLaunchAdjacentDisabled(disabled) + .build(); + } + /** create a hierarchy op for deleting a task **/ public static HierarchyOp createForRemoveTask(@NonNull IBinder container) { return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REMOVE_TASK) @@ -1695,6 +1723,7 @@ public final class WindowContainerTransaction implements Parcelable { mReparentLeafTaskIfRelaunch = copy.mReparentLeafTaskIfRelaunch; mIsTrimmableFromRecents = copy.mIsTrimmableFromRecents; mExcludeInsetsTypes = copy.mExcludeInsetsTypes; + mLaunchAdjacentDisabled = copy.mLaunchAdjacentDisabled; } protected HierarchyOp(Parcel in) { @@ -1719,6 +1748,7 @@ public final class WindowContainerTransaction implements Parcelable { mReparentLeafTaskIfRelaunch = in.readBoolean(); mIsTrimmableFromRecents = in.readBoolean(); mExcludeInsetsTypes = in.readInt(); + mLaunchAdjacentDisabled = in.readBoolean(); } public int getType() { @@ -1814,13 +1844,11 @@ public final class WindowContainerTransaction implements Parcelable { } /** Denotes whether the parents should also be included in the op. */ - @NonNull public boolean includingParents() { return mIncludingParents; } - /** Set the task to be trimmable */ - @NonNull + /** Denotes whether the task can be trimmable from recents */ public boolean isTrimmableFromRecents() { return mIsTrimmableFromRecents; } @@ -1829,6 +1857,11 @@ public final class WindowContainerTransaction implements Parcelable { return mExcludeInsetsTypes; } + /** Denotes whether launch-adjacent flag is respected from this task or its children */ + public boolean isLaunchAdjacentDisabled() { + return mLaunchAdjacentDisabled; + } + /** Gets a string representation of a hierarchy-op type. */ public static String hopToString(int type) { switch (type) { @@ -1839,6 +1872,8 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS: return "SetAdjacentRoot"; case HIERARCHY_OP_TYPE_LAUNCH_TASK: return "LaunchTask"; case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: return "SetAdjacentFlagRoot"; + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: + return "SetDisableLaunchAdjacent"; case HIERARCHY_OP_TYPE_PENDING_INTENT: return "PendingIntent"; case HIERARCHY_OP_TYPE_START_SHORTCUT: return "StartShortcut"; case HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER: return "addInsetsFrameProvider"; @@ -1891,6 +1926,10 @@ public final class WindowContainerTransaction implements Parcelable { case HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT: sb.append("container=").append(mContainer).append(" clearRoot=").append(mToTop); break; + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: + sb.append("container=").append(mContainer).append(" disabled=") + .append(mLaunchAdjacentDisabled); + break; case HIERARCHY_OP_TYPE_START_SHORTCUT: sb.append("options=").append(mLaunchOptions) .append(" info=").append(mShortcutInfo); @@ -1971,6 +2010,7 @@ public final class WindowContainerTransaction implements Parcelable { dest.writeBoolean(mReparentLeafTaskIfRelaunch); dest.writeBoolean(mIsTrimmableFromRecents); dest.writeInt(mExcludeInsetsTypes); + dest.writeBoolean(mLaunchAdjacentDisabled); } @Override @@ -2047,6 +2087,8 @@ public final class WindowContainerTransaction implements Parcelable { private @InsetsType int mExcludeInsetsTypes; + private boolean mLaunchAdjacentDisabled; + Builder(int type) { mType = type; } @@ -2153,6 +2195,11 @@ public final class WindowContainerTransaction implements Parcelable { return this; } + Builder setLaunchAdjacentDisabled(boolean disabled) { + mLaunchAdjacentDisabled = disabled; + return this; + } + HierarchyOp build() { final HierarchyOp hierarchyOp = new HierarchyOp(mType); hierarchyOp.mContainer = mContainer; @@ -2179,6 +2226,7 @@ public final class WindowContainerTransaction implements Parcelable { hierarchyOp.mReparentLeafTaskIfRelaunch = mReparentLeafTaskIfRelaunch; hierarchyOp.mIsTrimmableFromRecents = mIsTrimmableFromRecents; hierarchyOp.mExcludeInsetsTypes = mExcludeInsetsTypes; + hierarchyOp.mLaunchAdjacentDisabled = mLaunchAdjacentDisabled; return hierarchyOp; } diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index 44a374fb7c20..c9d458f22463 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -17,6 +17,7 @@ package android.window; import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver; +import static com.android.window.flags.Flags.predictiveBackTimestampApi; import android.annotation.NonNull; import android.annotation.Nullable; @@ -563,7 +564,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { } OnBackAnimationCallback animationCallback = getBackAnimationCallback(); if (animationCallback != null - && !(callback instanceof ImeBackAnimationController)) { + && !(callback instanceof ImeBackAnimationController) + && !predictiveBackTimestampApi()) { mProgressAnimator.onBackInvoked(() -> { if (mIsSystemCallback) { mSystemNavigationObserverCallbackRunnable.run(); diff --git a/core/java/android/window/flags/lse_desktop_experience.aconfig b/core/java/android/window/flags/lse_desktop_experience.aconfig index 155494fb3b25..45f6480b0b7f 100644 --- a/core/java/android/window/flags/lse_desktop_experience.aconfig +++ b/core/java/android/window/flags/lse_desktop_experience.aconfig @@ -239,6 +239,13 @@ flag { } flag { + name: "enable_desktop_windowing_enter_transitions" + namespace: "lse_desktop_experience" + description: "Enables enter desktop windowing transition & motion polish changes" + bug: "369763947" +} + +flag { name: "enable_desktop_windowing_exit_transitions" namespace: "lse_desktop_experience" description: "Enables exit desktop windowing transition & motion polish changes" @@ -301,6 +308,13 @@ flag { } flag { + name: "enable_restore_to_previous_size_from_desktop_immersive" + namespace: "lse_desktop_experience" + description: "Restores the window bounds to their previous size when exiting desktop immersive" + bug: "372318163" +} + +flag { name: "enable_display_focus_in_shell_transitions" namespace: "lse_desktop_experience" description: "Creates a shell transition when display focus switches." diff --git a/core/java/com/android/internal/app/ResolverListAdapter.java b/core/java/com/android/internal/app/ResolverListAdapter.java index 18c8eb4ec46b..de7ad346a7cd 100644 --- a/core/java/com/android/internal/app/ResolverListAdapter.java +++ b/core/java/com/android/internal/app/ResolverListAdapter.java @@ -1195,7 +1195,12 @@ public class ResolverListAdapter extends BaseAdapter { @Nullable protected Drawable loadIconFromResource(Resources res, int resId) { - return res.getDrawableForDensity(resId, mIconDpi); + try { + return res.getDrawableForDensity(resId, mIconDpi); + } catch (Resources.NotFoundException e) { + Log.e(TAG, "Resource not found", e); + return null; + } } } diff --git a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java index 0a80e006d5bc..3c201fc42df3 100644 --- a/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java +++ b/core/java/com/android/internal/protolog/ProtoLogViewerConfigReader.java @@ -39,7 +39,7 @@ public class ProtoLogViewerConfigReader { * or the viewer config is not loaded into memory. */ @Nullable - public synchronized String getViewerString(long messageHash) { + public String getViewerString(long messageHash) { return mLogMessageMap.get(messageHash); } @@ -179,6 +179,6 @@ public class ProtoLogViewerConfigReader { } } - throw new RuntimeException("Group " + group + "not found in viewer config"); + throw new RuntimeException("Group " + group + " not found in viewer config"); } } diff --git a/core/java/com/android/internal/widget/NotificationProgressBar.java b/core/java/com/android/internal/widget/NotificationProgressBar.java index 3e597d73f217..d3b1f972a955 100644 --- a/core/java/com/android/internal/widget/NotificationProgressBar.java +++ b/core/java/com/android/internal/widget/NotificationProgressBar.java @@ -20,16 +20,20 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.Notification.ProgressStyle; import android.content.Context; +import android.content.res.ColorStateList; import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; +import android.graphics.drawable.LayerDrawable; import android.os.Bundle; import android.util.AttributeSet; +import android.util.Log; import android.view.RemotableViewMethod; import android.widget.ProgressBar; import android.widget.RemoteViews; import androidx.annotation.ColorInt; +import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import com.android.internal.widget.NotificationProgressDrawable.Part; @@ -49,7 +53,13 @@ import java.util.TreeSet; */ @RemoteViews.RemoteView public class NotificationProgressBar extends ProgressBar { + private static final String TAG = "NotificationProgressBar"; + private NotificationProgressModel mProgressModel; + + @Nullable + private List<Part> mProgressDrawableParts = null; + @Nullable private Drawable mProgressTrackerDrawable = null; @@ -58,7 +68,7 @@ public class NotificationProgressBar extends ProgressBar { } public NotificationProgressBar(Context context, AttributeSet attrs) { - this(context, attrs, com.android.internal.R.attr.progressBarStyle); + this(context, attrs, R.attr.progressBarStyle); } public NotificationProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { @@ -82,10 +92,42 @@ public class NotificationProgressBar extends ProgressBar { "Bundle shouldn't be null"); mProgressModel = NotificationProgressModel.fromBundle(bundle); + + if (mProgressModel.isIndeterminate()) { + final int indeterminateColor = mProgressModel.getIndeterminateColor(); + setIndeterminateTintList(ColorStateList.valueOf(indeterminateColor)); + } else { + mProgressDrawableParts = processAndConvertToDrawableParts(mProgressModel.getSegments(), + mProgressModel.getPoints(), + mProgressModel.getProgress(), mProgressModel.isStyledByProgress()); + + try { + final NotificationProgressDrawable drawable = getNotificationProgressDrawable(); + drawable.setParts(mProgressDrawableParts); + } catch (IllegalStateException ex) { + Log.e(TAG, "Can't set parts because can't get NotificationProgressDrawable", ex); + } + } } - private void setProgressModel(@NonNull NotificationProgressModel model) { - mProgressModel = model; + @NonNull + private NotificationProgressDrawable getNotificationProgressDrawable() { + final Drawable d = getProgressDrawable(); + if (d == null) { + throw new IllegalStateException("getProgressDrawable() returns null"); + } + if (!(d instanceof LayerDrawable)) { + throw new IllegalStateException("getProgressDrawable() doesn't return a LayerDrawable"); + } + + final Drawable layer = ((LayerDrawable) d).findDrawableByLayerId(R.id.background); + if (!(layer instanceof NotificationProgressDrawable)) { + throw new IllegalStateException( + "Couldn't get NotificationProgressDrawable, retrieved drawable is: " + ( + layer != null ? layer.toString() : null)); + } + + return (NotificationProgressDrawable) layer; } /** @@ -97,7 +139,6 @@ public class NotificationProgressBar extends ProgressBar { public void setProgressTrackerIcon(@Nullable Icon icon) { } - /** * Async version of {@link #setProgressTrackerIcon} */ diff --git a/core/java/com/android/internal/widget/NotificationProgressDrawable.java b/core/java/com/android/internal/widget/NotificationProgressDrawable.java index 89ef8759a169..2be7273e5d10 100644 --- a/core/java/com/android/internal/widget/NotificationProgressDrawable.java +++ b/core/java/com/android/internal/widget/NotificationProgressDrawable.java @@ -45,6 +45,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.Objects; /** @@ -156,11 +157,18 @@ public final class NotificationProgressDrawable extends Drawable { } /** - * + * Set the segments and points that constitute the drawable. */ - public void setParts(@NonNull Part... parts) { + public void setParts(List<Part> parts) { mParts.clear(); - mParts.addAll(Arrays.asList(parts)); + mParts.addAll(parts); + } + + /** + * Set the segments and points that constitute the drawable. + */ + public void setParts(@NonNull Part... parts) { + setParts(Arrays.asList(parts)); } @Override @@ -379,7 +387,7 @@ public final class NotificationProgressDrawable extends Drawable { if (state.mThemeAttrsPoints != null) { final TypedArray a = t.resolveAttributes( state.mThemeAttrsPoints, R.styleable.NotificationProgressDrawablePoints); - updateSegmentsFromTypedArray(a); + updatePointsFromTypedArray(a); a.recycle(); } } @@ -651,9 +659,11 @@ public final class NotificationProgressDrawable extends Drawable { State(@NonNull State orig, @Nullable Resources res) { mChangingConfigurations = orig.mChangingConfigurations; + mSegSegGap = orig.mSegSegGap; + mSegPointGap = orig.mSegPointGap; + mStrokeWidth = orig.mStrokeWidth; mStrokeColor = orig.mStrokeColor; mFadedStrokeColor = orig.mFadedStrokeColor; - mStrokeWidth = orig.mStrokeWidth; mStrokeDashWidth = orig.mStrokeDashWidth; mStrokeDashGap = orig.mStrokeDashGap; mPointRadius = orig.mPointRadius; @@ -791,6 +801,7 @@ public final class NotificationProgressDrawable extends Drawable { final State state = mState; mStrokePaint.setStrokeWidth(state.mStrokeWidth); + mDashedStrokePaint.setStrokeWidth(state.mStrokeWidth); if (state.mStrokeDashWidth != 0.0f) { final DashPathEffect e = new DashPathEffect( diff --git a/core/java/com/android/internal/widget/NotificationRowIconView.java b/core/java/com/android/internal/widget/NotificationRowIconView.java index 98e6e8505534..adcc0f64b598 100644 --- a/core/java/com/android/internal/widget/NotificationRowIconView.java +++ b/core/java/com/android/internal/widget/NotificationRowIconView.java @@ -39,17 +39,24 @@ import com.android.internal.R; /** * An image view that holds the icon displayed at the start of a notification row. + * This can generally either display the "small icon" of a notification set via + * {@link this#setImageIcon(Icon)}, or an app icon controlled and fetched by the provider set + * through {@link this#setIconProvider(NotificationIconProvider)}. */ @RemoteViews.RemoteView public class NotificationRowIconView extends CachingIconView { + private NotificationIconProvider mIconProvider; + private boolean mApplyCircularCrop = false; private boolean mShouldShowAppIcon = false; + private Drawable mAppIcon = null; - // Padding and background set on the view prior to being changed by setShouldShowAppIcon(true), - // to be restored if shouldShowAppIcon becomes false again. + // Padding, background and colors set on the view prior to being overridden when showing the app + // icon, to be restored if we're showing the small icon again. private Rect mOriginalPadding = null; private Drawable mOriginalBackground = null; - + private int mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID; + private int mOriginalIconColor = ColoredIconHelper.COLOR_INVALID; public NotificationRowIconView(Context context) { super(context); @@ -81,6 +88,71 @@ public class NotificationRowIconView extends CachingIconView { super.onFinishInflate(); } + /** + * Sets the icon provider for this view. This is used to determine whether we should show the + * app icon instead of the small icon, and to fetch the app icon if needed. + */ + public void setIconProvider(NotificationIconProvider iconProvider) { + mIconProvider = iconProvider; + } + + private Drawable loadAppIcon() { + if (mIconProvider != null && mIconProvider.shouldShowAppIcon()) { + return mIconProvider.getAppIcon(); + } + return null; + } + + @RemotableViewMethod(asyncImpl = "setImageIconAsync") + @Override + public void setImageIcon(Icon icon) { + if (Flags.notificationsRedesignAppIcons()) { + if (mAppIcon != null) { + // We already know that we should be using the app icon, and we already loaded it. + // We assume that cannot change throughout the lifetime of a notification, so + // there's nothing to do here. + return; + } + mAppIcon = loadAppIcon(); + if (mAppIcon != null) { + setImageDrawable(mAppIcon); + adjustViewForAppIcon(); + } else { + super.setImageIcon(icon); + restoreViewForSmallIcon(); + } + return; + } + super.setImageIcon(icon); + } + + @RemotableViewMethod + @Override + public Runnable setImageIconAsync(Icon icon) { + if (Flags.notificationsRedesignAppIcons()) { + if (mAppIcon != null) { + // We already know that we should be using the app icon, and we already loaded it. + // We assume that cannot change throughout the lifetime of a notification, so + // there's nothing to do here. + return () -> { + }; + } + mAppIcon = loadAppIcon(); + if (mAppIcon != null) { + return () -> { + setImageDrawable(mAppIcon); + adjustViewForAppIcon(); + }; + } else { + return () -> { + super.setImageIcon(icon); + restoreViewForSmallIcon(); + }; + } + } + return super.setImageIconAsync(icon); + } + /** Whether the icon represents the app icon (instead of the small icon). */ @RemotableViewMethod public void setShouldShowAppIcon(boolean shouldShowAppIcon) { @@ -91,35 +163,122 @@ public class NotificationRowIconView extends CachingIconView { mShouldShowAppIcon = shouldShowAppIcon; if (mShouldShowAppIcon) { - if (mOriginalPadding == null && mOriginalBackground == null) { - mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(), - getPaddingRight(), getPaddingBottom()); - mOriginalBackground = getBackground(); - } - - setPadding(0, 0, 0, 0); - - // Make the background white in case the icon itself doesn't have one. - ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE, - PorterDuff.Mode.SRC_ATOP); - - if (mOriginalBackground == null) { - setBackground(getContext().getDrawable(R.drawable.notification_icon_circle)); - } - getBackground().mutate().setColorFilter(colorFilter); + adjustViewForAppIcon(); } else { // Restore original padding and background if needed - if (mOriginalPadding != null) { - setPadding(mOriginalPadding.left, mOriginalPadding.top, mOriginalPadding.right, - mOriginalPadding.bottom); - mOriginalPadding = null; - } - setBackground(mOriginalBackground); - mOriginalBackground = null; + restoreViewForSmallIcon(); } } } + /** + * Override padding and background from the view to display the app icon. + */ + private void adjustViewForAppIcon() { + removePadding(); + + if (Flags.notificationsUseAppIconInRow()) { + addWhiteBackground(); + } else { + // No need to set the background for notification redesign, since the icon + // factory already does that for us. + removeBackground(); + } + } + + /** + * Restore padding and background overridden by {@link this#adjustViewForAppIcon}. + * Does nothing if they were not overridden. + */ + private void restoreViewForSmallIcon() { + restorePadding(); + restoreBackground(); + restoreColors(); + } + + private void removePadding() { + if (mOriginalPadding == null) { + mOriginalPadding = new Rect(getPaddingLeft(), getPaddingTop(), + getPaddingRight(), getPaddingBottom()); + } + setPadding(0, 0, 0, 0); + } + + private void restorePadding() { + if (mOriginalPadding != null) { + setPadding(mOriginalPadding.left, mOriginalPadding.top, + mOriginalPadding.right, + mOriginalPadding.bottom); + mOriginalPadding = null; + } + } + + private void removeBackground() { + if (mOriginalBackground == null) { + mOriginalBackground = getBackground(); + } + + setBackground(null); + } + + private void addWhiteBackground() { + if (mOriginalBackground == null) { + mOriginalBackground = getBackground(); + } + + // Make the background white in case the icon itself doesn't have one. + ColorFilter colorFilter = new PorterDuffColorFilter(Color.WHITE, + PorterDuff.Mode.SRC_ATOP); + + if (mOriginalBackground == null) { + setBackground(getContext().getDrawable(R.drawable.notification_icon_circle)); + } + getBackground().mutate().setColorFilter(colorFilter); + } + + private void restoreBackground() { + // NOTE: This will not work if the original background was null, but that's better than + // accidentally clearing the background. We expect that there's generally going to be one + // anyway unless we manually clear it. + if (mOriginalBackground != null) { + setBackground(mOriginalBackground); + mOriginalBackground = null; + } + } + + private void restoreColors() { + if (mOriginalBackgroundColor != ColoredIconHelper.COLOR_INVALID) { + super.setBackgroundColor(mOriginalBackgroundColor); + mOriginalBackgroundColor = ColoredIconHelper.COLOR_INVALID; + } + if (mOriginalIconColor != ColoredIconHelper.COLOR_INVALID) { + super.setOriginalIconColor(mOriginalIconColor); + mOriginalIconColor = ColoredIconHelper.COLOR_INVALID; + } + } + + @RemotableViewMethod + @Override + public void setBackgroundColor(int color) { + // Ignore color overrides if we're showing the app icon. + if (mAppIcon == null) { + super.setBackgroundColor(color); + } else { + mOriginalBackgroundColor = color; + } + } + + @RemotableViewMethod + @Override + public void setOriginalIconColor(int color) { + // Ignore color overrides if we're showing the app icon. + if (mAppIcon == null) { + super.setOriginalIconColor(color); + } else { + mOriginalIconColor = color; + } + } + @Nullable @Override Drawable loadSizeRestrictedIcon(@Nullable Icon icon) { @@ -197,4 +356,17 @@ public class NotificationRowIconView extends CachingIconView { return bitmap; } + + /** + * A provider that allows this view to verify whether it should use the app icon instead of the + * icon provided to it via setImageIcon, as well as actually fetching the app icon. It should + * primarily be called on the background thread. + */ + public interface NotificationIconProvider { + /** Whether this notification should use the app icon instead of the small icon. */ + boolean shouldShowAppIcon(); + + /** Get the app icon for this notification. */ + Drawable getAppIcon(); + } } diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp index 68d49cda191d..041fed74c573 100644 --- a/core/jni/android_hardware_camera2_CameraMetadata.cpp +++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp @@ -336,7 +336,7 @@ static void CameraMetadata_swap(JNIEnv *env, jclass thiz, jlong ptr, jlong other static jbyteArray CameraMetadata_readValues(JNIEnv *env, jclass thiz, jint tag, jlong ptr) { ALOGV("%s (tag = %d)", __FUNCTION__, tag); - const CameraMetadata *metadata = CameraMetadata_getPointerThrow(env, ptr); + CameraMetadata* metadata = CameraMetadata_getPointerThrow(env, ptr); if (metadata == NULL) return NULL; const camera_metadata_t *metaBuffer = metadata->getAndLock(); @@ -349,14 +349,16 @@ static jbyteArray CameraMetadata_readValues(JNIEnv *env, jclass thiz, jint tag, } size_t tagSize = Helpers::getTypeSize(tagType); - camera_metadata_ro_entry entry = metadata->find(tag); + camera_metadata_entry entry = metadata->find(tag); if (entry.count == 0) { - if (!metadata->exists(tag)) { - ALOGV("%s: Tag %d does not have any entries", __FUNCTION__, tag); - } else { - ALOGV("%s: Tag %d had an entry, but it had 0 data", __FUNCTION__, tag); - } - return NULL; + if (!metadata->exists(tag)) { + ALOGV("%s: Tag %d does not have any entries", __FUNCTION__, tag); + return NULL; + } else { + // OK: we will return a 0-sized array. + ALOGV("%s: Tag %d had an entry, but it had 0 data", __FUNCTION__, + tag); + } } jsize byteCount = entry.count * tagSize; diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 5693d666adc2..5522aa09a32e 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -8479,6 +8479,18 @@ android:protectionLevel="internal" android:featureFlag="android.content.pm.verification_service" /> + <!-- + This permission allows the system to receive PACKAGE_CHANGED broadcasts when the component + state of a non-exported component has been changed. + <p>Not for use by third-party applications. </p> + <p>Protection level: internal + @hide + --> + <permission + android:name="android.permission.RECEIVE_PACKAGE_CHANGED_BROADCAST_ON_COMPONENT_STATE_CHANGED" + android:protectionLevel="internal" + android:featureFlag="android.content.pm.reduce_broadcasts_for_component_state_changes"/> + <!-- Attribution for Geofencing service. --> <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/> <!-- Attribution for Country Detector. --> diff --git a/core/res/res/color-watch-v36/btn_material_filled_background_color.xml b/core/res/res/color-watch-v36/btn_material_filled_background_color.xml new file mode 100644 index 000000000000..8b2afa86986c --- /dev/null +++ b/core/res/res/color-watch-v36/btn_material_filled_background_color.xml @@ -0,0 +1,23 @@ +<!-- + ~ Copyright (C) 2024 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="?attr/materialColorOnSurface" /> + <item android:state_enabled="true" + android:color="?attr/materialColorPrimary" /> +</selector>
\ No newline at end of file diff --git a/core/res/res/color-watch-v36/btn_material_filled_text_color.xml b/core/res/res/color-watch-v36/btn_material_filled_text_color.xml new file mode 100644 index 000000000000..cefc9121b7a4 --- /dev/null +++ b/core/res/res/color-watch-v36/btn_material_filled_text_color.xml @@ -0,0 +1,23 @@ +<!-- + ~ Copyright (C) 2024 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/primaryContentAlpha" + android:color="?attr/materialColorOnSurface" /> + <item android:state_enabled="true" + android:color="?attr/materialColorOnPrimary" /> +</selector>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml b/core/res/res/color-watch-v36/btn_material_filled_tonal_background_color.xml index 530d752732c1..eaf9e7d50bbd 100644 --- a/packages/SystemUI/res/layout/udfps_keyguard_view_legacy.xml +++ b/core/res/res/color-watch-v36/btn_material_filled_tonal_background_color.xml @@ -1,6 +1,5 @@ -<?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2021 The Android Open Source Project + ~ Copyright (C) 2024 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. @@ -14,12 +13,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> -<com.android.systemui.biometrics.UdfpsKeyguardViewLegacy - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/udfps_animation_view_legacy" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <!-- Add fingerprint views here. See udfps_keyguard_view_internal.xml. --> - -</com.android.systemui.biometrics.UdfpsKeyguardViewLegacy> +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/disabledAlpha" + android:color="?attr/materialColorOnSurface" /> + <item android:state_enabled="true" + android:color="?attr/materialColorSurfaceContainer" /> +</selector>
\ No newline at end of file diff --git a/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml b/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml new file mode 100644 index 000000000000..94e50fbe2533 --- /dev/null +++ b/core/res/res/color-watch-v36/btn_material_filled_tonal_text_color.xml @@ -0,0 +1,23 @@ +<!-- + ~ Copyright (C) 2024 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. + --> + +<selector xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:state_enabled="false" + android:alpha="?attr/primaryContentAlpha" + android:color="?attr/materialColorOnSurface" /> + <item android:state_enabled="true" + android:color="?attr/materialColorOnSurface" /> +</selector>
\ No newline at end of file diff --git a/core/res/res/drawable-watch-v36/btn_background_material_filled.xml b/core/res/res/drawable-watch-v36/btn_background_material_filled.xml new file mode 100644 index 000000000000..0029de14e34a --- /dev/null +++ b/core/res/res/drawable-watch-v36/btn_background_material_filled.xml @@ -0,0 +1,28 @@ +<!-- + ~ Copyright (C) 2024 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. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle"> + <solid android:color="@color/btn_material_filled_background_color"/> + <corners android:radius="?android:attr/buttonCornerRadius"/> + <size + android:width="@dimen/btn_material_width" + android:height="@dimen/btn_material_height" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml b/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml new file mode 100644 index 000000000000..105f077cd841 --- /dev/null +++ b/core/res/res/drawable-watch-v36/btn_background_material_filled_tonal.xml @@ -0,0 +1,28 @@ +<!-- + ~ Copyright (C) 2024 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. + --> + +<ripple xmlns:android="http://schemas.android.com/apk/res/android" + android:color="?attr/colorControlHighlight"> + <item> + <shape android:shape="rectangle"> + <solid android:color="@color/btn_material_filled_tonal_background_color"/> + <corners android:radius="?android:attr/buttonCornerRadius"/> + <size + android:width="@dimen/btn_material_width" + android:height="@dimen/btn_material_height" /> + </shape> + </item> +</ripple>
\ No newline at end of file diff --git a/core/res/res/drawable/ic_zen_mode_icon_piano.xml b/core/res/res/drawable/ic_zen_mode_icon_piano.xml new file mode 100644 index 000000000000..012b9398d687 --- /dev/null +++ b/core/res/res/drawable/ic_zen_mode_icon_piano.xml @@ -0,0 +1,25 @@ +<!-- +Copyright (C) 2024 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="24dp" + android:height="24dp" + android:tint="?android:attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/white" + android:pathData="M200,840Q167,840 143.5,816.5Q120,793 120,760L120,200Q120,167 143.5,143.5Q167,120 200,120L760,120Q793,120 816.5,143.5Q840,167 840,200L840,760Q840,793 816.5,816.5Q793,840 760,840L200,840ZM200,760L330,760L330,580L320,580Q303,580 291.5,568.5Q280,557 280,540L280,200L200,200Q200,200 200,200Q200,200 200,200L200,760Q200,760 200,760Q200,760 200,760ZM630,760L760,760Q760,760 760,760Q760,760 760,760L760,200Q760,200 760,200Q760,200 760,200L680,200L680,540Q680,557 668.5,568.5Q657,580 640,580L630,580L630,760ZM390,760L570,760L570,580L560,580Q543,580 531.5,568.5Q520,557 520,540L520,200L440,200L440,540Q440,557 428.5,568.5Q417,580 400,580L390,580L390,760Z" /> +</vector>
\ No newline at end of file diff --git a/core/res/res/drawable/notification_progress.xml b/core/res/res/drawable/notification_progress.xml new file mode 100644 index 000000000000..3a6b60077045 --- /dev/null +++ b/core/res/res/drawable/notification_progress.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@id/background" + android:gravity="center_vertical|fill_horizontal"> + <com.android.internal.widget.NotificationProgressDrawable + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:segSegGap="@dimen/notification_progress_segSeg_gap" + android:segPointGap="@dimen/notification_progress_segPoint_gap"> + <segments + android:color="?attr/colorProgressBackgroundNormal" + android:dashGap="@dimen/notification_progress_segments_dash_gap" + android:dashWidth="@dimen/notification_progress_segments_dash_width" + android:width="@dimen/notification_progress_segments_height" /> + <points + android:color="?attr/colorProgressBackgroundNormal" + android:radius="@dimen/notification_progress_points_radius" + android:cornerRadius="@dimen/notification_progress_points_corner_radius" + android:inset="@dimen/notification_progress_points_inset" /> + </com.android.internal.widget.NotificationProgressDrawable> + </item> +</layer-list> diff --git a/core/res/res/layout/notification_template_material_progress.xml b/core/res/res/layout/notification_template_material_progress.xml index fdcefccdbf16..75827a279ff7 100644 --- a/core/res/res/layout/notification_template_material_progress.xml +++ b/core/res/res/layout/notification_template_material_progress.xml @@ -75,12 +75,11 @@ /> - <com.android.internal.widget.NotificationProgressBar - android:id="@+id/progress" + <include android:layout_width="0dp" - android:layout_height="@dimen/notification_progress_bar_height" - style="@style/Widget.Material.Light.ProgressBar.Horizontal" android:layout_weight="1" + android:layout_height="@dimen/notification_progress_tracker_height" + layout="@layout/notification_template_notification_progress_bar" /> <com.android.internal.widget.CachingIconView diff --git a/packages/SystemUI/res/layout/udfps_bp_view.xml b/core/res/res/layout/notification_template_notification_progress_bar.xml index f1c55ef16cdc..35748962cfb2 100644 --- a/packages/SystemUI/res/layout/udfps_bp_view.xml +++ b/core/res/res/layout/notification_template_notification_progress_bar.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ Copyright (C) 2021 The Android Open Source Project + ~ Copyright (C) 2014 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. @@ -12,11 +12,12 @@ ~ 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. + ~ limitations under the License --> -<com.android.systemui.biometrics.UdfpsBpView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/udfps_animation_view" + +<com.android.internal.widget.NotificationProgressBar xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@android:id/progress" android:layout_width="match_parent" - android:layout_height="match_parent"> -</com.android.systemui.biometrics.UdfpsBpView> + android:layout_height="@dimen/notification_progress_tracker_height" + style="@style/Widget.Material.Notification.NotificationProgressBar" + /> diff --git a/core/res/res/values-watch-v36/colors.xml b/core/res/res/values-watch-v36/colors.xml new file mode 100644 index 000000000000..4bc2a66fa206 --- /dev/null +++ b/core/res/res/values-watch-v36/colors.xml @@ -0,0 +1,18 @@ +<!-- + ~ Copyright (C) 2024 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. + --> +<!-- TODO(b/372524566): update color token's value to match material3 design. --> +<resources> +</resources>
\ No newline at end of file diff --git a/core/res/res/values-watch-v36/config.xml b/core/res/res/values-watch-v36/config.xml new file mode 100644 index 000000000000..c8f347afb318 --- /dev/null +++ b/core/res/res/values-watch-v36/config.xml @@ -0,0 +1,20 @@ +<!-- + ~ Copyright (C) 2024 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. + --> + +<resources> + <!-- Overrides system value --> + <dimen name="config_buttonCornerRadius">26dp</dimen> +</resources> diff --git a/core/res/res/values-watch-v36/dimens_material.xml b/core/res/res/values-watch-v36/dimens_material.xml new file mode 100644 index 000000000000..ad3c1a3ef3a1 --- /dev/null +++ b/core/res/res/values-watch-v36/dimens_material.xml @@ -0,0 +1,28 @@ +<!-- + ~ Copyright (C) 2024 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. + --> +<resources> + <!-- values for material3 button --> + <dimen name="btn_material_width">172dp</dimen> + <dimen name="btn_material_height">52dp</dimen> + <dimen name="btn_horizontal_edge_padding">14dp</dimen> + <dimen name="btn_drawable_padding">6dp</dimen> + <dimen name="btn_lineHeight">18sp</dimen> + <dimen name="btn_textSize">15sp</dimen> + + <!-- Opacity factor for disabled material3 widget --> + <dimen name="disabled_alpha_device_default">0.12</dimen> + <dimen name="primary_content_alpha_device_default">0.38</dimen> +</resources> diff --git a/core/res/res/values-watch-v36/styles_material.xml b/core/res/res/values-watch-v36/styles_material.xml new file mode 100644 index 000000000000..32a22bb755cb --- /dev/null +++ b/core/res/res/values-watch-v36/styles_material.xml @@ -0,0 +1,58 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ Copyright (C) 2024 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. + --> + +<resources> + <!-- Button Styles --> + <!-- Material Button - Filled --> + <style name="Widget.DeviceDefault.Button.Filled" parent="Widget.DeviceDefault.Button"> + <item name="android:background">@drawable/btn_background_material_filled</item> + <item name="textAppearance">@style/TextAppearance.Widget.Button.Material.Filled</item> + </style> + + <!-- Material Button - Filled Tonal(Override system default button styles) --> + <style name="Widget.DeviceDefault.Button"> + <item name="background">@drawable/btn_background_material_filled_tonal</item> + <item name="textAppearance">@style/TextAppearance.Widget.Button.Material</item> + <item name="minHeight">@dimen/btn_material_height</item> + <item name="maxWidth">@dimen/btn_material_width</item> + <item name="android:paddingStart">@dimen/btn_horizontal_edge_padding</item> + <item name="android:paddingEnd">@dimen/btn_horizontal_edge_padding</item> + <item name="android:drawablePadding">@dimen/btn_drawable_padding</item> + <item name="android:maxLines">2</item> + <item name="android:ellipsize">end</item> + <item name="android:breakStrategy">simple</item> + <item name="stateListAnimator">@anim/button_state_list_anim_material</item> + <item name="focusable">true</item> + <item name="clickable">true</item> + <item name="gravity">center_vertical</item> + </style> + + <!-- Text Styles --> + <!-- TextAppearance for Material Button - Filled --> + <style name="TextAppearance.Widget.Button.Material.Filled" parent="TextAppearance.Widget.Button.Material"> + <item name="textColor">@color/btn_material_filled_text_color</item> + </style> + + <!-- TextAppearance for Material Button - Filled Tonal --> + <style name="TextAppearance.Widget.Button.Material" parent="TextAppearance.DeviceDefault"> + <item name="android:fontFamily">font-family-flex-device-default</item> + <item name="android:fontVariationSettings">"'wdth' 90, 'wght' 500, 'ROND' 100, 'opsz' 15, 'GRAD' 0"</item> + <item name="textSize">@dimen/btn_textSize</item> + <item name="textColor">@color/btn_material_filled_tonal_text_color</item> + <item name="lineHeight">@dimen/btn_lineHeight</item> + </style> +</resources>
\ No newline at end of file diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 8a2d767e9ad1..91b482051065 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -287,6 +287,11 @@ vibration params. --> <integer name="config_requestVibrationParamsTimeout">50</integer> + <!-- The max duration (in milliseconds) that the vibrator service will allow effects to be + pipelined (i.e. service will wait for ongoing vibration to finish instead of cancelling it + to start the new one). Value should be positive. Zero will disable effect pipelining. --> + <integer name="config_vibrationPipelineMaxDuration">25</integer> + <!-- Array containing the usages that should request vibration params before they are played. These usages don't have strong latency requirements, e.g. ringtone and notification, and can be slightly delayed. --> @@ -4447,17 +4452,25 @@ <string-array translatable="false" name="config_defaultPinnerServiceFiles"> </string-array> - <!-- True if camera app should be pinned via Pinner Service --> + <!-- Note: This config is deprecated, use config_pinnerCameraPinBytes instead. + True if camera app should be pinned via Pinner Service --> <bool name="config_pinnerCameraApp">false</bool> - <!-- Bytes that the PinnerService will pin for Home app --> - <integer name="config_pinnerHomePinBytes">0</integer> + <!-- Default: 60 MB. Bytes that the PinnerService will pin for Home app --> + <integer name="config_pinnerHomePinBytes">62914560</integer> + + <!-- Default: 80 MB. Bytes that the PinnerService will pin for Camera app --> + <integer name="config_pinnerCameraPinBytes">83886080</integer> - <!-- True if assistant app should be pinned via Pinner Service --> + <!-- Note: This config is deprecated, use config_pinnerAssistantPinBytes instead. + True if assistant app should be pinned via Pinner Service --> <bool name="config_pinnerAssistantApp">false</bool> - <!-- Bytes that the PinnerService will pin for WebView --> - <integer name="config_pinnerWebviewPinBytes">0</integer> + <!-- Default: 60 MB. Bytes that the PinnerService will pin for Assistant --> + <integer name="config_pinnerAssistantPinBytes">62914560</integer> + + <!-- Default: 20 MB. Bytes that the PinnerService will pin for WebView --> + <integer name="config_pinnerWebviewPinBytes">20971520</integer> <!-- Maximum memory that PinnerService will pin for apps expressed as a percentage of total device memory [0,100]. diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml index b92aa2f355ed..7184d9a8c890 100644 --- a/core/res/res/values/dimens.xml +++ b/core/res/res/values/dimens.xml @@ -817,6 +817,22 @@ <dimen name="notification_progress_tracker_width">40dp</dimen> <!-- The size of the progress tracker height --> <dimen name="notification_progress_tracker_height">20dp</dimen> + <!-- The gap between segments in the notification progress bar --> + <dimen name="notification_progress_segSeg_gap">2dp</dimen> + <!-- The gap between a segment and a point in the notification progress bar --> + <dimen name="notification_progress_segPoint_gap">4dp</dimen> + <!-- The dash gap of the notification progress bar segments --> + <dimen name="notification_progress_segments_dash_gap">9dp</dimen> + <!-- The dash width of the notification progress bar segments --> + <dimen name="notification_progress_segments_dash_width">3dp</dimen> + <!-- The height of the notification progress bar segments --> + <dimen name="notification_progress_segments_height">6dp</dimen> + <!-- The radius of the notification progress bar points --> + <dimen name="notification_progress_points_radius">10dp</dimen> + <!-- The corner radius of the notification progress bar points drawn as rects --> + <dimen name="notification_progress_points_corner_radius">4dp</dimen> + <!-- The inset of the notification progress bar points drawn as rects --> + <dimen name="notification_progress_points_inset">0dp</dimen> <!-- The maximum size of the small notification icon on low memory devices. --> <dimen name="notification_small_icon_size_low_ram">@dimen/notification_small_icon_size</dimen> diff --git a/core/res/res/values/styles_material.xml b/core/res/res/values/styles_material.xml index eec6ae3fb521..cb8e4aa9b2a9 100644 --- a/core/res/res/values/styles_material.xml +++ b/core/res/res/values/styles_material.xml @@ -502,6 +502,10 @@ please see styles_device_defaults.xml. <style name="Widget.Material.Notification.ProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal" /> + <style name="Widget.Material.Notification.NotificationProgressBar" parent="Widget.Material.Light.ProgressBar.Horizontal"> + <item name="progressDrawable">@drawable/notification_progress</item> + </style> + <style name="Widget.Material.Notification.Text" parent="Widget.Material.Light.TextView"> <item name="lineHeight">20sp</item> <item name="textAppearance">@style/TextAppearance.Material.Notification</item> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 4f63fac96a8d..0348b4685a66 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -2141,6 +2141,7 @@ <java-symbol type="integer" name="config_vibrationWaveformRampStepDuration" /> <java-symbol type="bool" name="config_ignoreVibrationsOnWirelessCharger" /> <java-symbol type="integer" name="config_vibrationWaveformRampDownDuration" /> + <java-symbol type="integer" name="config_vibrationPipelineMaxDuration" /> <java-symbol type="integer" name="config_radioScanningTimeout" /> <java-symbol type="integer" name="config_requestVibrationParamsTimeout" /> <java-symbol type="array" name="config_requestVibrationParamsForUsages" /> @@ -3471,6 +3472,8 @@ <java-symbol type="array" name="config_defaultPinnerServiceFiles" /> <java-symbol type="bool" name="config_pinnerCameraApp" /> <java-symbol type="integer" name="config_pinnerHomePinBytes" /> + <java-symbol type="integer" name="config_pinnerCameraPinBytes" /> + <java-symbol type="integer" name="config_pinnerAssistantPinBytes" /> <java-symbol type="bool" name="config_pinnerAssistantApp" /> <java-symbol type="integer" name="config_pinnerWebviewPinBytes" /> <java-symbol type="integer" name="config_pinnerMaxPinnedMemoryPercentage" /> @@ -3864,6 +3867,14 @@ <java-symbol type="dimen" name="notification_progress_icon_size" /> <java-symbol type="dimen" name="notification_progress_tracker_width" /> <java-symbol type="dimen" name="notification_progress_tracker_height" /> + <java-symbol type="dimen" name="notification_progress_segSeg_gap" /> + <java-symbol type="dimen" name="notification_progress_segPoint_gap" /> + <java-symbol type="dimen" name="notification_progress_segments_dash_gap" /> + <java-symbol type="dimen" name="notification_progress_segments_dash_width" /> + <java-symbol type="dimen" name="notification_progress_segments_height" /> + <java-symbol type="dimen" name="notification_progress_points_radius" /> + <java-symbol type="dimen" name="notification_progress_points_corner_radius" /> + <java-symbol type="dimen" name="notification_progress_points_inset" /> <java-symbol type="dimen" name="notification_small_icon_size_low_ram"/> <java-symbol type="dimen" name="notification_big_picture_max_height_low_ram"/> diff --git a/core/tests/coretests/src/android/app/NotificationTest.java b/core/tests/coretests/src/android/app/NotificationTest.java index 48e26203fab4..23a09857032c 100644 --- a/core/tests/coretests/src/android/app/NotificationTest.java +++ b/core/tests/coretests/src/android/app/NotificationTest.java @@ -2397,10 +2397,25 @@ public class NotificationTest { public void progressStyle_getProgressMax_returnsSumOfSegmentLength() { final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); progressStyle + .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(15), + new Notification.ProgressStyle.Segment(25))) .addProgressSegment(new Notification.ProgressStyle.Segment(10)) .addProgressSegment(new Notification.ProgressStyle.Segment(20)); - assertThat(progressStyle.getProgressMax()).isEqualTo(30); + assertThat(progressStyle.getProgressMax()).isEqualTo(70); + } + + @Test + @EnableFlags(Flags.FLAG_API_RICH_ONGOING) + public void progressStyle_getProgressMax_onSetProgressSegments_resets() { + final Notification.ProgressStyle progressStyle = new Notification.ProgressStyle(); + progressStyle + .addProgressSegment(new Notification.ProgressStyle.Segment(10)) + .addProgressSegment(new Notification.ProgressStyle.Segment(20)) + .setProgressSegments(List.of(new Notification.ProgressStyle.Segment(15), + new Notification.ProgressStyle.Segment(25))); + + assertThat(progressStyle.getProgressMax()).isEqualTo(40); } @Test diff --git a/core/tests/coretests/src/android/app/OWNERS b/core/tests/coretests/src/android/app/OWNERS index 5636f9bc436c..6d14ccb18c53 100644 --- a/core/tests/coretests/src/android/app/OWNERS +++ b/core/tests/coretests/src/android/app/OWNERS @@ -14,3 +14,5 @@ per-file KeyguardManagerTest.java = file:/services/core/java/com/android/server/ # Files related to background activity launches per-file Background*Start* = file:/BAL_OWNERS +# Files related to caching +per-file PropertyInvalidatedCache* = file:/PERFORMANCE_OWNERS diff --git a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java index 987f68d4f9e1..80255c5f6600 100644 --- a/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java +++ b/core/tests/coretests/src/android/content/pm/verify/VerificationSessionTest.java @@ -16,6 +16,9 @@ package android.content.pm.verify; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_WARN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; @@ -23,6 +26,8 @@ import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import android.content.pm.SharedLibraryInfo; @@ -73,6 +78,7 @@ public class VerificationSessionTest { private static final long TEST_EXTEND_TIME = 2000L; private static final String TEST_KEY = "test key"; private static final String TEST_VALUE = "test value"; + private static final int TEST_POLICY = VERIFICATION_POLICY_BLOCK_FAIL_CLOSED; private final ArrayList<SharedLibraryInfo> mTestDeclaredLibraries = new ArrayList<>(); private final PersistableBundle mTestExtensionParams = new PersistableBundle(); @@ -90,7 +96,7 @@ public class VerificationSessionTest { mTestExtensionParams.putString(TEST_KEY, TEST_VALUE); mTestSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO, mTestDeclaredLibraries, - mTestExtensionParams, mTestSessionInterface, mTestCallback); + mTestExtensionParams, TEST_POLICY, mTestSessionInterface, mTestCallback); } @Test @@ -118,6 +124,7 @@ public class VerificationSessionTest { // structure is different, but all the key/value pairs should be preserved as before. assertThat(sessionFromParcel.getExtensionParams().getString(TEST_KEY)) .isEqualTo(mTestExtensionParams.getString(TEST_KEY)); + assertThat(sessionFromParcel.getVerificationPolicy()).isEqualTo(TEST_POLICY); } @Test @@ -152,4 +159,42 @@ public class VerificationSessionTest { verify(mTestCallback, times(1)).reportVerificationIncomplete( eq(TEST_ID), eq(reason)); } + + @Test + public void testPolicyNoOverride() { + assertThat(mTestSession.getVerificationPolicy()).isEqualTo(TEST_POLICY); + // This "set" is a no-op + assertThat(mTestSession.setVerificationPolicy(TEST_POLICY)).isTrue(); + assertThat(mTestSession.getVerificationPolicy()).isEqualTo(TEST_POLICY); + verifyZeroInteractions(mTestSessionInterface); + } + + @Test + public void testPolicyOverrideFail() throws Exception { + final int newPolicy = VERIFICATION_POLICY_BLOCK_FAIL_WARN; + when(mTestSessionInterface.setVerificationPolicy(anyInt(), anyInt())).thenReturn(false); + assertThat(mTestSession.setVerificationPolicy(newPolicy)).isFalse(); + verify(mTestSessionInterface, times(1)) + .setVerificationPolicy(eq(TEST_ID), eq(newPolicy)); + // Next "get" should not trigger binder call because the previous "set" has failed + assertThat(mTestSession.getVerificationPolicy()).isEqualTo(TEST_POLICY); + verifyNoMoreInteractions(mTestSessionInterface); + } + + @Test + public void testPolicyOverrideSuccess() throws Exception { + final int newPolicy = VERIFICATION_POLICY_BLOCK_FAIL_WARN; + when(mTestSessionInterface.setVerificationPolicy(anyInt(), anyInt())).thenReturn(true); + assertThat(mTestSession.setVerificationPolicy(newPolicy)).isTrue(); + verify(mTestSessionInterface, times(1)) + .setVerificationPolicy(eq(TEST_ID), eq(newPolicy)); + assertThat(mTestSession.getVerificationPolicy()).isEqualTo(newPolicy); + assertThat(mTestSession.getVerificationPolicy()).isEqualTo(newPolicy); + + // Setting back to the original policy should still trigger binder calls + assertThat(mTestSession.setVerificationPolicy(TEST_POLICY)).isTrue(); + verify(mTestSessionInterface, times(1)) + .setVerificationPolicy(eq(TEST_ID), eq(TEST_POLICY)); + assertThat(mTestSession.getVerificationPolicy()).isEqualTo(TEST_POLICY); + } } diff --git a/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java index 7f73a1eb4b48..7807c8a94530 100644 --- a/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java +++ b/core/tests/coretests/src/android/content/pm/verify/VerifierServiceTest.java @@ -16,6 +16,8 @@ package android.content.pm.verify; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.eq; @@ -52,6 +54,7 @@ public class VerifierServiceTest { private static final String TEST_PACKAGE_NAME = "com.foo"; private static final Uri TEST_PACKAGE_URI = Uri.parse("test://test"); private static final SigningInfo TEST_SIGNING_INFO = new SigningInfo(); + private static final int TEST_POLICY = VERIFICATION_POLICY_BLOCK_FAIL_CLOSED; private VerifierService mService; private VerificationSession mSession; @@ -60,8 +63,7 @@ public class VerifierServiceTest { mService = Mockito.mock(VerifierService.class, Answers.CALLS_REAL_METHODS); mSession = new VerificationSession(TEST_ID, TEST_INSTALL_SESSION_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, TEST_SIGNING_INFO, - new ArrayList<>(), - new PersistableBundle(), null, null); + new ArrayList<>(), new PersistableBundle(), TEST_POLICY, null, null); } @Test diff --git a/core/tests/coretests/src/android/os/OWNERS b/core/tests/coretests/src/android/os/OWNERS index 1c0073417c9a..6149382d0800 100644 --- a/core/tests/coretests/src/android/os/OWNERS +++ b/core/tests/coretests/src/android/os/OWNERS @@ -9,3 +9,6 @@ per-file PowerManager*.java = file:/services/core/java/com/android/server/power/ # PerformanceHintManager per-file PerformanceHintManagerTest.java = file:/ADPF_OWNERS + +# Caching +per-file IpcDataCache* = file:/PERFORMANCE_OWNERS diff --git a/core/tests/coretests/src/android/text/LayoutTest.java b/core/tests/coretests/src/android/text/LayoutTest.java index 25f9cb7c1088..6311298ec8cf 100644 --- a/core/tests/coretests/src/android/text/LayoutTest.java +++ b/core/tests/coretests/src/android/text/LayoutTest.java @@ -42,6 +42,7 @@ import android.text.Layout.Alignment; import android.text.style.ForegroundColorSpan; import android.text.style.StrikethroughSpan; +import androidx.annotation.NonNull; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; @@ -697,7 +698,7 @@ public class LayoutTest { if (drawCommand.path != null) { expect.that(drawCommand.path).isEqualTo(selectionPath); - expect.that(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW); + expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.YELLOW); expect.that(drawCommand.paint.getBlendMode()).isNotNull(); highlightsFound++; } else if (drawCommand.text != null) { @@ -750,7 +751,7 @@ public class LayoutTest { if (drawCommand.path != null) { expect.that(drawCommand.path).isEqualTo(selectionPath); - expect.that(drawCommand.paint.getColor()).isEqualTo(Color.YELLOW); + expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.YELLOW); expect.that(drawCommand.paint.getBlendMode()).isNotNull(); highlightsFound++; } else if (drawCommand.text != null) { @@ -802,7 +803,7 @@ public class LayoutTest { if (drawCommand.path != null) { expect.that(drawCommand.path).isEqualTo(selectionPath); - expect.that(drawCommand.paint.getColor()).isEqualTo(Color.CYAN); + expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.CYAN); expect.that(drawCommand.paint.getBlendMode()).isNull(); highlightsFound++; } else if (drawCommand.text != null) { @@ -855,7 +856,7 @@ public class LayoutTest { if (drawCommand.path != null) { expect.that(drawCommand.path).isEqualTo(selectionPath); - expect.that(drawCommand.paint.getColor()).isEqualTo(Color.CYAN); + expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.CYAN); expect.that(drawCommand.paint.getBlendMode()).isNull(); highlightsFound++; } else if (drawCommand.text != null) { @@ -914,7 +915,7 @@ public class LayoutTest { if (drawCommand.rect != null) { numBackgroundsFound++; - expect.that(drawCommand.paint.getColor()).isEqualTo(Color.BLACK); + expect.that(removeAlpha(drawCommand.paint.getColor())).isEqualTo(Color.BLACK); expect.that(drawCommand.rect.height()).isAtLeast(LINE_HEIGHT); expect.that(drawCommand.rect.width()).isGreaterThan(0); float expectedY = (numBackgroundsFound) * (LINE_HEIGHT + LINE_DESCENT); @@ -997,20 +998,38 @@ public class LayoutTest { .filter(it -> it.rect != null) .toList(); - expect.that(backgroundCommands.get(0).paint.getColor()).isEqualTo(Color.BLACK); - expect.that(backgroundCommands.get(1).paint.getColor()).isEqualTo(Color.WHITE); - expect.that(backgroundCommands.get(2).paint.getColor()).isEqualTo(Color.WHITE); - expect.that(backgroundCommands.get(3).paint.getColor()).isEqualTo(Color.WHITE); - expect.that(backgroundCommands.get(4).paint.getColor()).isEqualTo(Color.WHITE); - expect.that(backgroundCommands.get(5).paint.getColor()).isEqualTo(Color.BLACK); - expect.that(backgroundCommands.get(6).paint.getColor()).isEqualTo(Color.BLACK); - expect.that(backgroundCommands.get(7).paint.getColor()).isEqualTo(Color.BLACK); - expect.that(backgroundCommands.get(8).paint.getColor()).isEqualTo(Color.BLACK); - expect.that(backgroundCommands.get(9).paint.getColor()).isEqualTo(Color.BLACK); + expect.that(removeAlpha(backgroundCommands.get(0).paint.getColor())) + .isEqualTo(Color.BLACK); + expect.that(removeAlpha(backgroundCommands.get(1).paint.getColor())) + .isEqualTo(Color.WHITE); + expect.that(removeAlpha(backgroundCommands.get(2).paint.getColor())) + .isEqualTo(Color.WHITE); + expect.that(removeAlpha(backgroundCommands.get(3).paint.getColor())) + .isEqualTo(Color.WHITE); + expect.that(removeAlpha(backgroundCommands.get(4).paint.getColor())) + .isEqualTo(Color.WHITE); + expect.that(removeAlpha(backgroundCommands.get(5).paint.getColor())) + .isEqualTo(Color.BLACK); + expect.that(removeAlpha(backgroundCommands.get(6).paint.getColor())) + .isEqualTo(Color.BLACK); + expect.that(removeAlpha(backgroundCommands.get(7).paint.getColor())) + .isEqualTo(Color.BLACK); + expect.that(removeAlpha(backgroundCommands.get(8).paint.getColor())) + .isEqualTo(Color.BLACK); + expect.that(removeAlpha(backgroundCommands.get(9).paint.getColor())) + .isEqualTo(Color.BLACK); expect.that(backgroundCommands.size()).isEqualTo(backgroundRectsDrawn); } + private int removeAlpha(int color) { + return Color.rgb( + Color.red(color), + Color.green(color), + Color.blue(color) + ); + } + private static final class MockCanvas extends Canvas { static class DrawCommand { @@ -1122,6 +1141,11 @@ public class LayoutTest { mDrawCommands.add(new DrawCommand(rect, p)); } + @Override + public void drawRoundRect(@NonNull RectF rect, float rx, float ry, @NonNull Paint paint) { + mDrawCommands.add(new DrawCommand(rect, paint)); + } + List<DrawCommand> getDrawCommands() { return mDrawCommands; } diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java index 632721126714..ed9fc1c9e547 100644 --- a/core/tests/coretests/src/android/view/ViewRootImplTest.java +++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java @@ -71,6 +71,7 @@ import android.graphics.Rect; import android.hardware.display.DisplayManagerGlobal; import android.os.Binder; import android.os.SystemProperties; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; @@ -82,6 +83,7 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.WindowInsets.Side; import android.view.WindowInsets.Type; +import android.view.accessibility.AccessibilityManager; import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -1628,6 +1630,42 @@ public class ViewRootImplTest { }); } + @Test + @EnableFlags(android.view.accessibility.Flags.FLAG_FOCUS_RECT_MIN_SIZE) + public void testAdjustAccessibilityFocusedBounds_largeEnoughBoundsAreUnchanged() { + final int strokeWidth = sContext.getSystemService(AccessibilityManager.class) + .getAccessibilityFocusStrokeWidth(); + final int left, top, width, height; + left = top = 100; + width = height = strokeWidth * 2; + final Rect bounds = new Rect(left, top, left + width, top + height); + final Rect originalBounds = new Rect(bounds); + + mViewRootImpl.adjustAccessibilityFocusedRectBoundsIfNeeded(bounds); + + assertThat(bounds).isEqualTo(originalBounds); + } + + @Test + @EnableFlags(android.view.accessibility.Flags.FLAG_FOCUS_RECT_MIN_SIZE) + public void testAdjustAccessibilityFocusedBounds_smallBoundsAreExpanded() { + final int strokeWidth = sContext.getSystemService(AccessibilityManager.class) + .getAccessibilityFocusStrokeWidth(); + final int left, top, width, height; + left = top = 100; + width = height = strokeWidth; + final Rect bounds = new Rect(left, top, left + width, top + height); + final Rect originalBounds = new Rect(bounds); + + mViewRootImpl.adjustAccessibilityFocusedRectBoundsIfNeeded(bounds); + + // Bounds should be centered on the same point, but expanded to at least strokeWidth * 2 + assertThat(bounds.centerX()).isEqualTo(originalBounds.centerX()); + assertThat(bounds.centerY()).isEqualTo(originalBounds.centerY()); + assertThat(bounds.width()).isAtLeast(strokeWidth * 2); + assertThat(bounds.height()).isAtLeast(strokeWidth * 2); + } + private boolean setForceDarkSysProp(boolean isForceDarkEnabled) { try { SystemProperties.set( diff --git a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java index 6419c1e07f2e..79a478a7676b 100644 --- a/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java +++ b/core/tests/coretests/src/com/android/internal/widget/NotificationProgressBarTest.java @@ -254,7 +254,6 @@ public class NotificationProgressBarTest { // Colors with 50% opacity int fadedGreen = 0x7F00FF00; - int fadedBlue = 0x7F0000FF; int fadedYellow = 0x7FFFFF00; List<Part> expected = new ArrayList<>(List.of( diff --git a/core/tests/vibrator/src/android/os/CombinedVibrationTest.java b/core/tests/vibrator/src/android/os/CombinedVibrationTest.java index 244fcff7d27d..37ddfd21c98d 100644 --- a/core/tests/vibrator/src/android/os/CombinedVibrationTest.java +++ b/core/tests/vibrator/src/android/os/CombinedVibrationTest.java @@ -22,6 +22,9 @@ import static junit.framework.Assert.assertTrue; import static org.testng.Assert.assertThrows; +import android.hardware.vibrator.IVibrator; +import android.util.SparseArray; + import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -134,6 +137,54 @@ public class CombinedVibrationTest { } @Test + public void testDurationMono_withVibratorSupportingPrimitives() { + SparseArray<VibratorInfo> infos = new SparseArray<>(2); + infos.put(1, new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 5) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 5) + .build()); + infos.put(2, new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1) + .build()); + + // Use max duration from all vibrators. + assertEquals(10, CombinedVibration.createParallel( + VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration(infos)); + assertEquals(111, CombinedVibration.createParallel( + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .getDuration(infos)); + } + + @Test + public void testDurationMono_withVibratorNotSupportingPrimitives() { + SparseArray<VibratorInfo> infos = new SparseArray<>(2); + infos.put(1, new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) + .build()); + infos.put(2, new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1) + .build()); + + // Use max duration from all vibrators. + assertEquals(-1, CombinedVibration.createParallel( + VibrationEffect.get(VibrationEffect.EFFECT_CLICK)).getDuration(infos)); + assertEquals(-1, CombinedVibration.createParallel( + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .getDuration(infos)); + } + + @Test public void testDurationStereo() { assertEquals(6, CombinedVibration.startParallel() .addVibrator(1, VibrationEffect.createOneShot(1, 1)) @@ -156,6 +207,75 @@ public class CombinedVibrationTest { } @Test + public void testDurationStereo_withVibratorSupportingPrimitives() { + SparseArray<VibratorInfo> infos = new SparseArray<>(2); + infos.put(1, new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 5) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 5) + .build()); + infos.put(2, new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1) + .build()); + + // Use specific vibrator durations, then max effect duration + assertEquals(111, CombinedVibration.startParallel() + .addVibrator(1, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .addVibrator(2, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .combine() + .getDuration(infos)); + assertEquals(110, CombinedVibration.startParallel() + .addVibrator(1, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .combine() + .getDuration(infos)); + } + + @Test + public void testDurationStereo_withVibratorNotSupportingPrimitives() { + SparseArray<VibratorInfo> infos = new SparseArray<>(2); + infos.put(1, new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) + .build()); + infos.put(2, new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1) + .build()); + + // One vibrator does not support primitives + assertEquals(-1, CombinedVibration.startParallel() + .addVibrator(1, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .addVibrator(2, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .combine() + .getDuration(infos)); + // Invalid vibrator ID + assertEquals(-1, CombinedVibration.startParallel() + .addVibrator(3, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .combine() + .getDuration(infos)); + } + + @Test public void testDurationSequential() { assertEquals(26, CombinedVibration.startSequential() .addNext(1, VibrationEffect.createOneShot(10, 10), 10) @@ -178,6 +298,59 @@ public class CombinedVibrationTest { } @Test + public void testDurationSequential_withVibratorSupportingPrimitives() { + SparseArray<VibratorInfo> infos = new SparseArray<>(2); + infos.put(1, new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 5) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 5) + .build()); + infos.put(2, new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1) + .build()); + + // Add each duration and delay + assertEquals(321, CombinedVibration.startSequential() + .addNext(1, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose(), 100) + .addNext(2, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .combine() + .getDuration(infos)); + } + + @Test + public void testDurationSequential_withVibratorNotSupportingPrimitives() { + SparseArray<VibratorInfo> infos = new SparseArray<>(2); + infos.put(1, new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) + .build()); + infos.put(2, new VibratorInfo.Builder(/* id= */ 2) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1) + .build()); + + assertEquals(-1, CombinedVibration.startSequential() + .addNext(1, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose(), 100) + .addNext(2, VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose()) + .combine() + .getDuration(infos)); + } + + @Test public void testIsHapticFeedbackCandidateMono() { assertTrue(CombinedVibration.createParallel( VibrationEffect.createOneShot(1, 1)).isHapticFeedbackCandidate()); diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java index f5b04ee759a5..8acf2ed87e95 100644 --- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java +++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java @@ -1078,6 +1078,52 @@ public class VibrationEffectTest { } @Test + public void testDuration_withVibratorSupportingPrimitives() { + VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 10) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 5) + .build(); + + VibrationEffect composition = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, 1, 100) + .compose(); + + assertEquals(1, VibrationEffect.createOneShot(1, 1).getDuration()); + assertEquals(10, VibrationEffect.get(VibrationEffect.EFFECT_CLICK).getDuration(info)); + assertEquals(115, composition.getDuration(info)); + assertEquals(Long.MAX_VALUE, + VibrationEffect.startComposition() + .repeatEffectIndefinitely(composition) + .compose() + .getDuration(info)); + if (Flags.vendorVibrationEffects()) { + assertEquals(-1, + VibrationEffect.createVendorEffect(createNonEmptyBundle()).getDuration(info)); + } + } + + @Test + public void testDuration_withVibratorNotSupportingPrimitives() { + VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) + .build(); + + VibrationEffect composition = VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .compose(); + + assertEquals(-1, VibrationEffect.get(VibrationEffect.EFFECT_CLICK).getDuration(info)); + assertEquals(-1, composition.getDuration(info)); + assertEquals(Long.MAX_VALUE, + VibrationEffect.startComposition() + .repeatEffectIndefinitely(composition) + .compose() + .getDuration(info)); + } + + @Test public void testAreVibrationFeaturesSupported_allSegmentsSupported() { VibratorInfo info = new VibratorInfo.Builder(/* id= */ 1) .setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL) diff --git a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java index 7dd9e55f8f3e..f9ec5f0b2305 100644 --- a/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/PrebakedSegmentTest.java @@ -24,6 +24,7 @@ import static junit.framework.Assert.assertTrue; import static org.testng.Assert.assertNotEquals; import static org.testng.Assert.assertThrows; +import android.hardware.vibrator.IVibrator; import android.os.Parcel; import android.os.VibrationEffect; import android.os.VibratorInfo; @@ -114,39 +115,82 @@ public class PrebakedSegmentTest { @Test public void testDuration() { - assertEquals(-1, new PrebakedSegment( - VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) - .getDuration()); - assertEquals(-1, new PrebakedSegment( - VibrationEffect.EFFECT_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) - .getDuration()); - assertEquals(-1, new PrebakedSegment( - VibrationEffect.EFFECT_DOUBLE_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) - .getDuration()); - assertEquals(-1, new PrebakedSegment( - VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_CLICK).getDuration()); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_TICK).getDuration()); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_THUD).getDuration()); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK) .getDuration()); } @Test + public void testDuration_withVibratorSupportingPrimitives_returnsPrimitiveDuration() { + int tickDuration = 5; + int clickDuration = 10; + int thudDuration = 15; + + VibratorInfo vibratorInfo = new VibratorInfo.Builder(/* id= */ 1) + .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_TICK, tickDuration) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, clickDuration) + .setSupportedPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD, thudDuration) + .build(); + + assertEquals(5, createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK) + .getDuration(vibratorInfo)); + assertEquals(10, createSegmentWithFallback(VibrationEffect.EFFECT_TICK) + .getDuration(vibratorInfo)); + assertEquals(10, createSegmentWithFallback(VibrationEffect.EFFECT_CLICK) + .getDuration(vibratorInfo)); + assertEquals(10, createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK) + .getDuration(vibratorInfo)); + assertEquals(20, createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK) + .getDuration(vibratorInfo)); + assertEquals(15, createSegmentWithFallback(VibrationEffect.EFFECT_THUD) + .getDuration(vibratorInfo)); + + // Unknown effects + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_POP) + .getDuration(vibratorInfo)); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.RINGTONES[0]) + .getDuration(vibratorInfo)); + } + + @Test + public void testDuration_withVibratorNotSupportingPrimitives_returnsUnknown() { + VibratorInfo vibratorInfo = createVibratorInfoWithSupportedEffects( + VibrationEffect.EFFECT_CLICK, VibrationEffect.EFFECT_POP); + + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK) + .getDuration(vibratorInfo)); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_TICK) + .getDuration(vibratorInfo)); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_CLICK) + .getDuration(vibratorInfo)); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK) + .getDuration(vibratorInfo)); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK) + .getDuration(vibratorInfo)); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_THUD) + .getDuration(vibratorInfo)); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.EFFECT_POP) + .getDuration(vibratorInfo)); + assertEquals(-1, createSegmentWithFallback(VibrationEffect.RINGTONES[0]) + .getDuration(vibratorInfo)); + } + + @Test public void testIsHapticFeedbackCandidate_prebakedConstants_areCandidates() { - assertTrue(new PrebakedSegment( - VibrationEffect.EFFECT_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_CLICK) .isHapticFeedbackCandidate()); - assertTrue(new PrebakedSegment( - VibrationEffect.EFFECT_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TICK) .isHapticFeedbackCandidate()); - assertTrue(new PrebakedSegment( - VibrationEffect.EFFECT_DOUBLE_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_DOUBLE_CLICK) .isHapticFeedbackCandidate()); - assertTrue(new PrebakedSegment( - VibrationEffect.EFFECT_HEAVY_CLICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_HEAVY_CLICK) .isHapticFeedbackCandidate()); - assertTrue(new PrebakedSegment( - VibrationEffect.EFFECT_THUD, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_THUD) .isHapticFeedbackCandidate()); - assertTrue(new PrebakedSegment( - VibrationEffect.EFFECT_TEXTURE_TICK, true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + assertTrue(createSegmentWithFallback(VibrationEffect.EFFECT_TEXTURE_TICK) .isHapticFeedbackCandidate()); } @@ -271,8 +315,7 @@ public class PrebakedSegmentTest { @Test public void testIsHapticFeedbackCandidate_prebakedRingtones_notCandidates() { - assertFalse(new PrebakedSegment( - VibrationEffect.RINGTONES[1], true, VibrationEffect.EFFECT_STRENGTH_MEDIUM) + assertFalse(createSegmentWithFallback(VibrationEffect.RINGTONES[1]) .isHapticFeedbackCandidate()); } diff --git a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java index 97f1d5e77ddb..a6d9dc51d7bb 100644 --- a/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/PrimitiveSegmentTest.java @@ -201,6 +201,22 @@ public class PrimitiveSegmentTest { } @Test + public void testDuration_withVibratorSupportingPrimitives_returnsVibratorDurationWithDelay() { + VibratorInfo vibratorInfo = createVibratorInfoWithSupportedPrimitive( + VibrationEffect.Composition.PRIMITIVE_CLICK, /* durationMs= */ 10); + assertEquals(15, new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_CLICK, 1, 5).getDuration(vibratorInfo)); + } + + @Test + public void testDuration_withVibratorNotSupportingPrimitive_returnsUnknown() { + VibratorInfo vibratorInfo = createVibratorInfoWithSupportedPrimitive( + VibrationEffect.Composition.PRIMITIVE_CLICK); + assertEquals(-1, new PrimitiveSegment( + VibrationEffect.Composition.PRIMITIVE_NOOP, 1, 5).getDuration(vibratorInfo)); + } + + @Test public void testVibrationFeaturesSupport_primitiveSupportedByVibrator() { assertTrue(createSegment(VibrationEffect.Composition.PRIMITIVE_CLICK) .areVibrationFeaturesSupported( @@ -252,9 +268,14 @@ public class PrimitiveSegmentTest { } private static VibratorInfo createVibratorInfoWithSupportedPrimitive(int primitiveId) { + return createVibratorInfoWithSupportedPrimitive(primitiveId, /* durationMs= */ 10); + } + + private static VibratorInfo createVibratorInfoWithSupportedPrimitive(int primitiveId, + int durationMs) { return new VibratorInfo.Builder(/* id= */ 1) .setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS) - .setSupportedPrimitive(primitiveId, 10) + .setSupportedPrimitive(primitiveId, durationMs) .build(); } } diff --git a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java index bea82931dda7..df874bcb73ca 100644 --- a/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/RampSegmentTest.java @@ -195,7 +195,14 @@ public class RampSegmentTest { @Test public void testDuration() { + VibratorInfo infoWithSupport = + createVibInfo(/* hasAmplitudeControl= */ true, /* hasFrequencyControl= */ true); + VibratorInfo infoWithoutSupport = + createVibInfo(/* hasAmplitudeControl= */ false, /* hasFrequencyControl= */ false); + assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration()); + assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration(infoWithSupport)); + assertEquals(10, new RampSegment(0.5f, 1, 0, 0, 10).getDuration(infoWithoutSupport)); } @Test diff --git a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java index 411074a75e2e..914117c10c87 100644 --- a/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java +++ b/core/tests/vibrator/src/android/os/vibrator/StepSegmentTest.java @@ -213,7 +213,13 @@ public class StepSegmentTest { @Test public void testDuration() { + VibratorInfo infoWithSupport = createVibInfoForAmplitude(/* hasAmplitudeControl= */ true); + VibratorInfo infoWithoutSupport = + createVibInfoForAmplitude(/* hasAmplitudeControl= */ false); + assertEquals(5, new StepSegment(0, 0, 5).getDuration()); + assertEquals(5, new StepSegment(0, 0, 5).getDuration(infoWithSupport)); + assertEquals(5, new StepSegment(0, 0, 5).getDuration(infoWithoutSupport)); } @Test diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index 2e72f0ec90b8..4aa7e96f352d 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -572,6 +572,7 @@ applications that come with the platform <permission name="android.permission.READ_BLOCKED_NUMBERS" /> <!-- Permission required for CTS test - PackageManagerTest --> <permission name="android.permission.DOMAIN_VERIFICATION_AGENT"/> + <permission name="android.permission.VERIFICATION_AGENT"/> <!-- Permission required for CTS test CtsInputTestCases --> <permission name="android.permission.OVERRIDE_SYSTEM_KEY_BEHAVIOR_IN_FOCUSED_WINDOW" /> <!-- Permission required for CTS test - PackageManagerShellCommandInstallTest --> diff --git a/errorprone/Android.bp b/errorprone/Android.bp index c1d2235e3324..b559a15c3a60 100644 --- a/errorprone/Android.bp +++ b/errorprone/Android.bp @@ -22,6 +22,7 @@ java_library_host { static_libs: [ "annotations", + "jsr305", "framework-annotations-lib", "//external/error_prone:error_prone_core", ], diff --git a/errorprone/OWNERS b/errorprone/OWNERS index bddbdb364683..aa8c126a32e1 100644 --- a/errorprone/OWNERS +++ b/errorprone/OWNERS @@ -1,2 +1 @@ -jsharkey@android.com -jsharkey@google.com +colefaust@google.com diff --git a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java index 8dc9579e6b52..6d5e44844a83 100644 --- a/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java +++ b/errorprone/java/com/google/errorprone/bugpatterns/android/HideInCommentsChecker.java @@ -29,6 +29,7 @@ import com.google.errorprone.bugpatterns.BugChecker; import com.google.errorprone.fixes.SuggestedFix; import com.google.errorprone.matchers.Description; import com.google.errorprone.util.ASTHelpers; +import com.google.errorprone.util.ErrorProneComment; import com.google.errorprone.util.ErrorProneToken; import com.google.errorprone.util.ErrorProneTokens; import com.sun.source.tree.ClassTree; @@ -37,7 +38,6 @@ import com.sun.source.tree.MethodTree; import com.sun.source.tree.NewClassTree; import com.sun.source.tree.Tree; import com.sun.source.tree.VariableTree; -import com.sun.tools.javac.parser.Tokens; import java.util.HashMap; import java.util.Map; @@ -66,7 +66,7 @@ public class HideInCommentsChecker extends BugChecker implements final Map<Integer, Tree> javadocableTrees = findJavadocableTrees(tree, state); final String sourceCode = state.getSourceCode().toString(); for (ErrorProneToken token : ErrorProneTokens.getTokens(sourceCode, state.context)) { - for (Tokens.Comment comment : token.comments()) { + for (ErrorProneComment comment : token.comments()) { if (!javadocableTrees.containsKey(token.pos())) { continue; } @@ -81,7 +81,7 @@ public class HideInCommentsChecker extends BugChecker implements return NO_MATCH; } - private static Optional<SuggestedFix> generateFix(Tokens.Comment comment) { + private static Optional<SuggestedFix> generateFix(ErrorProneComment comment) { final String text = comment.getText(); if (text.startsWith("/**")) { return Optional.empty(); diff --git a/graphics/java/android/graphics/text/PositionedGlyphs.java b/graphics/java/android/graphics/text/PositionedGlyphs.java index 671eb6e514c5..ed17fdefcb53 100644 --- a/graphics/java/android/graphics/text/PositionedGlyphs.java +++ b/graphics/java/android/graphics/text/PositionedGlyphs.java @@ -26,6 +26,7 @@ import android.graphics.Typeface; import android.graphics.fonts.Font; import com.android.internal.util.Preconditions; +import com.android.text.flags.Flags; import dalvik.annotation.optimization.CriticalNative; @@ -132,6 +133,9 @@ public final class PositionedGlyphs { @NonNull public Font getFont(@IntRange(from = 0) int index) { Preconditions.checkArgumentInRange(index, 0, glyphCount() - 1, "index"); + if (Flags.typefaceRedesign()) { + return mFonts.get(nGetFontId(mLayoutPtr, index)); + } return mFonts.get(index); } @@ -245,20 +249,29 @@ public final class PositionedGlyphs { */ public PositionedGlyphs(long layoutPtr, float xOffset, float yOffset) { mLayoutPtr = layoutPtr; - int glyphCount = nGetGlyphCount(layoutPtr); - mFonts = new ArrayList<>(glyphCount); mXOffset = xOffset; mYOffset = yOffset; - long prevPtr = 0; - Font prevFont = null; - for (int i = 0; i < glyphCount; ++i) { - long ptr = nGetFont(layoutPtr, i); - if (prevPtr != ptr) { - prevPtr = ptr; - prevFont = new Font(ptr); + if (Flags.typefaceRedesign()) { + int fontCount = nGetFontCount(layoutPtr); + mFonts = new ArrayList<>(fontCount); + for (int i = 0; i < fontCount; ++i) { + mFonts.add(new Font(nGetFontRef(layoutPtr, i))); + } + } else { + int glyphCount = nGetGlyphCount(layoutPtr); + mFonts = new ArrayList<>(glyphCount); + + long prevPtr = 0; + Font prevFont = null; + for (int i = 0; i < glyphCount; ++i) { + long ptr = nGetFont(layoutPtr, i); + if (prevPtr != ptr) { + prevPtr = ptr; + prevFont = new Font(ptr); + } + mFonts.add(prevFont); } - mFonts.add(prevFont); } NoImagePreloadHolder.REGISTRY.registerNativeAllocation(this, layoutPtr); @@ -290,6 +303,12 @@ public final class PositionedGlyphs { private static native float nGetWeightOverride(long minikinLayout, int i); @CriticalNative private static native float nGetItalicOverride(long minikinLayout, int i); + @CriticalNative + private static native int nGetFontCount(long minikinLayout); + @CriticalNative + private static native long nGetFontRef(long minikinLayout, int fontId); + @CriticalNative + private static native int nGetFontId(long minikinLayout, int glyphIndex); @Override public boolean equals(Object o) { diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java index 882a8d035e93..ad194f707cf3 100644 --- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java +++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/DividerPresenter.java @@ -1255,7 +1255,8 @@ class DividerPresenter implements View.OnTouchListener { // Update divider line surface visibility and color. // If a container is fully expanded, the divider line is invisible unless dragging. - final boolean isDividerLineVisible = !mProperties.mIsDraggableExpandType || mIsDragging; + final boolean isDividerLineVisible = mProperties.mDividerWidthPx > 0 + && (!mProperties.mIsDraggableExpandType || mIsDragging); t.setVisibility(mDividerLineSurface, isDividerLineVisible); t.setColor(mDividerLineSurface, colorToFloatArray( Color.valueOf(mProperties.mDividerAttributes.getDividerColor()))); diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt index 96ffa03a1f65..52ce8cb5c0dc 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleStackViewTest.kt @@ -67,6 +67,7 @@ class BubbleStackViewTest { private val context = ApplicationProvider.getApplicationContext<Context>() private lateinit var positioner: BubblePositioner + private lateinit var bubbleLogger: BubbleLogger private lateinit var iconFactory: BubbleIconFactory private lateinit var expandedViewManager: FakeBubbleExpandedViewManager private lateinit var bubbleStackView: BubbleStackView @@ -96,10 +97,11 @@ class BubbleStackViewTest { ) ) positioner = BubblePositioner(context, windowManager) + bubbleLogger = BubbleLogger(UiEventLoggerFake()) bubbleData = BubbleData( context, - BubbleLogger(UiEventLoggerFake()), + bubbleLogger, positioner, BubbleEducationController(context), shellExecutor, @@ -394,6 +396,7 @@ class BubbleStackViewTest { expandedViewManager, bubbleTaskViewFactory, positioner, + bubbleLogger, bubbleStackView, null, iconFactory, diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt index 9fdde128ce41..712cc7c475bc 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskTest.kt @@ -29,6 +29,7 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R +import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.internal.statusbar.IStatusBarService import com.android.launcher3.icons.BubbleIconFactory @@ -70,6 +71,7 @@ class BubbleViewInfoTaskTest { private lateinit var bgExecutor: TestExecutor private lateinit var bubbleStackView: BubbleStackView private lateinit var bubblePositioner: BubblePositioner + private lateinit var bubbleLogger: BubbleLogger private lateinit var expandedViewManager: BubbleExpandedViewManager private val bubbleTaskViewFactory = BubbleTaskViewFactory { @@ -103,10 +105,11 @@ class BubbleViewInfoTaskTest { mainExecutor ) bubblePositioner = BubblePositioner(context, windowManager) + bubbleLogger = BubbleLogger(UiEventLoggerFake()) val bubbleData = BubbleData( context, - mock<BubbleLogger>(), + bubbleLogger, bubblePositioner, BubbleEducationController(context), mainExecutor, @@ -138,7 +141,7 @@ class BubbleViewInfoTaskTest { WindowManagerShellWrapper(mainExecutor), mock<UserManager>(), mock<LauncherApps>(), - mock<BubbleLogger>(), + bubbleLogger, mock<TaskStackListenerImpl>(), mock<ShellTaskOrganizer>(), bubblePositioner, @@ -314,6 +317,7 @@ class BubbleViewInfoTaskTest { expandedViewManager, bubbleTaskViewFactory, bubblePositioner, + bubbleLogger, bubbleStackView, null /* layerView */, iconFactory, diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt index 35d459f27534..fa9d2baa78d9 100644 --- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt +++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewTest.kt @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles.bar import android.app.ActivityManager import android.content.Context +import android.content.pm.ShortcutInfo import android.graphics.Insets import android.graphics.Rect import android.view.LayoutInflater @@ -27,11 +28,13 @@ import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation +import com.android.internal.logging.testing.UiEventLoggerFake import com.android.internal.protolog.ProtoLog import com.android.wm.shell.R import com.android.wm.shell.bubbles.Bubble import com.android.wm.shell.bubbles.BubbleData import com.android.wm.shell.bubbles.BubbleExpandedViewManager +import com.android.wm.shell.bubbles.BubbleLogger import com.android.wm.shell.bubbles.BubblePositioner import com.android.wm.shell.bubbles.BubbleTaskView import com.android.wm.shell.bubbles.BubbleTaskViewFactory @@ -43,11 +46,14 @@ import com.android.wm.shell.shared.handles.RegionSamplingHelper import com.android.wm.shell.taskview.TaskView import com.android.wm.shell.taskview.TaskViewTaskController import com.google.common.truth.Truth.assertThat +import com.google.common.util.concurrent.MoreExecutors.directExecutor import org.junit.After import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import java.util.Collections import java.util.concurrent.Executor @@ -70,14 +76,18 @@ class BubbleBarExpandedViewTest { private lateinit var expandedViewManager: BubbleExpandedViewManager private lateinit var positioner: BubblePositioner private lateinit var bubbleTaskView: BubbleTaskView + private lateinit var bubble: Bubble private lateinit var bubbleExpandedView: BubbleBarExpandedView private var testableRegionSamplingHelper: TestableRegionSamplingHelper? = null private var regionSamplingProvider: TestRegionSamplingProvider? = null + private val bubbleLogger = spy(BubbleLogger(UiEventLoggerFake())) + @Before fun setUp() { ProtoLog.REQUIRE_PROTOLOGTOOL = false + ProtoLog.init() mainExecutor = TestExecutor() bgExecutor = TestExecutor() positioner = BubblePositioner(context, windowManager) @@ -106,11 +116,12 @@ class BubbleBarExpandedViewTest { bubbleExpandedView.initialize( expandedViewManager, positioner, + bubbleLogger, false /* isOverflow */, bubbleTaskView, mainExecutor, bgExecutor, - regionSamplingProvider + regionSamplingProvider, ) getInstrumentation().runOnMainSync(Runnable { @@ -118,6 +129,20 @@ class BubbleBarExpandedViewTest { // Helper should be created once attached to window testableRegionSamplingHelper = regionSamplingProvider!!.helper }) + + bubble = Bubble( + "key", + ShortcutInfo.Builder(context, "id").build(), + 100 /* desiredHeight */, + 0 /* desiredHeightResId */, + "title", + 0 /* taskId */, + null /* locus */, + true /* isDismissable */, + directExecutor(), + directExecutor() + ) {} + bubbleExpandedView.update(bubble) } @After @@ -191,6 +216,16 @@ class BubbleBarExpandedViewTest { assertThat(testableRegionSamplingHelper!!.isStopped).isTrue() } + @Test + fun testEventLogging_dismissBubbleViaAppMenu() { + getInstrumentation().runOnMainSync { bubbleExpandedView.handleView.performClick() } + val dismissMenuItem = + bubbleExpandedView.findViewWithTag<View>(BubbleBarMenuView.DISMISS_ACTION_TAG) + assertThat(dismissMenuItem).isNotNull() + getInstrumentation().runOnMainSync { dismissMenuItem.performClick() } + verify(bubbleLogger).log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU) + } + private inner class FakeBubbleTaskViewFactory : BubbleTaskViewFactory { override fun create(): BubbleTaskView { val taskViewTaskController = mock<TaskViewTaskController>() diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml index 35ef2393bb9b..37596182f05b 100644 --- a/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml +++ b/libs/WindowManager/Shell/res/layout/desktop_mode_window_decor_maximize_menu.xml @@ -38,7 +38,7 @@ <Button android:layout_width="94dp" android:layout_height="60dp" - android:id="@+id/maximize_menu_maximize_button" + android:id="@+id/maximize_menu_size_toggle_button" style="?android:attr/buttonBarButtonStyle" android:stateListAnimator="@null" android:importantForAccessibility="yes" @@ -48,7 +48,7 @@ android:alpha="0"/> <TextView - android:id="@+id/maximize_menu_maximize_window_text" + android:id="@+id/maximize_menu_size_toggle_button_text" android:layout_width="94dp" android:layout_height="18dp" android:textSize="11sp" diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml index 1f1565160965..df1e2248872b 100644 --- a/libs/WindowManager/Shell/res/values/dimen.xml +++ b/libs/WindowManager/Shell/res/values/dimen.xml @@ -492,8 +492,12 @@ <dimen name="desktop_mode_maximize_menu_buttons_outline_stroke">1dp</dimen> <!-- The radius of the inner fill of the maximize menu buttons. --> <dimen name="desktop_mode_maximize_menu_buttons_fill_radius">4dp</dimen> - <!-- The padding between the outline and fill of the maximize menu buttons. --> - <dimen name="desktop_mode_maximize_menu_buttons_fill_padding">4dp</dimen> + <!-- The padding between the outline and fill of the maximize menu snap and maximize buttons. --> + <dimen name="desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding">4dp</dimen> + <!-- The vertical padding between the outline and fill of the maximize menu restore button. --> + <dimen name="desktop_mode_maximize_menu_restore_button_fill_vertical_padding">13dp</dimen> + <!-- The horizontal padding between the outline and fill of the maximize menu restore button. --> + <dimen name="desktop_mode_maximize_menu_restore_button_fill_horizontal_padding">21dp</dimen> <!-- The corner radius of the maximize menu. --> <dimen name="desktop_mode_maximize_menu_corner_radius">8dp</dimen> diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index 621e2aacd673..afac9f6433a3 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -319,6 +319,8 @@ <string name="desktop_mode_non_resizable_snap_text">App can\'t be moved here</string> <!-- Accessibility text for the Maximize Menu's maximize button [CHAR LIMIT=NONE] --> <string name="desktop_mode_maximize_menu_maximize_button_text">Maximize</string> + <!-- Accessibility text for the Maximize Menu's restore button [CHAR LIMIT=NONE] --> + <string name="desktop_mode_maximize_menu_restore_button_text">Restore</string> <!-- Accessibility text for the Maximize Menu's snap left button [CHAR LIMIT=NONE] --> <string name="desktop_mode_maximize_menu_snap_left_button_text">Snap left</string> <!-- Accessibility text for the Maximize Menu's snap right button [CHAR LIMIT=NONE] --> 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 e3fc5c2273e2..e8e25e20d8d8 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 @@ -572,6 +572,7 @@ public class Bubble implements BubbleViewProvider { * @param expandedViewManager the bubble expanded view manager. * @param taskViewFactory the task view factory used to create the task view for the bubble. * @param positioner the bubble positioner. + * @param bubbleLogger log bubble metrics. * @param stackView the view the bubble is added to, iff showing as floating. * @param layerView the layer the bubble is added to, iff showing in the bubble bar. * @param iconFactory the icon factory used to create images for the bubble. @@ -581,6 +582,7 @@ public class Bubble implements BubbleViewProvider { BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory taskViewFactory, BubblePositioner positioner, + BubbleLogger bubbleLogger, @Nullable BubbleStackView stackView, @Nullable BubbleBarLayerView layerView, BubbleIconFactory iconFactory, @@ -595,6 +597,7 @@ public class Bubble implements BubbleViewProvider { expandedViewManager, taskViewFactory, positioner, + bubbleLogger, stackView, layerView, iconFactory, @@ -616,6 +619,7 @@ public class Bubble implements BubbleViewProvider { expandedViewManager, taskViewFactory, positioner, + bubbleLogger, stackView, layerView, iconFactory, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index c81ffdabf9b8..37e8ead4fc78 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -892,7 +892,7 @@ public class BubbleController implements ConfigurationChangeListener, registerBroadcastReceiver(); if (isShowingAsBubbleBar()) { mBubbleData.getOverflow().initializeForBubbleBar( - mExpandedViewManager, mBubblePositioner); + mExpandedViewManager, mBubblePositioner, mLogger); } else { mBubbleData.getOverflow().initialize( mExpandedViewManager, mStackView, mBubblePositioner); @@ -1100,6 +1100,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, @@ -1111,6 +1112,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, @@ -1246,9 +1248,12 @@ public class BubbleController implements ConfigurationChangeListener, */ public void dragBubbleToDismiss(String bubbleKey, long timestamp) { String selectedBubbleKey = mBubbleData.getSelectedBubbleKey(); - if (mBubbleData.hasAnyBubbleWithKey(bubbleKey)) { + Bubble bubbleToDismiss = mBubbleData.getAnyBubbleWithkey(bubbleKey); + if (bubbleToDismiss != null) { mBubbleData.dismissBubbleWithKey( bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp); + mLogger.log(bubbleToDismiss, + BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE); } if (mBubbleData.hasBubbles()) { // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded @@ -1582,6 +1587,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, @@ -1644,6 +1650,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, @@ -1724,6 +1731,7 @@ public class BubbleController implements ConfigurationChangeListener, mExpandedViewManager, mBubbleTaskViewFactory, mBubblePositioner, + mLogger, mStackView, mLayerView, mBubbleIconFactory, 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 709a7bdc61f2..4de9dfa54c5d 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 @@ -495,7 +495,7 @@ public class BubbleData { /** * When this method is called it is expected that all info in the bubble has completed loading. * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager, - * BubbleTaskViewFactory, BubblePositioner, BubbleStackView, + * BubbleTaskViewFactory, BubblePositioner, BubbleLogger, BubbleStackView, * com.android.wm.shell.bubbles.bar.BubbleBarLayerView, * com.android.launcher3.icons.BubbleIconFactory, boolean) */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt index 68c4657f2b68..c74412b825d9 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleOverflow.kt @@ -73,17 +73,19 @@ class BubbleOverflow(private val context: Context, private val positioner: Bubbl fun initializeForBubbleBar( expandedViewManager: BubbleExpandedViewManager, - positioner: BubblePositioner + positioner: BubblePositioner, + bubbleLogger: BubbleLogger, ) { createBubbleBarExpandedView() .initialize( expandedViewManager, positioner, + bubbleLogger, /* isOverflow= */ true, /* bubbleTaskView= */ null, /* mainExecutor= */ null, /* backgroundExecutor= */ null, - /* regionSamplingProvider= */ null + /* regionSamplingProvider= */ null, ) } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java index 39fb2f49c1ee..96b6043059d2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java @@ -73,6 +73,7 @@ public class BubbleViewInfoTask { private final WeakReference<BubbleExpandedViewManager> mExpandedViewManager; private final WeakReference<BubbleTaskViewFactory> mTaskViewFactory; private final WeakReference<BubblePositioner> mPositioner; + private final WeakReference<BubbleLogger> mBubbleLogger; private final WeakReference<BubbleStackView> mStackView; private final WeakReference<BubbleBarLayerView> mLayerView; private final BubbleIconFactory mIconFactory; @@ -94,6 +95,7 @@ public class BubbleViewInfoTask { BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory taskViewFactory, BubblePositioner positioner, + BubbleLogger bubbleLogger, @Nullable BubbleStackView stackView, @Nullable BubbleBarLayerView layerView, BubbleIconFactory factory, @@ -106,6 +108,7 @@ public class BubbleViewInfoTask { mExpandedViewManager = new WeakReference<>(expandedViewManager); mTaskViewFactory = new WeakReference<>(taskViewFactory); mPositioner = new WeakReference<>(positioner); + mBubbleLogger = new WeakReference<>(bubbleLogger); mStackView = new WeakReference<>(stackView); mLayerView = new WeakReference<>(layerView); mIconFactory = factory; @@ -221,8 +224,9 @@ public class BubbleViewInfoTask { ProtoLog.v(WM_SHELL_BUBBLES, "Task initializing bubble bar expanded view key=%s", mBubble.getKey()); viewInfo.bubbleBarExpandedView.initialize(mExpandedViewManager.get(), - mPositioner.get(), false /* isOverflow */, viewInfo.taskView, - mMainExecutor, mBgExecutor, new RegionSamplingProvider() { + mPositioner.get(), mBubbleLogger.get(), false /* isOverflow */, + viewInfo.taskView, mMainExecutor, mBgExecutor, + new RegionSamplingProvider() { @Override public RegionSamplingHelper createHelper(View sampledView, RegionSamplingHelper.SamplingCallback callback, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java index e9a593392dc2..c1da94cc470f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java @@ -78,6 +78,7 @@ public class BubbleViewInfoTaskLegacy extends private WeakReference<BubbleExpandedViewManager> mExpandedViewManager; private WeakReference<BubbleTaskViewFactory> mTaskViewFactory; private WeakReference<BubblePositioner> mPositioner; + private WeakReference<BubbleLogger> mBubbleLogger; private WeakReference<BubbleStackView> mStackView; private WeakReference<BubbleBarLayerView> mLayerView; private BubbleIconFactory mIconFactory; @@ -95,6 +96,7 @@ public class BubbleViewInfoTaskLegacy extends BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory taskViewFactory, BubblePositioner positioner, + BubbleLogger bubbleLogger, @Nullable BubbleStackView stackView, @Nullable BubbleBarLayerView layerView, BubbleIconFactory factory, @@ -107,6 +109,7 @@ public class BubbleViewInfoTaskLegacy extends mExpandedViewManager = new WeakReference<>(expandedViewManager); mTaskViewFactory = new WeakReference<>(taskViewFactory); mPositioner = new WeakReference<>(positioner); + mBubbleLogger = new WeakReference<>(bubbleLogger); mStackView = new WeakReference<>(stackView); mLayerView = new WeakReference<>(layerView); mIconFactory = factory; @@ -124,8 +127,9 @@ public class BubbleViewInfoTaskLegacy extends } if (mLayerView.get() != null) { return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(), - mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory, - mBubble, mSkipInflation, mMainExecutor, mBackgroundExecutor); + mTaskViewFactory.get(), mPositioner.get(), mBubbleLogger.get(), + mLayerView.get(), mIconFactory, mBubble, mSkipInflation, mMainExecutor, + mBackgroundExecutor); } else { return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(), mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory, @@ -187,6 +191,7 @@ public class BubbleViewInfoTaskLegacy extends BubbleExpandedViewManager expandedViewManager, BubbleTaskViewFactory taskViewFactory, BubblePositioner positioner, + BubbleLogger bubbleLogger, BubbleBarLayerView layerView, BubbleIconFactory iconFactory, Bubble b, @@ -200,9 +205,9 @@ public class BubbleViewInfoTaskLegacy extends LayoutInflater inflater = LayoutInflater.from(c); info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate( R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */); - info.bubbleBarExpandedView.initialize( - expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView, - mainExecutor, backgroundExecutor, new RegionSamplingProvider() { + info.bubbleBarExpandedView.initialize(expandedViewManager, positioner, bubbleLogger, + false /* isOverflow */, bubbleTaskView, mainExecutor, backgroundExecutor, + new RegionSamplingProvider() { @Override public RegionSamplingHelper createHelper(View sampledView, RegionSamplingHelper.SamplingCallback callback, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java index 2a9001728cc2..0ce651c3f1fe 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedView.java @@ -39,6 +39,7 @@ import androidx.annotation.VisibleForTesting; import com.android.wm.shell.R; import com.android.wm.shell.bubbles.Bubble; import com.android.wm.shell.bubbles.BubbleExpandedViewManager; +import com.android.wm.shell.bubbles.BubbleLogger; import com.android.wm.shell.bubbles.BubbleOverflowContainerView; import com.android.wm.shell.bubbles.BubblePositioner; import com.android.wm.shell.bubbles.BubbleTaskView; @@ -90,6 +91,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView private Bubble mBubble; private BubbleExpandedViewManager mManager; private BubblePositioner mPositioner; + private BubbleLogger mBubbleLogger; private boolean mIsOverflow; private BubbleTaskViewHelper mBubbleTaskViewHelper; private BubbleBarMenuViewController mMenuViewController; @@ -178,6 +180,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView /** Initializes the view, must be called before doing anything else. */ public void initialize(BubbleExpandedViewManager expandedViewManager, BubblePositioner positioner, + BubbleLogger bubbleLogger, boolean isOverflow, @Nullable BubbleTaskView bubbleTaskView, @Nullable Executor mainExecutor, @@ -185,6 +188,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Nullable RegionSamplingProvider regionSamplingProvider) { mManager = expandedViewManager; mPositioner = positioner; + mBubbleLogger = bubbleLogger; mIsOverflow = isOverflow; mMainExecutor = mainExecutor; mBackgroundExecutor = backgroundExecutor; @@ -248,6 +252,7 @@ public class BubbleBarExpandedView extends FrameLayout implements BubbleTaskView @Override public void onDismissBubble(Bubble bubble) { mManager.dismissBubble(bubble, Bubbles.DISMISS_USER_GESTURE); + mBubbleLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_APP_MENU); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java index 0300869cbbe1..52b807abddd6 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuView.java @@ -16,6 +16,7 @@ package com.android.wm.shell.bubbles.bar; import android.annotation.ColorInt; +import android.annotation.Nullable; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.TypedArray; @@ -41,6 +42,9 @@ import java.util.ArrayList; * Bubble bar expanded view menu */ public class BubbleBarMenuView extends LinearLayout { + + public static final Object DISMISS_ACTION_TAG = new Object(); + private ViewGroup mBubbleSectionView; private ViewGroup mActionsSectionView; private ImageView mBubbleIconView; @@ -119,6 +123,9 @@ public class BubbleBarMenuView extends LinearLayout { R.layout.bubble_bar_menu_item, mActionsSectionView, false); itemView.update(action.mIcon, action.mTitle, action.mTint); itemView.setOnClickListener(action.mOnClick); + if (action.mTag != null) { + itemView.setTag(action.mTag); + } mActionsSectionView.addView(itemView); } } @@ -159,6 +166,8 @@ public class BubbleBarMenuView extends LinearLayout { private Icon mIcon; private @ColorInt int mTint; private String mTitle; + @Nullable + private Object mTag; private OnClickListener mOnClick; MenuAction(Icon icon, String title, OnClickListener onClick) { @@ -171,5 +180,14 @@ public class BubbleBarMenuView extends LinearLayout { this.mTint = tint; this.mOnClick = onClick; } + + MenuAction(Icon icon, String title, @ColorInt int tint, @Nullable Object tag, + OnClickListener onClick) { + this.mIcon = icon; + this.mTitle = title; + this.mTint = tint; + this.mTag = tag; + this.mOnClick = onClick; + } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java index 514810745e10..5ed01b66ec67 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarMenuViewController.java @@ -212,6 +212,7 @@ class BubbleBarMenuViewController { Icon.createWithResource(resources, R.drawable.ic_remove_no_shadow), resources.getString(R.string.bubble_dismiss_text), tintColor, + BubbleBarMenuView.DISMISS_ACTION_TAG, view -> { hideMenu(true /* animated */); if (mListener != null) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java index 0b2b3e7c41c8..85921703d559 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java @@ -87,7 +87,7 @@ public class DividerSnapAlgorithm { private final boolean mCalculateRatiosBasedOnAvailableSpace; /** Allows split ratios that go offscreen (a.k.a. "flexible split") */ private final boolean mAllowOffscreenRatios; - private final boolean mIsHorizontalDivision; + private final boolean mIsLeftRightSplit; /** The first target which is still splitting the screen */ private final SnapTarget mFirstSplitTarget; @@ -101,13 +101,13 @@ public class DividerSnapAlgorithm { public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, - boolean isHorizontalDivision, Rect insets, int dockSide) { - this(res, displayWidth, displayHeight, dividerSize, isHorizontalDivision, insets, - dockSide, false /* minimized */, true /* resizable */); + boolean isLeftRightSplit, Rect insets, int dockSide) { + this(res, displayWidth, displayHeight, dividerSize, isLeftRightSplit, insets, + dockSide, false /* minimized */, true /* resizable */); } public DividerSnapAlgorithm(Resources res, int displayWidth, int displayHeight, int dividerSize, - boolean isHorizontalDivision, Rect insets, int dockSide, boolean isMinimizedMode, + boolean isLeftRightSplit, Rect insets, int dockSide, boolean isMinimizedMode, boolean isHomeResizable) { mMinFlingVelocityPxPerSecond = MIN_FLING_VELOCITY_DP_PER_SECOND * res.getDisplayMetrics().density; @@ -116,7 +116,7 @@ public class DividerSnapAlgorithm { mDividerSize = dividerSize; mDisplayWidth = displayWidth; mDisplayHeight = displayHeight; - mIsHorizontalDivision = isHorizontalDivision; + mIsLeftRightSplit = isLeftRightSplit; mInsets.set(insets); mSnapMode = isMinimizedMode ? SNAP_MODE_MINIMIZED : res.getInteger(com.android.internal.R.integer.config_dockedStackDividerSnapMode); @@ -133,7 +133,7 @@ public class DividerSnapAlgorithm { Flags.enableFlexibleTwoAppSplit() && SplitScreenUtils.allowOffscreenRatios(res); mTaskHeightInMinimizedMode = isHomeResizable ? res.getDimensionPixelSize( com.android.internal.R.dimen.task_height_of_minimized_mode) : 0; - calculateTargets(isHorizontalDivision, dockSide); + calculateTargets(isLeftRightSplit, dockSide); mFirstSplitTarget = mTargets.get(1); mLastSplitTarget = mTargets.get(mTargets.size() - 2); mDismissStartTarget = mTargets.get(0); @@ -218,18 +218,18 @@ public class DividerSnapAlgorithm { } private int getStartInset() { - if (mIsHorizontalDivision) { - return mInsets.top; - } else { + if (mIsLeftRightSplit) { return mInsets.left; + } else { + return mInsets.top; } } private int getEndInset() { - if (mIsHorizontalDivision) { - return mInsets.bottom; - } else { + if (mIsLeftRightSplit) { return mInsets.right; + } else { + return mInsets.bottom; } } @@ -269,11 +269,11 @@ public class DividerSnapAlgorithm { return mTargets.get(minIndex); } - private void calculateTargets(boolean isHorizontalDivision, int dockedSide) { + private void calculateTargets(boolean isLeftRightSplit, int dockedSide) { mTargets.clear(); - int dividerMax = isHorizontalDivision - ? mDisplayHeight - : mDisplayWidth; + int dividerMax = isLeftRightSplit + ? mDisplayWidth + : mDisplayHeight; int startPos = -mDividerSize; if (dockedSide == DOCKED_RIGHT) { startPos += mInsets.left; @@ -281,38 +281,38 @@ public class DividerSnapAlgorithm { mTargets.add(new SnapTarget(startPos, SNAP_TO_START_AND_DISMISS, 0.35f)); switch (mSnapMode) { case SNAP_MODE_16_9: - addRatio16_9Targets(isHorizontalDivision, dividerMax); + addRatio16_9Targets(isLeftRightSplit, dividerMax); break; case SNAP_FIXED_RATIO: - addFixedDivisionTargets(isHorizontalDivision, dividerMax); + addFixedDivisionTargets(isLeftRightSplit, dividerMax); break; case SNAP_ONLY_1_1: - addMiddleTarget(isHorizontalDivision); + addMiddleTarget(isLeftRightSplit); break; case SNAP_MODE_MINIMIZED: - addMinimizedTarget(isHorizontalDivision, dockedSide); + addMinimizedTarget(isLeftRightSplit, dockedSide); break; } mTargets.add(new SnapTarget(dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f)); } - private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition, + private void addNonDismissingTargets(boolean isLeftRightSplit, int topPosition, int bottomPosition, int dividerMax) { @PersistentSnapPosition int firstTarget = mAllowOffscreenRatios ? SNAP_TO_2_10_90 : SNAP_TO_2_33_66; @PersistentSnapPosition int lastTarget = mAllowOffscreenRatios ? SNAP_TO_2_90_10 : SNAP_TO_2_66_33; maybeAddTarget(topPosition, topPosition - getStartInset(), firstTarget); - addMiddleTarget(isHorizontalDivision); + addMiddleTarget(isLeftRightSplit); maybeAddTarget(bottomPosition, dividerMax - getEndInset() - (bottomPosition + mDividerSize), lastTarget); } - private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) { - int start = isHorizontalDivision ? mInsets.top : mInsets.left; - int end = isHorizontalDivision - ? mDisplayHeight - mInsets.bottom - : mDisplayWidth - mInsets.right; + private void addFixedDivisionTargets(boolean isLeftRightSplit, int dividerMax) { + int start = isLeftRightSplit ? mInsets.left : mInsets.top; + int end = isLeftRightSplit + ? mDisplayWidth - mInsets.right + : mDisplayHeight - mInsets.bottom; int size = (int) (mFixedRatio * (end - start)) - mDividerSize / 2; if (mAllowOffscreenRatios) { @@ -323,23 +323,23 @@ public class DividerSnapAlgorithm { } int topPosition = start + size; int bottomPosition = end - size - mDividerSize; - addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax); + addNonDismissingTargets(isLeftRightSplit, topPosition, bottomPosition, dividerMax); } - private void addRatio16_9Targets(boolean isHorizontalDivision, int dividerMax) { - int start = isHorizontalDivision ? mInsets.top : mInsets.left; - int end = isHorizontalDivision - ? mDisplayHeight - mInsets.bottom - : mDisplayWidth - mInsets.right; - int startOther = isHorizontalDivision ? mInsets.left : mInsets.top; - int endOther = isHorizontalDivision + private void addRatio16_9Targets(boolean isLeftRightSplit, int dividerMax) { + int start = isLeftRightSplit ? mInsets.left : mInsets.top; + int end = isLeftRightSplit ? mDisplayWidth - mInsets.right : mDisplayHeight - mInsets.bottom; + int startOther = isLeftRightSplit ? mInsets.top : mInsets.left; + int endOther = isLeftRightSplit + ? mDisplayHeight - mInsets.bottom + : mDisplayWidth - mInsets.right; float size = 9.0f / 16.0f * (endOther - startOther); int sizeInt = (int) Math.floor(size); int topPosition = start + sizeInt; int bottomPosition = end - sizeInt - mDividerSize; - addNonDismissingTargets(isHorizontalDivision, topPosition, bottomPosition, dividerMax); + addNonDismissingTargets(isLeftRightSplit, topPosition, bottomPosition, dividerMax); } /** @@ -352,17 +352,17 @@ public class DividerSnapAlgorithm { } } - private void addMiddleTarget(boolean isHorizontalDivision) { - int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision, + private void addMiddleTarget(boolean isLeftRightSplit) { + int position = DockedDividerUtils.calculateMiddlePosition(isLeftRightSplit, mInsets, mDisplayWidth, mDisplayHeight, mDividerSize); mTargets.add(new SnapTarget(position, SNAP_TO_2_50_50)); } - private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) { + private void addMinimizedTarget(boolean isLeftRightSplit, int dockedSide) { // In portrait offset the position by the statusbar height, in landscape add the statusbar // height as well to match portrait offset int position = mTaskHeightInMinimizedMode + mInsets.top; - if (!isHorizontalDivision) { + if (isLeftRightSplit) { if (dockedSide == DOCKED_LEFT) { position += mInsets.left; } else if (dockedSide == DOCKED_RIGHT) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java index f25dfeafb32c..25157c05d0de 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DockedDividerUtils.java @@ -97,12 +97,12 @@ public class DockedDividerUtils { } } - public static int calculateMiddlePosition(boolean isHorizontalDivision, Rect insets, + public static int calculateMiddlePosition(boolean isLeftRightSplit, Rect insets, int displayWidth, int displayHeight, int dividerSize) { - int start = isHorizontalDivision ? insets.top : insets.left; - int end = isHorizontalDivision - ? displayHeight - insets.bottom - : displayWidth - insets.right; + int start = isLeftRightSplit ? insets.left : insets.top; + int end = isLeftRightSplit + ? displayWidth - insets.right + : displayHeight - insets.bottom; return start + (end - start) / 2 - dividerSize / 2; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java index c9c3aa0dd537..f73065ea8eb8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java @@ -665,7 +665,7 @@ public final class SplitLayout implements DisplayInsetsController.OnInsetsChange rootBounds.width(), rootBounds.height(), mDividerSize, - !mIsLeftRightSplit, + mIsLeftRightSplit, insets, mIsLeftRightSplit ? DOCKED_LEFT : DOCKED_TOP /* dockSide */); } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java index 52262e68c401..97397cea4a38 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java @@ -16,6 +16,7 @@ package com.android.wm.shell.dagger; +import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_EXIT_TRANSITIONS; import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASK_LIMIT; @@ -727,7 +728,8 @@ public abstract class WMShellModule { Transitions transitions, RootTaskDisplayAreaOrganizer rootTaskDisplayAreaOrganizer, InteractionJankMonitor interactionJankMonitor) { - return Flags.enableDesktopWindowingTransitions() + return (Flags.enableDesktopWindowingTransitions() || + ENABLE_DESKTOP_WINDOWING_ENTER_TRANSITIONS.isTrue()) ? new SpringDragToDesktopTransitionHandler(context, transitions, rootTaskDisplayAreaOrganizer, interactionJankMonitor) : new DefaultDragToDesktopTransitionHandler(context, transitions, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt index 379e052e7b38..8ebe503a3816 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt @@ -21,17 +21,39 @@ import com.android.internal.protolog.ProtoLog import com.android.internal.util.FrameworkStatsLog import com.android.window.flags.Flags import com.android.wm.shell.EventLogTags -import com.android.wm.shell.protolog.ShellProtoLogGroup +import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE +import java.security.SecureRandom +import java.util.Random +import java.util.concurrent.atomic.AtomicInteger + /** Event logger for logging desktop mode session events */ class DesktopModeEventLogger { + private val random: Random = SecureRandom() + + /** The session id for the current desktop mode session */ + @VisibleForTesting + val currentSessionId: AtomicInteger = AtomicInteger(NO_SESSION_ID) + + private fun generateSessionId() = 1 + random.nextInt(1 shl 20) + /** - * Logs the enter of desktop mode having session id [sessionId] and the reason [enterReason] for - * entering desktop mode + * Logs enter into desktop mode with [enterReason] */ - fun logSessionEnter(sessionId: Int, enterReason: EnterReason) { + fun logSessionEnter(enterReason: EnterReason) { + val sessionId = generateSessionId() + val previousSessionId = currentSessionId.getAndSet(sessionId) + if (previousSessionId != NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: Existing desktop mode session id: %s found on desktop " + + "mode enter", + previousSessionId + ) + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging session enter, session: %s reason: %s", sessionId, enterReason.name @@ -47,12 +69,20 @@ class DesktopModeEventLogger { } /** - * Logs the exit of desktop mode having session id [sessionId] and the reason [exitReason] for - * exiting desktop mode + * Logs exit from desktop mode session with [exitReason] */ - fun logSessionExit(sessionId: Int, exitReason: ExitReason) { + fun logSessionExit(exitReason: ExitReason) { + val sessionId = currentSessionId.getAndSet(NO_SESSION_ID) + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging exit from desktop mode" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging session exit, session: %s reason: %s", sessionId, exitReason.name @@ -68,12 +98,20 @@ class DesktopModeEventLogger { } /** - * Logs that the task with update [taskUpdate] was added in the desktop mode session having - * session id [sessionId] + * Logs that a task with [taskUpdate] was added in a desktop mode session */ - fun logTaskAdded(sessionId: Int, taskUpdate: TaskUpdate) { + fun logTaskAdded(taskUpdate: TaskUpdate) { + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging task added" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task added, session: %s taskId: %s", sessionId, taskUpdate.instanceId @@ -85,12 +123,20 @@ class DesktopModeEventLogger { } /** - * Logs that the task with update [taskUpdate] was removed in the desktop mode session having - * session id [sessionId] + * Logs that a task with [taskUpdate] was removed from a desktop mode session */ - fun logTaskRemoved(sessionId: Int, taskUpdate: TaskUpdate) { + fun logTaskRemoved(taskUpdate: TaskUpdate) { + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging task removed" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task remove, session: %s taskId: %s", sessionId, taskUpdate.instanceId @@ -102,12 +148,20 @@ class DesktopModeEventLogger { } /** - * Logs that the task with update [taskUpdate] had it's info changed in the desktop mode session - * having session id [sessionId] + * Logs that a task with [taskUpdate] had it's info changed in a desktop mode session */ - fun logTaskInfoChanged(sessionId: Int, taskUpdate: TaskUpdate) { + fun logTaskInfoChanged(taskUpdate: TaskUpdate) { + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging task info changed" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task info changed, session: %s taskId: %s", sessionId, taskUpdate.instanceId @@ -119,14 +173,23 @@ class DesktopModeEventLogger { } /** - * Logs that a task resize event is starting with [taskSizeUpdate] within a - * Desktop mode [sessionId]. + * Logs that a task resize event is starting with [taskSizeUpdate] within a Desktop mode + * session. */ - fun logTaskResizingStarted(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) { + fun logTaskResizingStarted(taskSizeUpdate: TaskSizeUpdate) { if (!Flags.enableResizingMetrics()) return + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging start of task resizing" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task resize is starting, session: %s taskId: %s", sessionId, taskSizeUpdate.instanceId @@ -138,14 +201,22 @@ class DesktopModeEventLogger { } /** - * Logs that a task resize event is ending with [taskSizeUpdate] within a - * Desktop mode [sessionId]. + * Logs that a task resize event is ending with [taskSizeUpdate] within a Desktop mode session. */ - fun logTaskResizingEnded(sessionId: Int, taskSizeUpdate: TaskSizeUpdate) { + fun logTaskResizingEnded(taskSizeUpdate: TaskSizeUpdate) { if (!Flags.enableResizingMetrics()) return + val sessionId = currentSessionId.get() + if (sessionId == NO_SESSION_ID) { + ProtoLog.w( + WM_SHELL_DESKTOP_MODE, + "DesktopModeLogger: No session id found for logging end of task resizing" + ) + return + } + ProtoLog.v( - ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE, + WM_SHELL_DESKTOP_MODE, "DesktopModeLogger: Logging task resize is ending, session: %s taskId: %s", sessionId, taskSizeUpdate.instanceId @@ -248,6 +319,7 @@ class DesktopModeEventLogger { } companion object { + /** * Describes a task position and dimensions. * @@ -465,5 +537,6 @@ class DesktopModeEventLogger { FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE private const val DESKTOP_MODE_TASK_SIZE_UPDATED_ATOM_ID = FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED + @VisibleForTesting const val NO_SESSION_ID = 0 } -} +}
\ No newline at end of file diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt index f847aa8918c2..ed03982d191d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserver.kt @@ -35,8 +35,6 @@ import androidx.core.util.isEmpty import androidx.core.util.isNotEmpty import androidx.core.util.plus import androidx.core.util.putAll -import com.android.internal.logging.InstanceId -import com.android.internal.logging.InstanceIdSequence import com.android.internal.protolog.ProtoLog import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason @@ -48,8 +46,8 @@ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_ import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT import com.android.wm.shell.desktopmode.DesktopModeTransitionTypes.TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG import com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE -import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.shared.TransitionUtil +import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.Transitions @@ -65,8 +63,6 @@ class DesktopModeLoggerTransitionObserver( private val desktopModeEventLogger: DesktopModeEventLogger ) : Transitions.TransitionObserver { - private val idSequence: InstanceIdSequence by lazy { InstanceIdSequence(Int.MAX_VALUE) } - init { if (DesktopModeStatus.canEnterDesktopMode(context)) { shellInit.addInitCallback(this::onInit, this) @@ -87,15 +83,7 @@ class DesktopModeLoggerTransitionObserver( // following enter reason could be Screen On private var wasPreviousTransitionExitByScreenOff: Boolean = false - // The instanceId for the current logging session - private var loggerInstanceId: InstanceId? = null - - private val isSessionActive: Boolean - get() = loggerInstanceId != null - - private fun setSessionInactive() { - loggerInstanceId = null - } + @VisibleForTesting var isSessionActive: Boolean = false fun onInit() { transitions.registerObserver(this) @@ -247,38 +235,32 @@ class DesktopModeLoggerTransitionObserver( ) { // Sessions is finishing, log task updates followed by an exit event identifyAndLogTaskUpdates( - loggerInstanceId!!.id, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks ) desktopModeEventLogger.logSessionExit( - loggerInstanceId!!.id, getExitReason(transitionInfo) ) - - setSessionInactive() + isSessionActive = false } else if ( postTransitionVisibleFreeformTasks.isNotEmpty() && preTransitionVisibleFreeformTasks.isEmpty() && !isSessionActive ) { // Session is starting, log enter event followed by task updates - loggerInstanceId = idSequence.newInstanceId() + isSessionActive = true desktopModeEventLogger.logSessionEnter( - loggerInstanceId!!.id, getEnterReason(transitionInfo) ) identifyAndLogTaskUpdates( - loggerInstanceId!!.id, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks ) } else if (isSessionActive) { // Session is neither starting, nor finishing, log task updates if there are any identifyAndLogTaskUpdates( - loggerInstanceId!!.id, preTransitionVisibleFreeformTasks, postTransitionVisibleFreeformTasks ) @@ -291,7 +273,6 @@ class DesktopModeLoggerTransitionObserver( /** Compare the old and new state of taskInfos and identify and log the changes */ private fun identifyAndLogTaskUpdates( - sessionId: Int, preTransitionVisibleFreeformTasks: SparseArray<TaskInfo>, postTransitionVisibleFreeformTasks: SparseArray<TaskInfo> ) { @@ -302,7 +283,7 @@ class DesktopModeLoggerTransitionObserver( when { // new tasks added previousTaskInfo == null -> { - desktopModeEventLogger.logTaskAdded(sessionId, currentTaskUpdate) + desktopModeEventLogger.logTaskAdded(currentTaskUpdate) Trace.setCounter( Trace.TRACE_TAG_WINDOW_MANAGER, VISIBLE_TASKS_COUNTER_NAME, @@ -315,14 +296,14 @@ class DesktopModeLoggerTransitionObserver( // TODO(b/347935387): Log changes only once they are stable. buildTaskUpdateForTask(previousTaskInfo, postTransitionVisibleFreeformTasks.size()) != currentTaskUpdate -> - desktopModeEventLogger.logTaskInfoChanged(sessionId, currentTaskUpdate) + desktopModeEventLogger.logTaskInfoChanged(currentTaskUpdate) } } // find old tasks that were removed preTransitionVisibleFreeformTasks.forEach { taskId, taskInfo -> if (!postTransitionVisibleFreeformTasks.containsKey(taskId)) { - desktopModeEventLogger.logTaskRemoved(sessionId, + desktopModeEventLogger.logTaskRemoved( buildTaskUpdateForTask(taskInfo, postTransitionVisibleFreeformTasks.size())) Trace.setCounter( Trace.TRACE_TAG_WINDOW_MANAGER, @@ -417,13 +398,6 @@ class DesktopModeLoggerTransitionObserver( visibleFreeformTaskInfos.set(taskInfo.taskId, taskInfo) } - @VisibleForTesting fun getLoggerSessionId(): Int? = loggerInstanceId?.id - - @VisibleForTesting - fun setLoggerSessionId(id: Int) { - loggerInstanceId = InstanceId.fakeInstanceId(id) - } - private fun TransitionInfo.Change.requireTaskInfo(): RunningTaskInfo { return this.taskInfo ?: throw IllegalStateException("Expected TaskInfo in the Change") } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java index 52b92a89abdf..61de0777ed05 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java @@ -378,11 +378,12 @@ public class DesktopModeVisualIndicator { private static VisualIndicatorAnimator fadeBoundsIn( @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) { - final Rect startBounds = getIndicatorBounds(displayLayout, type); + final Rect endBounds = getIndicatorBounds(displayLayout, type); + final Rect startBounds = getMinBounds(endBounds); view.getBackground().setBounds(startBounds); final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( - view, startBounds, getMaxBounds(startBounds)); + view, startBounds, endBounds); animator.setInterpolator(new DecelerateInterpolator()); setupIndicatorAnimation(animator, AlphaAnimType.ALPHA_FADE_IN_ANIM); return animator; @@ -390,8 +391,8 @@ public class DesktopModeVisualIndicator { private static VisualIndicatorAnimator fadeBoundsOut( @NonNull View view, IndicatorType type, @NonNull DisplayLayout displayLayout) { - final Rect endBounds = getIndicatorBounds(displayLayout, type); - final Rect startBounds = getMaxBounds(endBounds); + final Rect startBounds = getIndicatorBounds(displayLayout, type); + final Rect endBounds = getMinBounds(startBounds); view.getBackground().setBounds(startBounds); final VisualIndicatorAnimator animator = new VisualIndicatorAnimator( @@ -422,28 +423,35 @@ public class DesktopModeVisualIndicator { return animator; } + /** Calculates the bounds the indicator should have when fully faded in. */ private static Rect getIndicatorBounds(DisplayLayout layout, IndicatorType type) { - final int padding = layout.stableInsets().top; + final Rect desktopStableBounds = new Rect(); + layout.getStableBounds(desktopStableBounds); + final int padding = desktopStableBounds.top; switch (type) { case TO_FULLSCREEN_INDICATOR: - return new Rect(padding, padding, - layout.width() - padding, - layout.height() - padding); + desktopStableBounds.top += padding; + desktopStableBounds.bottom -= padding; + desktopStableBounds.left += padding; + desktopStableBounds.right -= padding; + return desktopStableBounds; case TO_DESKTOP_INDICATOR: final float adjustmentPercentage = 1f - DesktopTasksController.DESKTOP_MODE_INITIAL_BOUNDS_SCALE; - return new Rect((int) (adjustmentPercentage * layout.width() / 2), - (int) (adjustmentPercentage * layout.height() / 2), - (int) (layout.width() - (adjustmentPercentage * layout.width() / 2)), - (int) (layout.height() - (adjustmentPercentage * layout.height() / 2))); + return new Rect((int) (adjustmentPercentage * desktopStableBounds.width() / 2), + (int) (adjustmentPercentage * desktopStableBounds.height() / 2), + (int) (desktopStableBounds.width() + - (adjustmentPercentage * desktopStableBounds.width() / 2)), + (int) (desktopStableBounds.height() + - (adjustmentPercentage * desktopStableBounds.height() / 2))); case TO_SPLIT_LEFT_INDICATOR: return new Rect(padding, padding, - layout.width() / 2 - padding, - layout.height() - padding); + desktopStableBounds.width() / 2 - padding, + desktopStableBounds.height()); case TO_SPLIT_RIGHT_INDICATOR: - return new Rect(layout.width() / 2 + padding, padding, - layout.width() - padding, - layout.height() - padding); + return new Rect(desktopStableBounds.width() / 2 + padding, padding, + desktopStableBounds.width() - padding, + desktopStableBounds.height()); default: throw new IllegalArgumentException("Invalid indicator type provided."); } @@ -505,17 +513,18 @@ public class DesktopModeVisualIndicator { } /** - * Return the max bounds of a visual indicator + * Return the minimum bounds of a visual indicator, to be used at the end of fading out + * and the start of fading in. */ - private static Rect getMaxBounds(Rect startBounds) { - return new Rect((int) (startBounds.left - - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())), - (int) (startBounds.top - - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height())), - (int) (startBounds.right - + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.width())), - (int) (startBounds.bottom - + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * startBounds.height()))); + private static Rect getMinBounds(Rect maxBounds) { + return new Rect((int) (maxBounds.left + + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width())), + (int) (maxBounds.top + + (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height())), + (int) (maxBounds.right + - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.width())), + (int) (maxBounds.bottom + - (FULLSCREEN_SCALE_ADJUSTMENT_PERCENT * maxBounds.height()))); } } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt index 57a59c946f98..4440778a5a45 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt @@ -659,6 +659,7 @@ class DesktopTasksController( } val wct = WindowContainerTransaction() + if (!task.isFreeform) addMoveToDesktopChanges(wct, task, displayId) wct.reparent(task.token, displayAreaInfo.token, true /* onTop */) transitions.startTransition(TRANSIT_CHANGE, wct, null /* handler */) @@ -1184,7 +1185,13 @@ class DesktopTasksController( val options = createNewWindowOptions(callingTask) if (options.launchWindowingMode == WINDOWING_MODE_FREEFORM) { wct.startTask(requestedTaskId, options.toBundle()) - transitions.startTransition(TRANSIT_OPEN, wct, null) + val taskToMinimize = bringDesktopAppsToFrontBeforeShowingNewTask( + callingTask.displayId, wct, requestedTaskId) + val runOnTransit = immersiveTransitionHandler + .exitImmersiveIfApplicable(wct, callingTask.displayId) + val transition = transitions.startTransition(TRANSIT_OPEN, wct, null) + addPendingMinimizeTransition(transition, taskToMinimize) + runOnTransit?.invoke(transition) } else { val splitPosition = splitScreenController.determineNewInstancePosition(callingTask) splitScreenController.startTask(requestedTaskId, splitPosition, @@ -1218,7 +1225,8 @@ class DesktopTasksController( .determineNewInstancePosition(callingTaskInfo) splitScreenController.startIntent( launchIntent, context.userId, fillIn, splitPosition, - options.toBundle(), null /* hideTaskToken */ + options.toBundle(), null /* hideTaskToken */, + true /* forceLaunchNewTask */ ) } WINDOWING_MODE_FREEFORM -> { @@ -1245,7 +1253,7 @@ class DesktopTasksController( val bounds = when (newTaskWindowingMode) { WINDOWING_MODE_FREEFORM -> { displayController.getDisplayLayout(callingTask.displayId) - ?.let { getInitialBounds(it, callingTask) } + ?.let { getInitialBounds(it, callingTask, callingTask.displayId) } } WINDOWING_MODE_MULTI_WINDOW -> { Rect() @@ -1311,7 +1319,7 @@ class DesktopTasksController( val displayLayout = displayController.getDisplayLayout(task.displayId) if (displayLayout != null) { val initialBounds = Rect(task.configuration.windowConfiguration.bounds) - cascadeWindow(task, initialBounds, displayLayout) + cascadeWindow(initialBounds, displayLayout, task.displayId) wct.setBounds(task.token, initialBounds) } } @@ -1399,13 +1407,19 @@ class DesktopTasksController( return if (wct.isEmpty) null else wct } + /** + * Apply all changes required when task is first added to desktop. Uses the task's current + * display by default to apply initial bounds and placement relative to the display. + * Use a different [displayId] if the task should be moved to a different display. + */ @VisibleForTesting fun addMoveToDesktopChanges( wct: WindowContainerTransaction, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + displayId: Int = taskInfo.displayId, ) { - val displayLayout = displayController.getDisplayLayout(taskInfo.displayId) ?: return - val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(taskInfo.displayId)!! + val displayLayout = displayController.getDisplayLayout(displayId) ?: return + val tdaInfo = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(displayId)!! val tdaWindowingMode = tdaInfo.configuration.windowConfiguration.windowingMode val targetWindowingMode = if (tdaWindowingMode == WINDOWING_MODE_FREEFORM) { @@ -1414,7 +1428,7 @@ class DesktopTasksController( } else { WINDOWING_MODE_FREEFORM } - val initialBounds = getInitialBounds(displayLayout, taskInfo) + val initialBounds = getInitialBounds(displayLayout, taskInfo, displayId) if (canChangeTaskPosition(taskInfo)) { wct.setBounds(taskInfo.token, initialBounds) @@ -1428,7 +1442,8 @@ class DesktopTasksController( private fun getInitialBounds( displayLayout: DisplayLayout, - taskInfo: RunningTaskInfo + taskInfo: RunningTaskInfo, + displayId: Int, ): Rect { val bounds = if (ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS.isTrue) { calculateInitialBounds(displayLayout, taskInfo) @@ -1437,7 +1452,7 @@ class DesktopTasksController( } if (DesktopModeFlags.ENABLE_CASCADING_WINDOWS.isTrue) { - cascadeWindow(taskInfo, bounds, displayLayout) + cascadeWindow(bounds, displayLayout, displayId) } return bounds } @@ -1466,11 +1481,11 @@ class DesktopTasksController( } } - private fun cascadeWindow(task: TaskInfo, bounds: Rect, displayLayout: DisplayLayout) { + private fun cascadeWindow(bounds: Rect, displayLayout: DisplayLayout, displayId: Int) { val stableBounds = Rect() displayLayout.getStableBoundsForDesktopMode(stableBounds) - val activeTasks = taskRepository.getActiveNonMinimizedOrderedTasks(task.displayId) + val activeTasks = taskRepository.getActiveNonMinimizedOrderedTasks(displayId) activeTasks.firstOrNull()?.let { activeTask -> shellTaskOrganizer.getRunningTaskInfo(activeTask)?.let { cascadeWindow(context.resources, stableBounds, @@ -2069,6 +2084,12 @@ class DesktopTasksController( c.removeDesktop(displayId) } } + + override fun moveToExternalDisplay(taskId: Int) { + executeRemoteCallWithTaskPermission(controller, "moveTaskToExternalDisplay") { c -> + c.moveToNextDisplay(taskId) + } + } } private fun logV(msg: String, vararg arguments: Any?) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl index 86351e364cdd..c27813de5358 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/IDesktopMode.aidl @@ -52,4 +52,7 @@ interface IDesktopMode { /** Remove desktop on the given display */ oneway void removeDesktop(int displayId); + + /** Move a task with given `taskId` to external display */ + void moveToExternalDisplay(int taskId); }
\ No newline at end of file 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 cbb08b804dfe..6da39951efbe 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 @@ -463,7 +463,7 @@ public class PipTransition extends PipTransitionController { // so update fixed rotation state to default. mFixedRotationState = FIXED_ROTATION_UNDEFINED; - if (transition != mExitTransition) { + if (transition != mExitTransition && transition != mMoveToBackTransition) { return; } // This means an expand happened before enter-pip finished and we are now "merging" a @@ -477,8 +477,10 @@ public class PipTransition extends PipTransitionController { cancelled = true; } - // Unset exitTransition AFTER cancel so that finishResize knows we are merging. + // Unset both exitTransition and moveToBackTransition AFTER cancel so that + // finishResize knows we are merging. mExitTransition = null; + mMoveToBackTransition = null; if (!cancelled) return; final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo(); if (taskInfo != null) { @@ -515,7 +517,8 @@ public class PipTransition extends PipTransitionController { // means we're expecting the exit transition will be "merged" into another transition // (likely a remote like launcher), so don't fire the finish-callback here -- wait until // the exit transition is merged. - if ((mExitTransition == null || isAnimatingLocally()) && mFinishCallback != null) { + if ((mExitTransition == null || mMoveToBackTransition == null || isAnimatingLocally()) + && mFinishCallback != null) { final SurfaceControl leash = mPipOrganizer.getSurfaceControl(); final boolean hasValidLeash = leash != null && leash.isValid(); WindowContainerTransaction wct = null; @@ -665,17 +668,6 @@ public class PipTransition extends PipTransitionController { return null; } - @Nullable - private TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) { - for (int i = info.getChanges().size() - 1; i >= 0; --i) { - final TransitionInfo.Change change = info.getChanges().get(i); - if (change.getEndFixedRotation() != ROTATION_UNDEFINED) { - return change; - } - } - return null; - } - private void startExitAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, 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 9b815817d4d3..94b344fb575a 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 @@ -16,6 +16,7 @@ package com.android.wm.shell.pip; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.view.WindowManager.TRANSIT_PIP; @@ -346,6 +347,21 @@ public abstract class PipTransitionController implements Transitions.TransitionH return false; } + /** + * Gets a change amongst the transition targets that is in a different final orientation than + * the display, signalling a potential fixed rotation transition. + */ + @Nullable + public TransitionInfo.Change findFixedRotationChange(@NonNull TransitionInfo info) { + for (int i = info.getChanges().size() - 1; i >= 0; --i) { + final TransitionInfo.Change change = info.getChanges().get(i); + if (change.getEndFixedRotation() != ROTATION_UNDEFINED) { + return change; + } + } + return null; + } + /** End the currently-playing PiP animation. */ public void end() { } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java index f40a87c39aef..fcd5c3baab5d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipEnterAnimator.java @@ -16,10 +16,14 @@ package com.android.wm.shell.pip2.animation; +import static android.view.Surface.ROTATION_270; +import static android.view.Surface.ROTATION_90; + import android.animation.Animator; import android.animation.RectEvaluator; import android.animation.ValueAnimator; import android.content.Context; +import android.graphics.Matrix; import android.graphics.PointF; import android.graphics.Rect; import android.view.Surface; @@ -60,6 +64,11 @@ public class PipEnterAnimator extends ValueAnimator private final PointF mInitScale = new PointF(); private final PointF mInitPos = new PointF(); private final Rect mInitCrop = new Rect(); + private final PointF mInitActivityScale = new PointF(); + private final PointF mInitActivityPos = new PointF(); + + Matrix mTransformTensor = new Matrix(); + final float[] mMatrixTmp = new float[9]; public PipEnterAnimator(Context context, @NonNull SurfaceControl leash, @@ -109,6 +118,10 @@ public class PipEnterAnimator extends ValueAnimator @Override public void onAnimationEnd(@NonNull Animator animation) { + if (mFinishTransaction != null) { + onEnterAnimationUpdate(mInitScale, mInitPos, mInitCrop, + 1f /* fraction */, mFinishTransaction); + } if (mAnimationEndCallback != null) { mAnimationEndCallback.run(); } @@ -126,16 +139,24 @@ public class PipEnterAnimator extends ValueAnimator float fraction, SurfaceControl.Transaction tx) { float scaleX = 1 + (initScale.x - 1) * (1 - fraction); float scaleY = 1 + (initScale.y - 1) * (1 - fraction); - tx.setScale(mLeash, scaleX, scaleY); - float posX = initPos.x + (mEndBounds.left - initPos.x) * fraction; float posY = initPos.y + (mEndBounds.top - initPos.y) * fraction; - tx.setPosition(mLeash, posX, posY); + + int normalizedRotation = mRotation; + if (normalizedRotation == ROTATION_270) { + normalizedRotation = -ROTATION_90; + } + float degrees = -normalizedRotation * 90f * fraction; Rect endCrop = new Rect(mEndBounds); endCrop.offsetTo(0, 0); mRectEvaluator.evaluate(fraction, initCrop, endCrop); tx.setCrop(mLeash, mAnimatedRect); + + mTransformTensor.setScale(scaleX, scaleY); + mTransformTensor.postTranslate(posX, posY); + mTransformTensor.postRotate(degrees); + tx.setMatrix(mLeash, mTransformTensor, mMatrixTmp); } // no-ops @@ -153,7 +174,22 @@ public class PipEnterAnimator extends ValueAnimator * calculated differently from generic transitions. * @param pipChange PiP change received as a transition target. */ - public void setEnterStartState(@NonNull TransitionInfo.Change pipChange) { + public void setEnterStartState(@NonNull TransitionInfo.Change pipChange, + @NonNull TransitionInfo.Change pipActivityChange) { + PipUtils.calcEndTransform(pipActivityChange, pipChange, mInitActivityScale, + mInitActivityPos); + if (mStartTransaction != null && pipActivityChange.getLeash() != null) { + mStartTransaction.setCrop(pipActivityChange.getLeash(), null); + mStartTransaction.setScale(pipActivityChange.getLeash(), mInitActivityScale.x, + mInitActivityScale.y); + mStartTransaction.setPosition(pipActivityChange.getLeash(), mInitActivityPos.x, + mInitActivityPos.y); + mFinishTransaction.setCrop(pipActivityChange.getLeash(), null); + mFinishTransaction.setScale(pipActivityChange.getLeash(), mInitActivityScale.x, + mInitActivityScale.y); + mFinishTransaction.setPosition(pipActivityChange.getLeash(), mInitActivityPos.x, + mInitActivityPos.y); + } PipUtils.calcStartTransform(pipChange, mInitScale, mInitPos, mInitCrop); } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java index 8fa5aa933929..a93ef12cb7fa 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/animation/PipExpandAnimator.java @@ -157,6 +157,7 @@ public class PipExpandAnimator extends ValueAnimator .shadow(tx, mLeash, false /* applyCornerRadius */); tx.apply(); } + private Rect getInsets(float fraction) { final Rect startInsets = mSourceRectHintInsets; final Rect endInsets = mZeroInsets; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java index 73be8db0ea8a..0427294579dc 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java @@ -292,23 +292,34 @@ public class PipController implements ConfigurationChangeListener, setDisplayLayout(mDisplayController.getDisplayLayout(displayId)); if (!mPipTransitionState.isInPip()) { + // Skip the PiP-relevant updates if we aren't in a valid PiP state. + if (mPipTransitionState.isInFixedRotation()) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, + "Fixed rotation flag shouldn't be set while in an invalid PiP state"); + } return; } mPipTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio()); - // Update the caches to reflect the new display layout in the movement bounds; - // temporarily update bounds to be at the top left for the movement bounds calculation. - Rect toBounds = new Rect(0, 0, - (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale), - (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale)); - mPipBoundsState.setBounds(toBounds); - mPipTouchHandler.updateMovementBounds(); - - // The policy is to keep PiP snap fraction invariant. - mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction); - mPipBoundsState.setBounds(toBounds); - t.setBounds(mPipTransitionState.mPipTaskToken, toBounds); + if (mPipTransitionState.isInFixedRotation()) { + // Do not change the bounds when in fixed rotation, but do update the movement bounds + // based on the current bounds state and potentially new display layout. + mPipTouchHandler.updateMovementBounds(); + mPipTransitionState.setInFixedRotation(false); + } else { + Rect toBounds = new Rect(0, 0, + (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale), + (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale)); + // Update the caches to reflect the new display layout in the movement bounds; + // temporarily update bounds to be at the top left for the movement bounds calculation. + mPipBoundsState.setBounds(toBounds); + mPipTouchHandler.updateMovementBounds(); + // The policy is to keep PiP snap fraction invariant. + mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction); + mPipBoundsState.setBounds(toBounds); + } + t.setBounds(mPipTransitionState.mPipTaskToken, mPipBoundsState.getBounds()); } private void setDisplayLayout(DisplayLayout layout) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java index a4a7973ef4bb..4d0432e1066e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchHandler.java @@ -406,12 +406,9 @@ public class PipTouchHandler implements PipTransitionState.PipTransitionStateCha // We need to remove the callback even if the shelf is visible, in case it the delayed // callback hasn't been executed yet to avoid the wrong final state. mMainExecutor.removeCallbacks(mMoveOnShelVisibilityChanged); - if (shelfVisible) { - mMoveOnShelVisibilityChanged.run(); - } else { - // Postpone moving in response to hide of Launcher in case there's another change - mMainExecutor.executeDelayed(mMoveOnShelVisibilityChanged, PIP_KEEP_CLEAR_AREAS_DELAY); - } + + // Postpone moving in response to hide of Launcher in case there's another change + mMainExecutor.executeDelayed(mMoveOnShelVisibilityChanged, PIP_KEEP_CLEAR_AREAS_DELAY); } /** diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java index ac1567aba6e9..779e4ea51347 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransition.java @@ -16,7 +16,9 @@ package com.android.wm.shell.pip2.phone; +import static android.app.WindowConfiguration.ROTATION_UNDEFINED; import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED; +import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_270; import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OPEN; @@ -36,6 +38,7 @@ import android.app.ActivityManager; import android.app.PictureInPictureParams; import android.content.Context; import android.graphics.Matrix; +import android.graphics.Point; import android.graphics.Rect; import android.os.Bundle; import android.os.IBinder; @@ -388,8 +391,15 @@ public class PipTransition extends PipTransitionController implements return false; } - Rect startBounds = pipChange.getStartAbsBounds(); + // We expect the PiP activity as a separate change in a config-at-end transition. + TransitionInfo.Change pipActivityChange = getDeferConfigActivityChange(info, + pipChange.getTaskInfo().getToken()); + if (pipActivityChange == null) { + return false; + } + Rect endBounds = pipChange.getEndAbsBounds(); + Rect activityEndBounds = pipActivityChange.getEndAbsBounds(); SurfaceControl pipLeash = mPipTransitionState.mPinnedTaskLeash; Preconditions.checkNotNull(pipLeash, "Leash is null for bounds transition."); @@ -411,14 +421,63 @@ public class PipTransition extends PipTransitionController implements } } + final TransitionInfo.Change fixedRotationChange = findFixedRotationChange(info); + int startRotation = pipChange.getStartRotation(); + int endRotation = fixedRotationChange != null + ? fixedRotationChange.getEndFixedRotation() : ROTATION_UNDEFINED; + final int delta = endRotation == ROTATION_UNDEFINED ? ROTATION_0 + : startRotation - endRotation; + + if (delta != ROTATION_0) { + mPipTransitionState.setInFixedRotation(true); + handleBoundsTypeFixedRotation(pipChange, pipActivityChange, fixedRotationChange); + } + PipEnterAnimator animator = new PipEnterAnimator(mContext, pipLeash, - startTransaction, finishTransaction, endBounds, sourceRectHint, Surface.ROTATION_0); - animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange)); + startTransaction, finishTransaction, endBounds, sourceRectHint, delta); + animator.setAnimationStartCallback(() -> animator.setEnterStartState(pipChange, + pipActivityChange)); animator.setAnimationEndCallback(this::finishInner); animator.start(); return true; } + private void handleBoundsTypeFixedRotation(TransitionInfo.Change pipTaskChange, + TransitionInfo.Change pipActivityChange, + TransitionInfo.Change fixedRotationChange) { + final Rect endBounds = pipTaskChange.getEndAbsBounds(); + final Rect endActivityBounds = pipActivityChange.getEndAbsBounds(); + int startRotation = pipTaskChange.getStartRotation(); + int endRotation = fixedRotationChange.getEndFixedRotation(); + + // Cache the task to activity offset to potentially restore later. + Point activityEndOffset = new Point(endActivityBounds.left - endBounds.left, + endActivityBounds.top - endBounds.top); + + // If we are running a fixed rotation bounds enter PiP animation, + // then update the display layout rotation, and recalculate the end rotation bounds. + // Update the endBounds in place, so that the PiP change is up-to-date. + mPipDisplayLayoutState.rotateTo(endRotation); + float snapFraction = mPipBoundsAlgorithm.getSnapFraction( + mPipBoundsAlgorithm.getEntryDestinationBounds()); + mPipBoundsAlgorithm.applySnapFraction(endBounds, snapFraction); + mPipBoundsState.setBounds(endBounds); + + // Display bounds were already updated to represent the final orientation, + // so we just need to readjust the origin, and perform rotation about (0, 0). + boolean isClockwise = (endRotation - startRotation) == -ROTATION_270; + Rect displayBounds = mPipDisplayLayoutState.getDisplayBounds(); + int originTranslateX = isClockwise ? 0 : -displayBounds.width(); + int originTranslateY = isClockwise ? -displayBounds.height() : 0; + endBounds.offset(originTranslateX, originTranslateY); + + // Update the activity end bounds in place as well, as this is used for transform + // calculation later. + endActivityBounds.offsetTo(endBounds.left + activityEndOffset.x, + endBounds.top + activityEndOffset.y); + } + + private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info, @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @@ -533,6 +592,19 @@ public class PipTransition extends PipTransitionController implements } @Nullable + private TransitionInfo.Change getDeferConfigActivityChange(TransitionInfo info, + @NonNull WindowContainerToken parent) { + for (TransitionInfo.Change change : info.getChanges()) { + if (change.getTaskInfo() == null + && change.hasFlags(TransitionInfo.FLAG_CONFIG_AT_END) + && change.getParent() != null && change.getParent().equals(parent)) { + return change; + } + } + return null; + } + + @Nullable private TransitionInfo.Change getChangeByToken(TransitionInfo info, WindowContainerToken token) { for (TransitionInfo.Change change : info.getChanges()) { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java index a132796f4a84..ccdd66b5d1a8 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTransitionState.java @@ -155,6 +155,8 @@ public class PipTransitionState { @Nullable private Runnable mOnIdlePipTransitionStateRunnable; + private boolean mInFixedRotation = false; + /** * An interface to track state updates as we progress through PiP transitions. */ @@ -256,7 +258,7 @@ public class PipTransitionState { private void maybeRunOnIdlePipTransitionStateCallback() { if (mOnIdlePipTransitionStateRunnable != null && isPipStateIdle()) { - mOnIdlePipTransitionStateRunnable.run(); + mMainHandler.post(mOnIdlePipTransitionStateRunnable); mOnIdlePipTransitionStateRunnable = null; } } @@ -303,6 +305,23 @@ public class PipTransitionState { } /** + * @return true if either in swipe or button-nav fixed rotation. + */ + public boolean isInFixedRotation() { + return mInFixedRotation; + } + + /** + * Sets the fixed rotation flag. + */ + public void setInFixedRotation(boolean inFixedRotation) { + mInFixedRotation = inFixedRotation; + if (!inFixedRotation) { + maybeRunOnIdlePipTransitionStateCallback(); + } + } + + /** * @return true if in swipe PiP to home. Note that this is true until overlay fades if used too. */ public boolean isInSwipePipToHomeTransition() { @@ -351,7 +370,7 @@ public class PipTransitionState { public boolean isPipStateIdle() { // This needs to be a valid in-PiP state that isn't a transient state. - return mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS; + return (mState == ENTERED_PIP || mState == CHANGED_PIP_BOUNDS) && !isInFixedRotation(); } @Override 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 9e39f440915c..a23b576beebc 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 @@ -772,15 +772,25 @@ public class SplitScreenController implements SplitDragPolicy.Starter, instanceId); } + @Override + public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent, + @SplitPosition int position, @Nullable Bundle options, + @Nullable WindowContainerToken hideTaskToken) { + startIntent(intent, userId1, fillInIntent, position, options, hideTaskToken, + false /* forceLaunchNewTask */); + } + /** * Starts the given intent into split. + * * @param hideTaskToken If non-null, a task matching this token will be moved to back in the * same window container transaction as the starting of the intent. + * @param forceLaunchNewTask If true, this method will skip the check for a background task + * matching the intent and launch a new task. */ - @Override public void startIntent(PendingIntent intent, int userId1, @Nullable Intent fillInIntent, @SplitPosition int position, @Nullable Bundle options, - @Nullable WindowContainerToken hideTaskToken) { + @Nullable WindowContainerToken hideTaskToken, boolean forceLaunchNewTask) { ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN, "startIntent(): intent=%s user=%d fillInIntent=%s position=%d", intent, userId1, fillInIntent, position); @@ -798,8 +808,9 @@ public class SplitScreenController implements SplitDragPolicy.Starter, // To prevent accumulating large number of instances in the background, reuse task // in the background. If we don't explicitly reuse, new may be created even if the app // isn't multi-instance because WM won't automatically remove/reuse the previous instance - final ActivityManager.RecentTaskInfo taskInfo = mRecentTasksOptional - .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1, + final ActivityManager.RecentTaskInfo taskInfo = forceLaunchNewTask ? null : + mRecentTasksOptional + .map(recentTasks -> recentTasks.findTaskInBackground(component, userId1, hideTaskToken)) .orElse(null); if (taskInfo != null) { 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 fea987ac53e2..3e76403de51b 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 @@ -175,6 +175,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private static final String TAG = StageCoordinator.class.getSimpleName(); + // The duration in ms to prevent launch-adjacent from working after split screen is first + // entered + private static final int DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS = 1000; + private final StageTaskListener mMainStage; private final StageTaskListener mSideStage; @SplitPosition @@ -239,6 +243,10 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, private SplitScreen.SplitInvocationListener mSplitInvocationListener; private Executor mSplitInvocationListenerExecutor; + // Re-enables launch-adjacent handling on the split root task. This needs to be a member + // because we will be posting and removing it from the handler. + private final Runnable mReEnableLaunchAdjacentOnRoot = () -> setLaunchAdjacentDisabled(false); + /** * Since StageCoordinator only coordinates MainStage and SideStage, it shouldn't support * CompatUI layouts. CompatUI is handled separately by MainStage and SideStage. @@ -2699,6 +2707,16 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, } } + /** + * Sets whether launch-adjacent is disabled or enabled. + */ + private void setLaunchAdjacentDisabled(boolean disabled) { + ProtoLog.d(WM_SHELL_SPLIT_SCREEN, "setLaunchAdjacentDisabled: disabled=%b", disabled); + final WindowContainerTransaction wct = new WindowContainerTransaction(); + wct.setDisableLaunchAdjacent(mRootTaskInfo.token, disabled); + mTaskOrganizer.applyTransaction(wct); + } + /** Starts the pending transition animation. */ public boolean startPendingAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, @@ -2711,6 +2729,14 @@ public class StageCoordinator implements SplitLayout.SplitLayoutHandler, if (mSplitTransitions.isPendingEnter(transition)) { shouldAnimate = startPendingEnterAnimation(transition, mSplitTransitions.mPendingEnter, info, startTransaction, finishTransaction); + + // Disable launch adjacent after an enter animation to prevent cases where apps are + // incorrectly trampolining and incorrectly triggering a double launch-adjacent task + // launch (ie. main -> split -> main). See b/344216031 + setLaunchAdjacentDisabled(true); + mMainHandler.removeCallbacks(mReEnableLaunchAdjacentOnRoot); + mMainHandler.postDelayed(mReEnableLaunchAdjacentOnRoot, + DISABLE_LAUNCH_ADJACENT_AFTER_ENTER_TIMEOUT_MS); } else if (mSplitTransitions.isPendingDismiss(transition)) { final SplitScreenTransitions.DismissSession dismiss = mSplitTransitions.mPendingDismiss; shouldAnimate = startPendingDismissAnimation( diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java index ae4bd1615ae1..6313231b449e 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java @@ -244,8 +244,9 @@ public class StageTaskListener implements ShellTaskOrganizer.TaskListener { return; } mChildrenTaskInfo.put(taskInfo.taskId, taskInfo); + mVisible = taskInfo.isVisible && taskInfo.isVisibleRequested; mCallbacks.onChildTaskStatusChanged(this, taskInfo.taskId, true /* present */, - taskInfo.isVisible && taskInfo.isVisibleRequested); + mVisible); } else { throw new IllegalArgumentException(this + "\n Unknown task: " + taskInfo + "\n mRootTaskInfo: " + mRootTaskInfo); 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 f783b45589b3..5a2abe1e7e25 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 @@ -16,9 +16,9 @@ package com.android.wm.shell.unfold; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.WindowManager.KEYGUARD_VISIBILITY_TRANSIT_FLAGS; import static android.view.WindowManager.TRANSIT_CHANGE; -import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH; import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS; @@ -30,6 +30,7 @@ import android.window.TransitionInfo; import android.window.TransitionRequestInfo; import android.window.WindowContainerTransaction; +import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -45,6 +46,8 @@ import com.android.wm.shell.unfold.animation.FullscreenUnfoldTaskAnimator; import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.List; import java.util.concurrent.Executor; @@ -56,6 +59,18 @@ import java.util.concurrent.Executor; */ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener { + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + DefaultDisplayChange.DEFAULT_DISPLAY_NO_CHANGE, + DefaultDisplayChange.DEFAULT_DISPLAY_UNFOLD, + DefaultDisplayChange.DEFAULT_DISPLAY_FOLD, + }) + private @interface DefaultDisplayChange { + int DEFAULT_DISPLAY_NO_CHANGE = 0; + int DEFAULT_DISPLAY_UNFOLD = 1; + int DEFAULT_DISPLAY_FOLD = 2; + } + private final ShellUnfoldProgressProvider mUnfoldProgressProvider; private final Transitions mTransitions; private final Executor mExecutor; @@ -66,7 +81,10 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Nullable private IBinder mTransition; + // TODO: b/318803244 - remove when we could guarantee finishing the animation + // after startAnimation callback private boolean mAnimationFinished = false; + private float mLastAnimationProgress = 0.0f; private final List<UnfoldTaskAnimator> mAnimators = new ArrayList<>(); public UnfoldTransitionHandler(ShellInit shellInit, @@ -107,16 +125,6 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @NonNull SurfaceControl.Transaction startTransaction, @NonNull SurfaceControl.Transaction finishTransaction, @NonNull TransitionFinishCallback finishCallback) { - 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 - mTransition = transition; - - ProtoLog.v(WM_SHELL_TRANSITIONS, "UnfoldTransitionHandler: " - + "take over startAnimation"); - } - if (transition != mTransition) return false; for (int i = 0; i < mAnimators.size(); i++) { @@ -158,6 +166,8 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public void onStateChangeProgress(float progress) { + mLastAnimationProgress = progress; + if (mTransition == null) return; SurfaceControl.Transaction transaction = null; @@ -182,8 +192,14 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public void onStateChangeFinished() { - mAnimationFinished = true; finishTransitionIfNeeded(); + + // mLastAnimationProgress is guaranteed to be 0f when folding finishes, see + // {@link PhysicsBasedUnfoldTransitionProgressProvider#cancelTransition}. + // We can use it as an indication that the next animation progress events will be related + // to unfolding, so let's reset mAnimationFinished to 'false' in this case. + final boolean isFoldingFinished = mLastAnimationProgress == 0f; + mAnimationFinished = !isFoldingFinished; } @Override @@ -211,6 +227,12 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene // Apply changes happening during the unfold animation immediately t.apply(); finishCallback.onTransitionFinished(null); + + if (getDefaultDisplayChange(info) == DefaultDisplayChange.DEFAULT_DISPLAY_FOLD) { + // Force-finish current unfold animation as we are processing folding now which doesn't + // have any animations on the Shell side + finishTransitionIfNeeded(); + } } /** Whether `request` contains an unfold action. */ @@ -219,18 +241,25 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene if (!ValueAnimator.areAnimatorsEnabled()) return false; return (request.getType() == TRANSIT_CHANGE - && request.getDisplayChange() != null - && isUnfoldDisplayChange(request.getDisplayChange())); + && getDefaultDisplayChange(request.getDisplayChange()) + == DefaultDisplayChange.DEFAULT_DISPLAY_UNFOLD); } - private boolean isUnfoldDisplayChange( - @NonNull TransitionRequestInfo.DisplayChange displayChange) { + @DefaultDisplayChange + private int getDefaultDisplayChange( + @Nullable TransitionRequestInfo.DisplayChange displayChange) { + if (displayChange == null) return DefaultDisplayChange.DEFAULT_DISPLAY_NO_CHANGE; + + if (displayChange.getDisplayId() != DEFAULT_DISPLAY) { + return DefaultDisplayChange.DEFAULT_DISPLAY_NO_CHANGE; + } + if (!displayChange.isPhysicalDisplayChanged()) { - return false; + return DefaultDisplayChange.DEFAULT_DISPLAY_NO_CHANGE; } if (displayChange.getStartAbsBounds() == null || displayChange.getEndAbsBounds() == null) { - return false; + return DefaultDisplayChange.DEFAULT_DISPLAY_NO_CHANGE; } // Handle only unfolding, currently we don't have an animation when folding @@ -239,17 +268,11 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene final int startArea = displayChange.getStartAbsBounds().width() * displayChange.getStartAbsBounds().height(); - return endArea > startArea; + return endArea > startArea ? DefaultDisplayChange.DEFAULT_DISPLAY_UNFOLD + : DefaultDisplayChange.DEFAULT_DISPLAY_FOLD; } - /** Whether `transitionInfo` contains an unfold action. */ - public boolean shouldPlayUnfoldAnimation(@NonNull TransitionInfo transitionInfo) { - // Unfold animation won't play when animations are disabled - if (!ValueAnimator.areAnimatorsEnabled()) return false; - // Only handle transitions that are marked as physical display switch - // See PhysicalDisplaySwitchTransitionLauncher for the conditions - if ((transitionInfo.getFlags() & TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH) == 0) return false; - + private int getDefaultDisplayChange(@NonNull TransitionInfo transitionInfo) { for (int i = 0; i < transitionInfo.getChanges().size(); i++) { final TransitionInfo.Change change = transitionInfo.getChanges().get(i); // We are interested only in display container changes @@ -268,11 +291,13 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene * change.getStartAbsBounds().height(); if (afterArea > beforeArea) { - return true; + return DefaultDisplayChange.DEFAULT_DISPLAY_UNFOLD; + } else { + return DefaultDisplayChange.DEFAULT_DISPLAY_FOLD; } } - return false; + return DefaultDisplayChange.DEFAULT_DISPLAY_NO_CHANGE; } @Nullable @@ -293,10 +318,6 @@ public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListene @Override public void onFoldStateChanged(boolean isFolded) { if (isFolded) { - // Reset unfold animation finished flag on folding, so it could be used next time - // when we unfold the device as an indication that animation hasn't finished yet - mAnimationFinished = false; - // If we are currently animating unfold animation we should finish it because // the animation might not start and finish as the device was folded finishTransitionIfNeeded(); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java index 2c621b1f1a52..d3b7ca15856f 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java @@ -514,8 +514,12 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin )); } else { mWindowDecorViewHolder.bindData(new AppHeaderViewHolder.HeaderData( - mTaskInfo, TaskInfoKt.getRequestingImmersive(mTaskInfo), inFullImmersive, - hasGlobalFocus + mTaskInfo, + TaskInfoKt.getRequestingImmersive(mTaskInfo), + inFullImmersive, + hasGlobalFocus, + /* maximizeHoverEnabled= */ canOpenMaximizeMenu( + /* animatingTaskResizeOrReposition= */ false) )); } Trace.endSection(); @@ -1616,8 +1620,14 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin void setAnimatingTaskResizeOrReposition(boolean animatingTaskResizeOrReposition) { if (mRelayoutParams.mLayoutResId == R.layout.desktop_mode_app_handle) return; - asAppHeader(mWindowDecorViewHolder) - .setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition); + final boolean inFullImmersive = + mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId); + asAppHeader(mWindowDecorViewHolder).bindData(new AppHeaderViewHolder.HeaderData( + mTaskInfo, + TaskInfoKt.getRequestingImmersive(mTaskInfo), + inFullImmersive, + isFocused(), + /* maximizeHoverEnabled= */ canOpenMaximizeMenu(animatingTaskResizeOrReposition))); } /** @@ -1634,6 +1644,16 @@ public class DesktopModeWindowDecoration extends WindowDecoration<WindowDecorLin asAppHeader(mWindowDecorViewHolder).onMaximizeWindowHoverEnter(); } + private boolean canOpenMaximizeMenu(boolean animatingTaskResizeOrReposition) { + if (!Flags.enableFullyImmersiveInDesktop()) { + return !animatingTaskResizeOrReposition; + } + final boolean inImmersiveAndRequesting = + mDesktopRepository.isTaskInFullImmersiveState(mTaskInfo.taskId) + && TaskInfoKt.getRequestingImmersive(mTaskInfo); + return !animatingTaskResizeOrReposition && !inImmersiveAndRequesting; + } + @Override public String toString() { return "{" diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt index 0cb219ae4b81..3ae5a1afc7e2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt @@ -62,6 +62,7 @@ import com.android.wm.shell.R import com.android.wm.shell.RootTaskDisplayAreaOrganizer import com.android.wm.shell.common.DisplayController import com.android.wm.shell.common.SyncTransactionQueue +import com.android.wm.shell.desktopmode.calculateMaximizeBounds import com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE import com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_LINEAR_IN import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer @@ -73,7 +74,8 @@ import java.util.function.Supplier /** * Menu that appears when user long clicks the maximize button. Gives the user the option to - * maximize the task or snap the task to the right or left half of the screen. + * maximize the task or restore previous task bounds from the maximized state and to snap the task + * to the right or left half of the screen. */ class MaximizeMenu( private val syncQueue: SyncTransactionQueue, @@ -176,6 +178,7 @@ class MaximizeMenu( "MaximizeMenu") maximizeMenuView = MaximizeMenuView( context = decorWindowContext, + sizeToggleDirection = getSizeToggleDirection(), menuHeight = menuHeight, menuPadding = menuPadding, ).also { menuView -> @@ -202,6 +205,18 @@ class MaximizeMenu( } } + private fun getSizeToggleDirection(): MaximizeMenuView.SizeToggleDirection { + val maximizeBounds = calculateMaximizeBounds( + displayController.getDisplayLayout(taskInfo.displayId)!!, + taskInfo + ) + val maximized = taskInfo.configuration.windowConfiguration.bounds.equals(maximizeBounds) + return if (maximized) + MaximizeMenuView.SizeToggleDirection.RESTORE + else + MaximizeMenuView.SizeToggleDirection.MAXIMIZE + } + private fun loadDimensionPixelSize(resourceId: Int): Int { return if (resourceId == Resources.ID_NULL) { 0 @@ -236,18 +251,19 @@ class MaximizeMenu( * resizing a Task. */ class MaximizeMenuView( - context: Context, + private val context: Context, + private val sizeToggleDirection: SizeToggleDirection, private val menuHeight: Int, - private val menuPadding: Int, + private val menuPadding: Int ) { val rootView = LayoutInflater.from(context) .inflate(R.layout.desktop_mode_window_decor_maximize_menu, null /* root */) as ViewGroup private val container = requireViewById(R.id.container) private val overlay = requireViewById(R.id.maximize_menu_overlay) - private val maximizeText = - requireViewById(R.id.maximize_menu_maximize_window_text) as TextView - private val maximizeButton = - requireViewById(R.id.maximize_menu_maximize_button) as Button + private val sizeToggleButtonText = + requireViewById(R.id.maximize_menu_size_toggle_button_text) as TextView + private val sizeToggleButton = + requireViewById(R.id.maximize_menu_size_toggle_button) as Button private val snapWindowText = requireViewById(R.id.maximize_menu_snap_window_text) as TextView private val snapRightButton = @@ -263,8 +279,6 @@ class MaximizeMenu( .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_outline_radius) private val outlineStroke = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_outline_stroke) - private val fillPadding = context.resources - .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_padding) private val fillRadius = context.resources .getDimensionPixelSize(R.dimen.desktop_mode_maximize_menu_buttons_fill_radius) @@ -324,7 +338,7 @@ class MaximizeMenu( return@setOnHoverListener false } - maximizeButton.setOnClickListener { onMaximizeClickListener?.invoke() } + sizeToggleButton.setOnClickListener { onMaximizeClickListener?.invoke() } snapRightButton.setOnClickListener { onRightSnapClickListener?.invoke() } snapLeftButton.setOnClickListener { onLeftSnapClickListener?.invoke() } rootView.setOnTouchListener { _, event -> @@ -335,9 +349,17 @@ class MaximizeMenu( true } + val btnTextId = if (sizeToggleDirection == SizeToggleDirection.RESTORE) + R.string.desktop_mode_maximize_menu_restore_button_text + else + R.string.desktop_mode_maximize_menu_maximize_button_text + val btnText = context.resources.getText(btnTextId) + sizeToggleButton.contentDescription = btnText + sizeToggleButtonText.text = btnText + // To prevent aliasing. - maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null) - maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + sizeToggleButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + sizeToggleButtonText.setLayerType(View.LAYER_TYPE_SOFTWARE, null) } /** Bind the menu views to the new [RunningTaskInfo] data. */ @@ -348,8 +370,8 @@ class MaximizeMenu( rootView.background.setTint(style.backgroundColor) // Maximize option. - maximizeButton.background = style.maximizeOption.drawable - maximizeText.setTextColor(style.textColor) + sizeToggleButton.background = style.maximizeOption.drawable + sizeToggleButtonText.setTextColor(style.textColor) // Snap options. snapWindowText.setTextColor(style.textColor) @@ -358,8 +380,8 @@ class MaximizeMenu( /** Animate the opening of the menu */ fun animateOpenMenu(onEnd: () -> Unit) { - maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null) - maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null) + sizeToggleButton.setLayerType(View.LAYER_TYPE_HARDWARE, null) + sizeToggleButtonText.setLayerType(View.LAYER_TYPE_HARDWARE, null) menuAnimatorSet = AnimatorSet() menuAnimatorSet?.playTogether( ObjectAnimator.ofFloat(rootView, SCALE_Y, STARTING_MENU_HEIGHT_SCALE, 1f) @@ -388,9 +410,9 @@ class MaximizeMenu( // Scale up the children of the maximize menu so that the menu // scale is cancelled out and only the background is scaled. val value = animatedValue as Float - maximizeButton.scaleY = value + sizeToggleButton.scaleY = value snapButtonsLayout.scaleY = value - maximizeText.scaleY = value + sizeToggleButtonText.scaleY = value snapWindowText.scaleY = value } }, @@ -409,9 +431,9 @@ class MaximizeMenu( startDelay = CONTROLS_ALPHA_OPEN_MENU_ANIMATION_DELAY_MS addUpdateListener { val value = animatedValue as Float - maximizeButton.alpha = value + sizeToggleButton.alpha = value snapButtonsLayout.alpha = value - maximizeText.alpha = value + sizeToggleButtonText.alpha = value snapWindowText.alpha = value } }, @@ -423,8 +445,8 @@ class MaximizeMenu( ) menuAnimatorSet?.addListener( onEnd = { - maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null) - maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + sizeToggleButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + sizeToggleButtonText.setLayerType(View.LAYER_TYPE_SOFTWARE, null) onEnd.invoke() } ) @@ -433,8 +455,8 @@ class MaximizeMenu( /** Animate the closing of the menu */ fun animateCloseMenu(onEnd: (() -> Unit)) { - maximizeButton.setLayerType(View.LAYER_TYPE_HARDWARE, null) - maximizeText.setLayerType(View.LAYER_TYPE_HARDWARE, null) + sizeToggleButton.setLayerType(View.LAYER_TYPE_HARDWARE, null) + sizeToggleButtonText.setLayerType(View.LAYER_TYPE_HARDWARE, null) cancelAnimation() menuAnimatorSet = AnimatorSet() menuAnimatorSet?.playTogether( @@ -464,9 +486,9 @@ class MaximizeMenu( // Scale up the children of the maximize menu so that the menu // scale is cancelled out and only the background is scaled. val value = animatedValue as Float - maximizeButton.scaleY = value + sizeToggleButton.scaleY = value snapButtonsLayout.scaleY = value - maximizeText.scaleY = value + sizeToggleButtonText.scaleY = value snapWindowText.scaleY = value } }, @@ -485,9 +507,9 @@ class MaximizeMenu( duration = ALPHA_ANIMATION_DURATION_MS addUpdateListener { val value = animatedValue as Float - maximizeButton.alpha = value + sizeToggleButton.alpha = value snapButtonsLayout.alpha = value - maximizeText.alpha = value + sizeToggleButtonText.alpha = value snapWindowText.alpha = value } }, @@ -498,8 +520,8 @@ class MaximizeMenu( ) menuAnimatorSet?.addListener( onEnd = { - maximizeButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null) - maximizeText.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + sizeToggleButton.setLayerType(View.LAYER_TYPE_SOFTWARE, null) + sizeToggleButtonText.setLayerType(View.LAYER_TYPE_SOFTWARE, null) onEnd?.invoke() } ) @@ -509,8 +531,8 @@ class MaximizeMenu( /** Request that the accessibility service focus on the menu. */ fun requestAccessibilityFocus() { // Focus the first button in the menu by default. - maximizeButton.post { - maximizeButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) + sizeToggleButton.post { + sizeToggleButton.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED) } } @@ -685,15 +707,31 @@ class MaximizeMenu( paint.color = strokeAndFillColor paint.style = Paint.Style.FILL }) + + val (horizontalFillPadding, verticalFillPadding) = + if (sizeToggleDirection == SizeToggleDirection.MAXIMIZE) { + context.resources.getDimensionPixelSize(R.dimen + .desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding) to + context.resources.getDimensionPixelSize(R.dimen + .desktop_mode_maximize_menu_snap_and_maximize_buttons_fill_padding) + } else { + context.resources.getDimensionPixelSize(R.dimen + .desktop_mode_maximize_menu_restore_button_fill_horizontal_padding) to + context.resources.getDimensionPixelSize(R.dimen + .desktop_mode_maximize_menu_restore_button_fill_vertical_padding) + } + return LayerDrawable(layers.toTypedArray()).apply { when (numberOfLayers) { 3 -> { setLayerInset(1, outlineStroke) - setLayerInset(2, fillPadding) + setLayerInset(2, horizontalFillPadding, verticalFillPadding, + horizontalFillPadding, verticalFillPadding) } 4 -> { setLayerInset(intArrayOf(1, 2), outlineStroke) - setLayerInset(3, fillPadding) + setLayerInset(3, horizontalFillPadding, verticalFillPadding, + horizontalFillPadding, verticalFillPadding) } else -> error("Unexpected number of layers: $numberOfLayers") } @@ -737,6 +775,11 @@ class MaximizeMenu( enum class SnapToHalfSelection { NONE, LEFT, RIGHT } + + /** The possible selection states of the size toggle button in the maximize menu. */ + enum class SizeToggleDirection { + MAXIMIZE, RESTORE + } } companion object { diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt index cf03b3f74dc7..d94391820d05 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHeaderViewHolder.kt @@ -70,7 +70,7 @@ class AppHeaderViewHolder( rootView: View, onCaptionTouchListener: View.OnTouchListener, onCaptionButtonClickListener: View.OnClickListener, - onLongClickListener: OnLongClickListener, + private val onLongClickListener: OnLongClickListener, onCaptionGenericMotionListener: View.OnGenericMotionListener, appName: CharSequence, appIconBitmap: Bitmap, @@ -81,7 +81,8 @@ class AppHeaderViewHolder( val taskInfo: RunningTaskInfo, val isRequestingImmersive: Boolean, val inFullImmersiveState: Boolean, - val hasGlobalFocus: Boolean + val hasGlobalFocus: Boolean, + val enableMaximizeLongClick: Boolean, ) : Data() private val decorThemeUtil = DecorThemeUtil(context) @@ -160,19 +161,30 @@ class AppHeaderViewHolder( } override fun bindData(data: HeaderData) { - bindData(data.taskInfo, data.isRequestingImmersive, data.inFullImmersiveState, - data.hasGlobalFocus) + bindData( + data.taskInfo, + data.isRequestingImmersive, + data.inFullImmersiveState, + data.hasGlobalFocus, + data.enableMaximizeLongClick + ) } private fun bindData( taskInfo: RunningTaskInfo, isRequestingImmersive: Boolean, inFullImmersiveState: Boolean, - hasGlobalFocus: Boolean + hasGlobalFocus: Boolean, + enableMaximizeLongClick: Boolean, ) { if (DesktopModeFlags.ENABLE_THEMED_APP_HEADERS.isTrue()) { - bindDataWithThemedHeaders(taskInfo, isRequestingImmersive, inFullImmersiveState, - hasGlobalFocus) + bindDataWithThemedHeaders( + taskInfo, + isRequestingImmersive, + inFullImmersiveState, + hasGlobalFocus, + enableMaximizeLongClick, + ) } else { bindDataLegacy(taskInfo, hasGlobalFocus) } @@ -215,7 +227,8 @@ class AppHeaderViewHolder( taskInfo: RunningTaskInfo, requestingImmersive: Boolean, inFullImmersiveState: Boolean, - hasGlobalFocus: Boolean + hasGlobalFocus: Boolean, + enableMaximizeLongClick: Boolean, ) { val header = fillHeaderInfo(taskInfo, hasGlobalFocus) val headerStyle = getHeaderStyle(header) @@ -281,6 +294,16 @@ class AppHeaderViewHolder( drawableInsets = closeDrawableInsets ) } + if (!enableMaximizeLongClick) { + maximizeButtonView.cancelHoverAnimation() + } + maximizeButtonView.hoverDisabled = !enableMaximizeLongClick + maximizeWindowButton.onLongClickListener = if (enableMaximizeLongClick) { + onLongClickListener + } else { + // Disable long-click to open maximize menu when in immersive. + null + } } override fun onHandleMenuOpened() {} @@ -291,14 +314,6 @@ class AppHeaderViewHolder( } } - fun setAnimatingTaskResizeOrReposition(animatingTaskResizeOrReposition: Boolean) { - // If animating a task resize or reposition, cancel any running hover animations - if (animatingTaskResizeOrReposition) { - maximizeButtonView.cancelHoverAnimation() - } - maximizeButtonView.hoverDisabled = animatingTaskResizeOrReposition - } - fun onMaximizeWindowHoverExit() { maximizeButtonView.cancelHoverAnimation() } @@ -364,6 +379,11 @@ class AppHeaderViewHolder( private fun shouldShowExitFullImmersiveIcon( requestingImmersive: Boolean, inFullImmersiveState: Boolean + ): Boolean = isInFullImmersiveStateAndRequesting(requestingImmersive, inFullImmersiveState) + + private fun isInFullImmersiveStateAndRequesting( + requestingImmersive: Boolean, + inFullImmersiveState: Boolean ): Boolean = Flags.enableFullyImmersiveInDesktop() && requestingImmersive && inFullImmersiveState diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java index 094af9652ea3..a1d4a1a301bd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleOverflowTest.java @@ -26,6 +26,7 @@ import android.view.WindowManager; import androidx.test.filters.SmallTest; +import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.wm.shell.ShellTestCase; import org.junit.Before; @@ -45,6 +46,7 @@ public class BubbleOverflowTest extends ShellTestCase { private TestableBubblePositioner mPositioner; private BubbleOverflow mOverflow; private BubbleExpandedViewManager mExpandedViewManager; + private BubbleLogger mBubbleLogger; @Mock private BubbleController mBubbleController; @@ -58,6 +60,7 @@ public class BubbleOverflowTest extends ShellTestCase { mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(mBubbleController); mPositioner = new TestableBubblePositioner(mContext, mContext.getSystemService(WindowManager.class)); + mBubbleLogger = new BubbleLogger(new UiEventLoggerFake()); when(mBubbleController.getPositioner()).thenReturn(mPositioner); when(mBubbleController.getStackView()).thenReturn(mBubbleStackView); @@ -77,7 +80,7 @@ public class BubbleOverflowTest extends ShellTestCase { @Test public void test_initialize_forBubbleBar() { - mOverflow.initializeForBubbleBar(mExpandedViewManager, mPositioner); + mOverflow.initializeForBubbleBar(mExpandedViewManager, mPositioner, mBubbleLogger); assertThat(mOverflow.getBubbleBarExpandedView()).isNotNull(); assertThat(mOverflow.getExpandedView()).isNull(); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt index dde9fda13ea9..0825b6b0d7be 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt @@ -18,7 +18,10 @@ package com.android.wm.shell.desktopmode import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.SetFlagsRule +import com.android.dx.mockito.inline.extended.ExtendedMockito.clearInvocations +import com.android.dx.mockito.inline.extended.ExtendedMockito.staticMockMarker import com.android.dx.mockito.inline.extended.ExtendedMockito.verify +import com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule import com.android.window.flags.Flags @@ -26,15 +29,16 @@ import com.android.wm.shell.EventLogTags import com.android.wm.shell.ShellTestCase import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.NO_SESSION_ID import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ResizeTrigger -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.InputMethod -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskSizeUpdate -import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON -import kotlinx.coroutines.runBlocking +import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason +import com.google.common.truth.Truth.assertThat import org.junit.Rule import org.junit.Test import org.mockito.kotlin.eq @@ -49,40 +53,87 @@ class DesktopModeEventLoggerTest : ShellTestCase() { @JvmField @Rule(order = 0) val extendedMockitoRule = ExtendedMockitoRule.Builder(this) - .mockStatic(FrameworkStatsLog::class.java) - .mockStatic(EventLogTags::class.java).build()!! + .mockStatic(FrameworkStatsLog::class.java) + .mockStatic(EventLogTags::class.java).build()!! @JvmField @Rule(order = 1) val setFlagsRule = SetFlagsRule() @Test - fun logSessionEnter_enterReason() = runBlocking { - desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER) + fun logSessionEnter_logsEnterReasonWithNewSessionId() { + desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER) + + val sessionId = desktopModeEventLogger.currentSessionId.get() + assertThat(sessionId).isNotEqualTo(NO_SESSION_ID) + verify { + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED), + /* event */ + eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER), + /* enter_reason */ + eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER), + /* exit_reason */ + eq(0), + /* sessionId */ + eq(sessionId) + ) + } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verify { + EventLogTags.writeWmShellEnterDesktopMode( + eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason), + eq(sessionId) + ) + } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logSessionEnter_ongoingSession_logsEnterReasonWithNewSessionId() { + val previousSessionId = startDesktopModeSession() + desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER) + + val sessionId = desktopModeEventLogger.currentSessionId.get() + assertThat(sessionId).isNotEqualTo(NO_SESSION_ID) + assertThat(sessionId).isNotEqualTo(previousSessionId) verify { FrameworkStatsLog.write( eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED), /* event */ eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EVENT__ENTER), /* enter_reason */ - eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__UNKNOWN_ENTER), + eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__ENTER_REASON__KEYBOARD_SHORTCUT_ENTER), /* exit_reason */ eq(0), /* sessionId */ - eq(SESSION_ID) + eq(sessionId) ) } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellEnterDesktopMode( - eq(EnterReason.UNKNOWN_ENTER.reason), - eq(SESSION_ID)) + eq(EnterReason.KEYBOARD_SHORTCUT_ENTER.reason), + eq(sessionId) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logSessionExit_exitReason() = runBlocking { - desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT) + fun logSessionExit_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logSessionExit_logsExitReasonAndClearsSessionId() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logSessionExit(ExitReason.DRAG_TO_EXIT) verify { FrameworkStatsLog.write( @@ -92,24 +143,39 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* enter_reason */ eq(0), /* exit_reason */ - eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__UNKNOWN_EXIT), + eq(FrameworkStatsLog.DESKTOP_MODE_UICHANGED__EXIT_REASON__DRAG_TO_EXIT), /* sessionId */ - eq(SESSION_ID) + eq(sessionId) ) } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellExitDesktopMode( - eq(ExitReason.UNKNOWN_EXIT.reason), - eq(SESSION_ID)) + eq(ExitReason.DRAG_TO_EXIT.reason), + eq(sessionId) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + assertThat(desktopModeEventLogger.currentSessionId.get()).isEqualTo(NO_SESSION_ID) + } + + @Test + fun logTaskAdded_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskAdded(TASK_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskAdded_taskUpdate() = runBlocking { - desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE) + fun logTaskAdded_logsTaskUpdate() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskAdded(TASK_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED), /* instance_id */ @@ -125,36 +191,52 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logTaskRemoved_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskRemoved(TASK_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskRemoved_taskUpdate() = runBlocking { - desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE) + fun logTaskRemoved_taskUpdate() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskRemoved(TASK_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED), /* instance_id */ @@ -170,39 +252,57 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logTaskInfoChanged_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskInfoChanged_taskUpdate() = runBlocking { - desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE) + fun logTaskInfoChanged_taskUpdate() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskInfoChanged(TASK_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), /* instance_id */ eq(TASK_UPDATE.instanceId), /* uid */ @@ -216,40 +316,51 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() = runBlocking { - desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, - createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT)) + fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskInfoChanged( + createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT) + ) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), /* instance_id */ eq(TASK_UPDATE.instanceId), /* uid */ @@ -263,42 +374,53 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), /* minimize_reason */ eq(MinimizeReason.TASK_LIMIT.reason), /* unminimize_reason */ eq(UNSET_UNMINIMIZE_REASON), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(MinimizeReason.TASK_LIMIT.reason), eq(UNSET_UNMINIMIZE_REASON), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test - fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() = runBlocking { - desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, - createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP)) + fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskInfoChanged( + createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP) + ) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE), /* task_event */ - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), /* instance_id */ eq(TASK_UPDATE.instanceId), /* uid */ @@ -312,39 +434,55 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* task_y */ eq(TASK_UPDATE.taskY), /* session_id */ - eq(SESSION_ID), + eq(sessionId), /* minimize_reason */ eq(UNSET_MINIMIZE_REASON), /* unminimize_reason */ eq(UnminimizeReason.TASKBAR_TAP.reason), /* visible_task_count */ - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } - + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) verify { EventLogTags.writeWmShellDesktopModeTaskUpdate( - eq(FrameworkStatsLog - .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED), + eq( + FrameworkStatsLog + .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED + ), eq(TASK_UPDATE.instanceId), eq(TASK_UPDATE.uid), eq(TASK_UPDATE.taskHeight), eq(TASK_UPDATE.taskWidth), eq(TASK_UPDATE.taskX), eq(TASK_UPDATE.taskY), - eq(SESSION_ID), + eq(sessionId), eq(UNSET_MINIMIZE_REASON), eq(UnminimizeReason.TASKBAR_TAP.reason), - eq(TASK_COUNT)) + eq(TASK_COUNT) + ) } + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) + } + + @Test + fun logTaskResizingStarted_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskResizingStarted(TASK_SIZE_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS) - fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() = runBlocking { - desktopModeEventLogger.logTaskResizingStarted(sessionId = SESSION_ID, createTaskSizeUpdate()) + fun logTaskResizingStarted_logsTaskSizeUpdatedWithStartResizingStage() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskResizingStarted(TASK_SIZE_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), /* resize_trigger */ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER), /* resizing_stage */ @@ -352,7 +490,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* input_method */ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD), /* desktop_mode_session_id */ - eq(SESSION_ID), + eq(sessionId), /* instance_id */ eq(TASK_SIZE_UPDATE.instanceId), /* uid */ @@ -365,15 +503,27 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(TASK_SIZE_UPDATE.displayArea), ) } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + } + + @Test + fun logTaskResizingEnded_noOngoingSession_doesNotLog() { + desktopModeEventLogger.logTaskResizingEnded(TASK_SIZE_UPDATE) + + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + verifyZeroInteractions(staticMockMarker(EventLogTags::class.java)) } @Test @EnableFlags(Flags.FLAG_ENABLE_RESIZING_METRICS) - fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() = runBlocking { - desktopModeEventLogger.logTaskResizingEnded(sessionId = SESSION_ID, createTaskSizeUpdate()) + fun logTaskResizingEnded_logsTaskSizeUpdatedWithEndResizingStage() { + val sessionId = startDesktopModeSession() + + desktopModeEventLogger.logTaskResizingEnded(TASK_SIZE_UPDATE) verify { - FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), + FrameworkStatsLog.write( + eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED), /* resize_trigger */ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__RESIZE_TRIGGER__UNKNOWN_RESIZE_TRIGGER), /* resizing_stage */ @@ -381,7 +531,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { /* input_method */ eq(FrameworkStatsLog.DESKTOP_MODE_TASK_SIZE_UPDATED__INPUT_METHOD__UNKNOWN_INPUT_METHOD), /* desktop_mode_session_id */ - eq(SESSION_ID), + eq(sessionId), /* instance_id */ eq(TASK_SIZE_UPDATE.instanceId), /* uid */ @@ -394,6 +544,14 @@ class DesktopModeEventLoggerTest : ShellTestCase() { eq(TASK_SIZE_UPDATE.displayArea), ) } + verifyZeroInteractions(staticMockMarker(FrameworkStatsLog::class.java)) + } + + private fun startDesktopModeSession(): Int { + desktopModeEventLogger.logSessionEnter(EnterReason.KEYBOARD_SHORTCUT_ENTER) + clearInvocations(staticMockMarker(FrameworkStatsLog::class.java)) + clearInvocations(staticMockMarker(EventLogTags::class.java)) + return desktopModeEventLogger.currentSessionId.get() } @Test @@ -428,7 +586,7 @@ class DesktopModeEventLoggerTest : ShellTestCase() { } private companion object { - private const val SESSION_ID = 1 + private const val sessionId = 1 private const val TASK_ID = 1 private const val TASK_UID = 1 private const val TASK_X = 0 @@ -453,23 +611,12 @@ class DesktopModeEventLoggerTest : ShellTestCase() { DISPLAY_AREA, ) - private fun createTaskSizeUpdate( - resizeTrigger: ResizeTrigger = ResizeTrigger.UNKNOWN_RESIZE_TRIGGER, - inputMethod: InputMethod = InputMethod.UNKNOWN_INPUT_METHOD, - ) = TaskSizeUpdate( - resizeTrigger, - inputMethod, - TASK_ID, - TASK_UID, - TASK_HEIGHT, - TASK_WIDTH, - DISPLAY_AREA, - ) - private fun createTaskUpdate( minimizeReason: MinimizeReason? = null, unminimizeReason: UnminimizeReason? = null, - ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason, - unminimizeReason, TASK_COUNT) + ) = TaskUpdate( + TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason, + unminimizeReason, TASK_COUNT + ) } } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt index e7593b5b9324..f25faa53f356 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeLoggerTransitionObserverTest.kt @@ -59,8 +59,8 @@ import com.android.wm.shell.shared.desktopmode.DesktopModeStatus import com.android.wm.shell.sysui.ShellInit import com.android.wm.shell.transition.TransitionInfoBuilder import com.android.wm.shell.transition.Transitions -import junit.framework.Assert.assertNotNull -import junit.framework.Assert.assertNull +import kotlin.test.assertFalse +import kotlin.test.assertTrue import org.junit.Before import org.junit.Rule import org.junit.Test @@ -142,8 +142,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) - verify(desktopModeEventLogger, never()).logSessionEnter(any(), any()) - verify(desktopModeEventLogger, never()).logTaskAdded(any(), any()) + verify(desktopModeEventLogger, never()).logSessionEnter(any()) + verify(desktopModeEventLogger, never()).logTaskAdded(any()) } @Test @@ -228,11 +228,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun transitToFront_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { // previous exit to overview transition - val previousSessionId = 1 // add a freeform task val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.setLoggerSessionId(previousSessionId) + transitionObserver.isSessionActive = true val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) @@ -241,7 +240,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(previousTransitionInfo) verifyTaskRemovedAndExitLogging( - previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows @@ -259,11 +259,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun transitChange_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { // previous exit to overview transition - val previousSessionId = 1 // add a freeform task val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.setLoggerSessionId(previousSessionId) + transitionObserver.isSessionActive = true val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) @@ -272,7 +271,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(previousTransitionInfo) verifyTaskRemovedAndExitLogging( - previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows @@ -290,11 +290,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun transitOpen_previousTransitionExitToOverview_logTaskAddedAndEnterReasonOverview() { // previous exit to overview transition - val previousSessionId = 1 // add a freeform task val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.setLoggerSessionId(previousSessionId) + transitionObserver.isSessionActive = true val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) @@ -303,7 +302,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(previousTransitionInfo) verifyTaskRemovedAndExitLogging( - previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) // Enter desktop mode from cancelled recents has no transition. Enter is detected on the // next transition involving freeform windows @@ -324,11 +324,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { // Tests for AppFromOverview precedence in compared to cancelled Overview // previous exit to overview transition - val previousSessionId = 1 // add a freeform task val previousTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(previousTaskInfo) - transitionObserver.setLoggerSessionId(previousSessionId) + transitionObserver.isSessionActive = true val previousTransitionInfo = TransitionInfoBuilder(TRANSIT_TO_FRONT, TRANSIT_FLAG_IS_RECENTS) .addChange(createChange(TRANSIT_TO_BACK, previousTaskInfo)) @@ -337,7 +336,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(previousTransitionInfo) verifyTaskRemovedAndExitLogging( - previousSessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) // TRANSIT_ENTER_DESKTOP_FROM_APP_FROM_OVERVIEW val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FREEFORM)) @@ -379,11 +379,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { fun transitBack_previousExitReasonScreenOff_logTaskAddedAndEnterReasonScreenOn() { val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM) // Previous Exit reason recorded as Screen Off - val sessionId = 1 transitionObserver.addTaskInfosToCachedMap(freeformTask) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build()) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) // Enter desktop through back transition, this happens when user enters after dismissing // keyguard val change = createChange(TRANSIT_TO_FRONT, freeformTask) @@ -399,11 +398,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { fun transitEndDragToDesktop_previousExitReasonScreenOff_logTaskAddedAndEnterReasonAppDrag() { val freeformTask = createTaskInfo(WINDOWING_MODE_FREEFORM) // Previous Exit reason recorded as Screen Off - val sessionId = 1 transitionObserver.addTaskInfosToCachedMap(freeformTask) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true callOnTransitionReady(TransitionInfoBuilder(TRANSIT_SLEEP).build()) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) // Enter desktop through app handle drag. This represents cases where instead of moving to // desktop right after turning the screen on, we move to fullscreen then move another task @@ -419,24 +417,22 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { } @Test - fun transitSleep_logTaskRemovedAndExitReasonScreenOff_sessionIdNull() { - val sessionId = 1 + fun transitSleep_logTaskRemovedAndExitReasonScreenOff() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true val transitionInfo = TransitionInfoBuilder(TRANSIT_SLEEP).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.SCREEN_OFF, DEFAULT_TASK_UPDATE) } @Test - fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit_sessionIdNull() { - val sessionId = 1 + fun transitExitDesktopTaskDrag_logTaskRemovedAndExitReasonDragToExit() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // window mode changing from FREEFORM to FULLSCREEN val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) @@ -444,15 +440,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_TASK_DRAG).addChange(change).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.DRAG_TO_EXIT, DEFAULT_TASK_UPDATE) } @Test - fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton_sessionIdNull() { - val sessionId = 1 + fun transitExitDesktopAppHandleButton_logTaskRemovedAndExitReasonButton() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // window mode changing from FREEFORM to FULLSCREEN val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) @@ -462,16 +457,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { .build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging( - sessionId, ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.APP_HANDLE_MENU_BUTTON_EXIT, DEFAULT_TASK_UPDATE) } @Test - fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard_sessionIdNull() { - val sessionId = 1 + fun transitExitDesktopUsingKeyboard_logTaskRemovedAndExitReasonKeyboard() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // window mode changing from FREEFORM to FULLSCREEN val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) @@ -479,16 +472,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_KEYBOARD_SHORTCUT).addChange(change).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging( - sessionId, ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.KEYBOARD_SHORTCUT_EXIT, DEFAULT_TASK_UPDATE) } @Test - fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown_sessionIdNull() { - val sessionId = 1 + fun transitExitDesktopUnknown_logTaskRemovedAndExitReasonUnknown() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // window mode changing from FREEFORM to FULLSCREEN val change = createChange(TRANSIT_TO_FRONT, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) @@ -496,15 +487,14 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { TransitionInfoBuilder(TRANSIT_EXIT_DESKTOP_MODE_UNKNOWN).addChange(change).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.UNKNOWN_EXIT, DEFAULT_TASK_UPDATE) } @Test - fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview_sessionIdNull() { - val sessionId = 1 + fun transitToFrontWithFlagRecents_logTaskRemovedAndExitReasonOverview() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // recents transition val change = createChange(TRANSIT_TO_BACK, createTaskInfo(WINDOWING_MODE_FREEFORM)) @@ -513,31 +503,30 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) verifyTaskRemovedAndExitLogging( - sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) } @Test - fun transitClose_logTaskRemovedAndExitReasonTaskFinished_sessionIdNull() { - val sessionId = 1 + fun transitClose_logTaskRemovedAndExitReasonTaskFinished() { // add a freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // task closing val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FULLSCREEN)) val transitionInfo = TransitionInfoBuilder(TRANSIT_CLOSE).addChange(change).build() callOnTransitionReady(transitionInfo) - verifyTaskRemovedAndExitLogging(sessionId, ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE) + verifyTaskRemovedAndExitLogging(ExitReason.TASK_FINISHED, DEFAULT_TASK_UPDATE) } @Test fun sessionExitByRecents_cancelledAnimation_sessionRestored() { - val sessionId = 1 // add a freeform task to an existing session val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // recents transition sent freeform window to back val change = createChange(TRANSIT_TO_BACK, taskInfo) @@ -546,7 +535,8 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo1) verifyTaskRemovedAndExitLogging( - sessionId, ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE) + ExitReason.RETURN_HOME_OR_OVERVIEW, DEFAULT_TASK_UPDATE + ) val transitionInfo2 = TransitionInfoBuilder(TRANSIT_NONE).build() callOnTransitionReady(transitionInfo2) @@ -557,10 +547,9 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun sessionAlreadyStarted_newFreeformTaskAdded_logsTaskAdded() { - val sessionId = 1 // add an existing freeform task transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // new freeform task added val change = createChange(TRANSIT_OPEN, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) @@ -568,18 +557,16 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) verify(desktopModeEventLogger, times(1)) - .logTaskAdded(eq(sessionId), - eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2))) - verify(desktopModeEventLogger, never()).logSessionEnter(any(), any()) + .logTaskAdded(eq(DEFAULT_TASK_UPDATE.copy(instanceId = 2, visibleTaskCount = 2))) + verify(desktopModeEventLogger, never()).logSessionEnter(any()) } @Test fun sessionAlreadyStarted_taskPositionChanged_logsTaskUpdate() { - val sessionId = 1 // add an existing freeform task val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // task position changed val newTaskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) @@ -591,18 +578,17 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, times(1)) .logTaskInfoChanged( - eq(sessionId), - eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1))) + eq(DEFAULT_TASK_UPDATE.copy(taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 1)) + ) verifyZeroInteractions(desktopModeEventLogger) } @Test fun sessionAlreadyStarted_taskResized_logsTaskUpdate() { - val sessionId = 1 // add an existing freeform task val taskInfo = createTaskInfo(WINDOWING_MODE_FREEFORM) transitionObserver.addTaskInfosToCachedMap(taskInfo) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // task resized val newTaskInfo = @@ -618,23 +604,22 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, times(1)) .logTaskInfoChanged( - eq(sessionId), eq( DEFAULT_TASK_UPDATE.copy( taskWidth = DEFAULT_TASK_WIDTH + 100, taskHeight = DEFAULT_TASK_HEIGHT - 100, - visibleTaskCount = 1))) + visibleTaskCount = 1)) + ) verifyZeroInteractions(desktopModeEventLogger) } @Test fun sessionAlreadyStarted_multipleTasksUpdated_logsTaskUpdateForCorrectTask() { - val sessionId = 1 // add 2 existing freeform task val taskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM) val taskInfo2 = createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2) transitionObserver.addTaskInfosToCachedMap(taskInfo1) transitionObserver.addTaskInfosToCachedMap(taskInfo2) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // task 1 position update val newTaskInfo1 = createTaskInfo(WINDOWING_MODE_FREEFORM, taskX = DEFAULT_TASK_X + 100) @@ -646,8 +631,9 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, times(1)) .logTaskInfoChanged( - eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy( - taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2))) + eq(DEFAULT_TASK_UPDATE.copy( + taskX = DEFAULT_TASK_X + 100, visibleTaskCount = 2)) + ) verifyZeroInteractions(desktopModeEventLogger) // task 2 resize @@ -666,7 +652,6 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { verify(desktopModeEventLogger, times(1)) .logTaskInfoChanged( - eq(sessionId), eq( DEFAULT_TASK_UPDATE.copy( instanceId = 2, @@ -679,11 +664,10 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { @Test fun sessionAlreadyStarted_freeformTaskRemoved_logsTaskRemoved() { - val sessionId = 1 // add two existing freeform tasks transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM)) transitionObserver.addTaskInfosToCachedMap(createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) - transitionObserver.setLoggerSessionId(sessionId) + transitionObserver.isSessionActive = true // new freeform task closed val change = createChange(TRANSIT_CLOSE, createTaskInfo(WINDOWING_MODE_FREEFORM, id = 2)) @@ -691,9 +675,11 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { callOnTransitionReady(transitionInfo) verify(desktopModeEventLogger, times(1)) - .logTaskRemoved(eq(sessionId), eq(DEFAULT_TASK_UPDATE.copy( - instanceId = 2, visibleTaskCount = 1))) - verify(desktopModeEventLogger, never()).logSessionExit(any(), any()) + .logTaskRemoved( + eq(DEFAULT_TASK_UPDATE.copy( + instanceId = 2, visibleTaskCount = 1)) + ) + verify(desktopModeEventLogger, never()).logSessionExit(any()) } /** Simulate calling the onTransitionReady() method */ @@ -706,10 +692,9 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { } private fun verifyTaskAddedAndEnterLogging(enterReason: EnterReason, taskUpdate: TaskUpdate) { - val sessionId = transitionObserver.getLoggerSessionId() - assertNotNull(sessionId) - verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(sessionId!!), eq(enterReason)) - verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(sessionId), eq(taskUpdate)) + assertTrue(transitionObserver.isSessionActive) + verify(desktopModeEventLogger, times(1)).logSessionEnter(eq(enterReason)) + verify(desktopModeEventLogger, times(1)).logTaskAdded(eq(taskUpdate)) ExtendedMockito.verify { Trace.setCounter( eq(Trace.TRACE_TAG_WINDOW_MANAGER), @@ -725,14 +710,13 @@ class DesktopModeLoggerTransitionObserverTest : ShellTestCase() { } private fun verifyTaskRemovedAndExitLogging( - sessionId: Int, exitReason: ExitReason, taskUpdate: TaskUpdate ) { - verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(sessionId), eq(taskUpdate)) - verify(desktopModeEventLogger, times(1)).logSessionExit(eq(sessionId), eq(exitReason)) + assertFalse(transitionObserver.isSessionActive) + verify(desktopModeEventLogger, times(1)).logTaskRemoved(eq(taskUpdate)) + verify(desktopModeEventLogger, times(1)).logSessionExit(eq(exitReason)) verifyZeroInteractions(desktopModeEventLogger) - assertNull(transitionObserver.getLoggerSessionId()) } private companion object { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt index b3c10d64c3a3..f9376570dc83 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopTasksControllerTest.kt @@ -2902,7 +2902,8 @@ class DesktopTasksControllerTest : ShellTestCase() { runOpenNewWindow(task) verify(splitScreenController) .startIntent(any(), anyInt(), any(), any(), - optionsCaptor.capture(), anyOrNull()) + optionsCaptor.capture(), anyOrNull(), eq(true) + ) assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) } @@ -2917,7 +2918,7 @@ class DesktopTasksControllerTest : ShellTestCase() { verify(splitScreenController) .startIntent( any(), anyInt(), any(), any(), - optionsCaptor.capture(), anyOrNull() + optionsCaptor.capture(), anyOrNull(), eq(true) ) assertThat(ActivityOptions.fromBundle(optionsCaptor.value).launchWindowingMode) .isEqualTo(WINDOWING_MODE_MULTI_WINDOW) @@ -2984,6 +2985,58 @@ class DesktopTasksControllerTest : ShellTestCase() { .launchWindowingMode).isEqualTo(WINDOWING_MODE_FREEFORM) } + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromFreeform_minimizesIfNeeded() { + setUpLandscapeDisplay() + val homeTask = setUpHomeTask() + val freeformTasks = (1..MAX_TASK_LIMIT + 1).map { _ -> setUpFreeformTask() } + val oldestTask = freeformTasks.first() + val newestTask = freeformTasks.last() + + runOpenInstance(newestTask, freeformTasks[1].taskId) + + val wct = getLatestWct(type = TRANSIT_OPEN) + // Home is moved to front of everything. + assertThat( + wct.hierarchyOps.any { hop -> + hop.container == homeTask.token.asBinder() && hop.toTop + } + ).isTrue() + // And the oldest task isn't moved in front of home, effectively minimizing it. + assertThat( + wct.hierarchyOps.none { hop -> + hop.container == oldestTask.token.asBinder() && hop.toTop + } + ).isTrue() + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_MULTI_INSTANCE_FEATURES) + fun openInstance_fromFreeform_exitsImmersiveIfNeeded() { + setUpLandscapeDisplay() + val homeTask = setUpHomeTask() + val freeformTask = setUpFreeformTask() + val immersiveTask = setUpFreeformTask() + taskRepository.setTaskInFullImmersiveState( + displayId = immersiveTask.displayId, + taskId = immersiveTask.taskId, + immersive = true + ) + val runOnStartTransit = RunOnStartTransitionCallback() + val transition = Binder() + whenever(transitions.startTransition(eq(TRANSIT_OPEN), any(), anyOrNull())) + .thenReturn(transition) + whenever(mockDesktopFullImmersiveTransitionHandler + .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId))).thenReturn(runOnStartTransit) + + runOpenInstance(immersiveTask, freeformTask.taskId) + + verify(mockDesktopFullImmersiveTransitionHandler) + .exitImmersiveIfApplicable(any(), eq(immersiveTask.displayId)) + runOnStartTransit.assertOnlyInvocation(transition) + } + private fun runOpenInstance( callingTask: RunningTaskInfo, requestedTaskId: Int 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 9260a07fd945..ef3af8e7bdac 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 @@ -297,6 +297,28 @@ public class SplitScreenControllerTests extends ShellTestCase { } @Test + public void startIntent_forceLaunchNewTaskTrue_skipsBackgroundTasks() { + Intent startIntent = createStartIntent("startActivity"); + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); + mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, + true /* forceLaunchNewTask */); + verify(mRecentTasks, never()).findTaskInBackground(any(), anyInt(), any()); + } + + @Test + public void startIntent_forceLaunchNewTaskFalse_checksBackgroundTasks() { + Intent startIntent = createStartIntent("startActivity"); + PendingIntent pendingIntent = + PendingIntent.getActivity(mContext, 0, startIntent, FLAG_IMMUTABLE); + mSplitScreenController.startIntent(pendingIntent, mContext.getUserId(), null, + SPLIT_POSITION_TOP_OR_LEFT, null /* options */, null /* hideTaskToken */, + false /* forceLaunchNewTask */); + verify(mRecentTasks).findTaskInBackground(any(), anyInt(), any()); + } + + @Test public void testSwitchSplitPosition_checksIsSplitScreenVisible() { final String reason = "test"; when(mSplitScreenController.isSplitScreenVisible()).thenReturn(true, false); 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 cf2de91bad88..22da66dd5e67 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 @@ -18,13 +18,13 @@ package com.android.wm.shell.unfold; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY; -import static android.view.WindowManager.TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH; import static android.view.WindowManager.TRANSIT_NONE; import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -50,6 +50,7 @@ import com.android.wm.shell.unfold.animation.SplitTaskUnfoldAnimator; import org.junit.Before; import org.junit.Test; +import org.mockito.InOrder; import java.util.ArrayList; import java.util.List; @@ -140,11 +141,12 @@ public class UnfoldTransitionHandlerTest { } @Test - public void startAnimation_animationHasNotFinishedYet_doesNotFinishTheTransition() { + public void handleFoldMergeRequest_finishesTheTransition() { TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); - + // Starts the animation, the handler should wait for mShellUnfoldProgressProvider to + // notify about the end of the animation mUnfoldTransitionHandler.startAnimation( mTransition, mock(TransitionInfo.class), @@ -153,16 +155,24 @@ public class UnfoldTransitionHandlerTest { finishCallback ); - verify(finishCallback, never()).onTransitionFinished(any()); + // Send fold transition request + TransitionFinishCallback mergeFinishCallback = mock(TransitionFinishCallback.class); + mUnfoldTransitionHandler.mergeAnimation(new Binder(), createFoldTransitionInfo(), + mock(SurfaceControl.Transaction.class), mTransition, mergeFinishCallback); + + // Verify that fold transition is merged into unfold and that unfold is finished + final InOrder inOrder = inOrder(mergeFinishCallback, finishCallback); + inOrder.verify(mergeFinishCallback).onTransitionFinished(any()); + inOrder.verify(finishCallback).onTransitionFinished(any()); } @Test - public void startAnimation_sameTransitionAsHandleRequest_startsAnimation() { + public void startAnimation_animationHasNotFinishedYet_doesNotFinishTheTransition() { TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); - boolean animationStarted = mUnfoldTransitionHandler.startAnimation( + mUnfoldTransitionHandler.startAnimation( mTransition, mock(TransitionInfo.class), mock(SurfaceControl.Transaction.class), @@ -170,17 +180,18 @@ public class UnfoldTransitionHandlerTest { finishCallback ); - assertThat(animationStarted).isTrue(); + verify(finishCallback, never()).onTransitionFinished(any()); } @Test - public void startAnimation_differentTransitionFromRequestWithUnfold_startsAnimation() { - mUnfoldTransitionHandler.handleRequest(new Binder(), createNoneTransitionInfo()); + public void startAnimation_sameTransitionAsHandleRequest_startsAnimation() { + TransitionRequestInfo requestInfo = createUnfoldTransitionRequestInfo(); + mUnfoldTransitionHandler.handleRequest(mTransition, requestInfo); TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); boolean animationStarted = mUnfoldTransitionHandler.startAnimation( mTransition, - createUnfoldTransitionInfo(), + mock(TransitionInfo.class), mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), finishCallback @@ -196,7 +207,7 @@ public class UnfoldTransitionHandlerTest { boolean animationStarted = mUnfoldTransitionHandler.startAnimation( mTransition, - createDisplayResizeTransitionInfo(), + createUnfoldTransitionInfo(), mock(SurfaceControl.Transaction.class), mock(SurfaceControl.Transaction.class), finishCallback @@ -247,6 +258,7 @@ public class UnfoldTransitionHandlerTest { TransitionFinishCallback finishCallback = mock(TransitionFinishCallback.class); mShellUnfoldProgressProvider.onStateChangeStarted(); + mShellUnfoldProgressProvider.onStateChangeProgress(0.5f); mShellUnfoldProgressProvider.onStateChangeFinished(); mUnfoldTransitionHandler.startAnimation( mTransition, @@ -279,6 +291,8 @@ public class UnfoldTransitionHandlerTest { clearInvocations(finishCallback); // Fold + mShellUnfoldProgressProvider.onStateChangeProgress(/* progress= */ 0.0f); + mShellUnfoldProgressProvider.onStateChangeFinished(); mShellUnfoldProgressProvider.onFoldStateChanged(/* isFolded= */ true); // Second unfold @@ -370,6 +384,19 @@ public class UnfoldTransitionHandlerTest { triggerTaskInfo, /* remoteTransition= */ null, displayChange, 0 /* flags */); } + private TransitionInfo createFoldTransitionInfo() { + final TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0); + + final TransitionInfo.Change change = new TransitionInfo.Change(/* container= */ null, + /* leash= */ null); + change.setFlags(TransitionInfo.FLAG_IS_DISPLAY); + change.setStartAbsBounds(new Rect(0, 0, 200, 200)); + change.setEndAbsBounds(new Rect(0, 0, 100, 100)); + transitionInfo.addChange(change); + + return transitionInfo; + } + private TransitionRequestInfo createNoneTransitionInfo() { return new TransitionRequestInfo(TRANSIT_NONE, /* triggerTask= */ null, /* remoteTransition= */ null, @@ -446,17 +473,6 @@ public class UnfoldTransitionHandlerTest { change.setEndAbsBounds(new Rect(0, 0, 100, 100)); change.setFlags(TransitionInfo.FLAG_IS_DISPLAY); transitionInfo.addChange(change); - transitionInfo.setFlags(TRANSIT_FLAG_PHYSICAL_DISPLAY_SWITCH); - return transitionInfo; - } - - private TransitionInfo createDisplayResizeTransitionInfo() { - TransitionInfo transitionInfo = new TransitionInfo(TRANSIT_CHANGE, /* flags= */ 0); - TransitionInfo.Change change = new TransitionInfo.Change(null, mock(SurfaceControl.class)); - change.setStartAbsBounds(new Rect(0, 0, 10, 10)); - change.setEndAbsBounds(new Rect(0, 0, 100, 100)); - change.setFlags(TransitionInfo.FLAG_IS_DISPLAY); - transitionInfo.addChange(change); return transitionInfo; } diff --git a/libs/appfunctions/api/current.txt b/libs/appfunctions/api/current.txt index e9845c1d9f13..27817e9eb984 100644 --- a/libs/appfunctions/api/current.txt +++ b/libs/appfunctions/api/current.txt @@ -4,7 +4,6 @@ package com.google.android.appfunctions.sidecar { public final class AppFunctionManager { ctor public AppFunctionManager(android.content.Context); method public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); - method @Deprecated public void executeAppFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); method public void isAppFunctionEnabled(@NonNull String, @NonNull String, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,java.lang.Exception>); method public void setAppFunctionEnabled(@NonNull String, int, @NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Void,java.lang.Exception>); field public static final int APP_FUNCTION_STATE_DEFAULT = 0; // 0x0 @@ -15,9 +14,7 @@ package com.google.android.appfunctions.sidecar { public abstract class AppFunctionService extends android.app.Service { ctor public AppFunctionService(); method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); - method @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); - method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); - method @Deprecated @MainThread public void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); + method @MainThread public abstract void onExecuteFunction(@NonNull com.google.android.appfunctions.sidecar.ExecuteAppFunctionRequest, @NonNull String, @NonNull android.os.CancellationSignal, @NonNull java.util.function.Consumer<com.google.android.appfunctions.sidecar.ExecuteAppFunctionResponse>); field @NonNull public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE"; field @NonNull public static final String SERVICE_INTERFACE = "android.app.appfunctions.AppFunctionService"; } diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java index d660926575d1..43377d8eb91c 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionManager.java @@ -126,33 +126,6 @@ public final class AppFunctionManager { } /** - * Executes the app function. - * - * <p>Proxies request and response to the underlying {@link - * android.app.appfunctions.AppFunctionManager#executeAppFunction}, converting the request and - * response in the appropriate type required by the function. - * - * @deprecated Use {@link #executeAppFunction(ExecuteAppFunctionRequest, Executor, - * CancellationSignal, Consumer)} instead. This method will be removed once usage references - * are updated. - */ - @Deprecated - public void executeAppFunction( - @NonNull ExecuteAppFunctionRequest sidecarRequest, - @NonNull @CallbackExecutor Executor executor, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - Objects.requireNonNull(sidecarRequest); - Objects.requireNonNull(executor); - Objects.requireNonNull(callback); - - executeAppFunction( - sidecarRequest, - executor, - new CancellationSignal(), - callback); - } - - /** * Returns a boolean through a callback, indicating whether the app function is enabled. * * <p>* This method can only check app functions owned by the caller, or those where the caller diff --git a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java index 2a168e871713..0dc87e45b7e3 100644 --- a/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java +++ b/libs/appfunctions/java/com/google/android/appfunctions/sidecar/AppFunctionService.java @@ -119,76 +119,9 @@ public abstract class AppFunctionService extends Service { * @param callback A callback to report back the result. */ @MainThread - public void onExecuteFunction( + public abstract void onExecuteFunction( @NonNull ExecuteAppFunctionRequest request, @NonNull String callingPackage, @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - onExecuteFunction(request, cancellationSignal, callback); - } - - /** - * Called by the system to execute a specific app function. - * - * <p>This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - * <p>To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * - * @param request The function execution request. - * @param cancellationSignal A {@link CancellationSignal} to cancel the request. - * @param callback A callback to report back the result. - * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, String, - * CancellationSignal, Consumer)} instead. This method will be removed once usage references - * are updated. - */ - @MainThread - @Deprecated - public void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull CancellationSignal cancellationSignal, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - onExecuteFunction(request, callback); - } - - /** - * Called by the system to execute a specific app function. - * - * <p>This method is triggered when the system requests your AppFunctionService to handle a - * particular function you have registered and made available. - * - * <p>To ensure proper routing of function requests, assign a unique identifier to each - * function. This identifier doesn't need to be globally unique, but it must be unique within - * your app. For example, a function to order food could be identified as "orderFood". In most - * cases this identifier should come from the ID automatically generated by the AppFunctions - * SDK. You can determine the specific function to invoke by calling {@link - * ExecuteAppFunctionRequest#getFunctionIdentifier()}. - * - * <p>This method is always triggered in the main thread. You should run heavy tasks on a worker - * thread and dispatch the result with the given callback. You should always report back the - * result using the callback, no matter if the execution was successful or not. - * - * @param request The function execution request. - * @param callback A callback to report back the result. - * @deprecated Use {@link #onExecuteFunction(ExecuteAppFunctionRequest, CancellationSignal, - * Consumer)} instead. This method will be removed once usage references are updated. - */ - @MainThread - @Deprecated - public void onExecuteFunction( - @NonNull ExecuteAppFunctionRequest request, - @NonNull Consumer<ExecuteAppFunctionResponse> callback) { - Log.w( - "AppFunctionService", - "Calling deprecated default implementation of onExecuteFunction"); - } + @NonNull Consumer<ExecuteAppFunctionResponse> callback); } diff --git a/libs/hwui/DamageAccumulator.cpp b/libs/hwui/DamageAccumulator.cpp index 28d85bd860df..a3737131702d 100644 --- a/libs/hwui/DamageAccumulator.cpp +++ b/libs/hwui/DamageAccumulator.cpp @@ -242,45 +242,59 @@ void DamageAccumulator::applyRenderNodeTransform(DirtyStack* frame) { } } -SkRect DamageAccumulator::computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const { - const DirtyStack* frame = mHead; - Matrix4 transform; - SkRect pretransformResult = bounds; - while (true) { - SkRect currentBounds = pretransformResult; - pretransformResult.setEmpty(); - switch (frame->type) { - case TransformRenderNode: { - const RenderProperties& props = frame->renderNode->properties(); - // Perform clipping - if (props.getClipDamageToBounds() && !currentBounds.isEmpty()) { - if (!currentBounds.intersect( - SkRect::MakeIWH(props.getWidth(), props.getHeight()))) { - currentBounds.setEmpty(); - } +static void computeClipAndTransformImpl(const DirtyStack* currentFrame, SkRect* crop, + Matrix4* outMatrix) { + SkRect currentCrop = *crop; + switch (currentFrame->type) { + case TransformRenderNode: { + const RenderProperties& props = currentFrame->renderNode->properties(); + // Perform clipping + if (props.getClipDamageToBounds() && !currentCrop.isEmpty()) { + if (!currentCrop.intersect(SkRect::MakeIWH(props.getWidth(), props.getHeight()))) { + currentCrop.setEmpty(); } + } - // apply all transforms - mapRect(props, currentBounds, &pretransformResult); - frame->renderNode->applyViewPropertyTransforms(transform); - } break; - case TransformMatrix4: - mapRect(frame->matrix4, currentBounds, &pretransformResult); - transform.multiply(*frame->matrix4); - break; - default: - pretransformResult = currentBounds; - break; - } - if (frame->prev == frame) break; - frame = frame->prev; + // apply all transforms + crop->setEmpty(); + mapRect(props, currentCrop, crop); + } break; + case TransformMatrix4: + crop->setEmpty(); + mapRect(currentFrame->matrix4, currentCrop, crop); + break; + default: + break; + } + + if (currentFrame->prev != currentFrame) { + computeClipAndTransformImpl(currentFrame->prev, crop, outMatrix); + } + switch (currentFrame->type) { + case TransformRenderNode: + currentFrame->renderNode->applyViewPropertyTransforms(*outMatrix); + break; + case TransformMatrix4: + outMatrix->multiply(*currentFrame->matrix4); + break; + case TransformNone: + // nothing to be done + break; + default: + LOG_ALWAYS_FATAL("Tried to compute transform with an invalid type: %d", + currentFrame->type); } - SkRect result; +} + +SkRect DamageAccumulator::computeClipAndTransform(const SkRect& bounds, Matrix4* outMatrix) const { + SkRect cropInGlobal = bounds; + outMatrix->loadIdentity(); + computeClipAndTransformImpl(mHead, &cropInGlobal, outMatrix); + SkRect cropInLocal; Matrix4 globalToLocal; - globalToLocal.loadInverse(transform); - mapRect(&globalToLocal, pretransformResult, &result); - *outMatrix = transform; - return result; + globalToLocal.loadInverse(*outMatrix); + mapRect(&globalToLocal, cropInGlobal, &cropInLocal); + return cropInLocal; } void DamageAccumulator::dirty(float left, float top, float right, float bottom) { diff --git a/libs/hwui/hwui/Bitmap.cpp b/libs/hwui/hwui/Bitmap.cpp index 84bd45dfc012..b73380e38fd1 100644 --- a/libs/hwui/hwui/Bitmap.cpp +++ b/libs/hwui/hwui/Bitmap.cpp @@ -582,20 +582,22 @@ size_t Bitmap::mTotalBitmapBytes = 0; size_t Bitmap::mTotalBitmapCount = 0; void Bitmap::traceBitmapCreate() { + size_t bytes = getAllocationByteCount(); + std::lock_guard lock{mLock}; + mTotalBitmapBytes += bytes; + mTotalBitmapCount++; if (ATRACE_ENABLED()) { - std::lock_guard lock{mLock}; - mTotalBitmapBytes += getAllocationByteCount(); - mTotalBitmapCount++; ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes); ATRACE_INT64("Bitmap Count", mTotalBitmapCount); } } void Bitmap::traceBitmapDelete() { + size_t bytes = getAllocationByteCount(); + std::lock_guard lock{mLock}; + mTotalBitmapBytes -= getAllocationByteCount(); + mTotalBitmapCount--; if (ATRACE_ENABLED()) { - std::lock_guard lock{mLock}; - mTotalBitmapBytes -= getAllocationByteCount(); - mTotalBitmapCount--; ATRACE_INT64("Bitmap Memory", mTotalBitmapBytes); ATRACE_INT64("Bitmap Count", mTotalBitmapCount); } diff --git a/libs/hwui/jni/text/TextShaper.cpp b/libs/hwui/jni/text/TextShaper.cpp index 456338631ae4..70e6beda6cb9 100644 --- a/libs/hwui/jni/text/TextShaper.cpp +++ b/libs/hwui/jni/text/TextShaper.cpp @@ -31,12 +31,34 @@ namespace android { +struct FakedFontKey { + uint32_t operator()(const minikin::FakedFont& fakedFont) const { + return minikin::Hasher() + .update(reinterpret_cast<uintptr_t>(fakedFont.font.get())) + .update(fakedFont.fakery.bits()) + .update(fakedFont.fakery.variationSettings()) + .hash(); + } +}; + struct LayoutWrapper { LayoutWrapper(minikin::Layout&& layout, float ascent, float descent) : layout(std::move(layout)), ascent(ascent), descent(descent) {} + + LayoutWrapper(minikin::Layout&& layout, float ascent, float descent, std::vector<jlong>&& fonts, + std::vector<uint32_t>&& fontIds) + : layout(std::move(layout)) + , ascent(ascent) + , descent(descent) + , fonts(std::move(fonts)) + , fontIds(std::move(fontIds)) {} + minikin::Layout layout; float ascent; float descent; + + std::vector<jlong> fonts; + std::vector<uint32_t> fontIds; // per glyph }; static void releaseLayout(jlong ptr) { @@ -64,6 +86,43 @@ static jlong shapeTextRun(const uint16_t* text, int textSize, int start, int cou overallDescent = std::max(overallDescent, extent.descent); } + if (text_feature::typeface_redesign()) { + uint32_t runCount = layout.getFontRunCount(); + + std::unordered_map<minikin::FakedFont, uint32_t, FakedFontKey> fakedToFontIds; + std::vector<jlong> fonts; + std::vector<uint32_t> fontIds; + + fontIds.resize(layout.nGlyphs()); + for (uint32_t ri = 0; ri < runCount; ++ri) { + const minikin::FakedFont& fakedFont = layout.getFontRunFont(ri); + + auto it = fakedToFontIds.find(fakedFont); + uint32_t fontId; + if (it != fakedToFontIds.end()) { + fontId = it->second; // We've seen it. + } else { + fontId = fonts.size(); // This is new to us. Create new one. + std::shared_ptr<minikin::Font> font = std::make_shared<minikin::Font>( + fakedFont.font, fakedFont.fakery.variationSettings()); + fonts.push_back(reinterpret_cast<jlong>(new FontWrapper(std::move(font)))); + fakedToFontIds.insert(std::make_pair(fakedFont, fontId)); + } + + const uint32_t runStart = layout.getFontRunStart(ri); + const uint32_t runEnd = layout.getFontRunEnd(ri); + for (uint32_t i = runStart; i < runEnd; ++i) { + fontIds[i] = fontId; + } + } + + std::unique_ptr<LayoutWrapper> ptr = + std::make_unique<LayoutWrapper>(std::move(layout), overallAscent, overallDescent, + std::move(fonts), std::move(fontIds)); + + return reinterpret_cast<jlong>(ptr.release()); + } + std::unique_ptr<LayoutWrapper> ptr = std::make_unique<LayoutWrapper>( std::move(layout), overallAscent, overallDescent ); @@ -156,6 +215,8 @@ static jboolean TextShaper_Result_getFakeItalic(CRITICAL_JNI_PARAMS_COMMA jlong return layout->layout.getFakery(i).isFakeItalic(); } +constexpr float NO_OVERRIDE = -1; + float findValueFromVariationSettings(const minikin::FontFakery& fakery, minikin::AxisTag tag) { for (const minikin::FontVariation& fv : fakery.variationSettings()) { if (fv.axisTag == tag) { @@ -171,12 +232,7 @@ static jfloat TextShaper_Result_getWeightOverride(CRITICAL_JNI_PARAMS_COMMA jlon if (text_feature::typeface_redesign()) { float value = findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_wght); - if (!std::isnan(value)) { - return value; - } else { - const std::shared_ptr<minikin::Font>& font = layout->layout.getFontRef(i); - return font->style().weight(); - } + return std::isnan(value) ? NO_OVERRIDE : value; } else { return layout->layout.getFakery(i).wghtAdjustment(); } @@ -188,12 +244,7 @@ static jfloat TextShaper_Result_getItalicOverride(CRITICAL_JNI_PARAMS_COMMA jlon if (text_feature::typeface_redesign()) { float value = findValueFromVariationSettings(layout->layout.getFakery(i), minikin::TAG_ital); - if (!std::isnan(value)) { - return value; - } else { - const std::shared_ptr<minikin::Font>& font = layout->layout.getFontRef(i); - return font->style().isItalic(); - } + return std::isnan(value) ? NO_OVERRIDE : value; } else { return layout->layout.getFakery(i).italAdjustment(); } @@ -207,6 +258,24 @@ static jlong TextShaper_Result_getFont(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint } // CriticalNative +static jint TextShaper_Result_getFontCount(CRITICAL_JNI_PARAMS_COMMA jlong ptr) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->fonts.size(); +} + +// CriticalNative +static jlong TextShaper_Result_getFontRef(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint fontId) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->fonts[fontId]; +} + +// CriticalNative +static jint TextShaper_Result_getFontId(CRITICAL_JNI_PARAMS_COMMA jlong ptr, jint glyphIdx) { + const LayoutWrapper* layout = reinterpret_cast<LayoutWrapper*>(ptr); + return layout->fontIds[glyphIdx]; +} + +// CriticalNative static jlong TextShaper_Result_nReleaseFunc(CRITICAL_JNI_PARAMS) { return reinterpret_cast<jlong>(releaseLayout); } @@ -250,6 +319,10 @@ static const JNINativeMethod gResultMethods[] = { {"nGetWeightOverride", "(JI)F", (void*)TextShaper_Result_getWeightOverride}, {"nGetItalicOverride", "(JI)F", (void*)TextShaper_Result_getItalicOverride}, {"nReleaseFunc", "()J", (void*)TextShaper_Result_nReleaseFunc}, + + {"nGetFontCount", "(J)I", (void*)TextShaper_Result_getFontCount}, + {"nGetFontRef", "(JI)J", (void*)TextShaper_Result_getFontRef}, + {"nGetFontId", "(JI)I", (void*)TextShaper_Result_getFontId}, }; int register_android_graphics_text_TextShaper(JNIEnv* env) { diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl index eeb4853afadc..961962f6a010 100644 --- a/media/java/android/media/IMediaRouterService.aidl +++ b/media/java/android/media/IMediaRouterService.aidl @@ -85,8 +85,7 @@ interface IMediaRouterService { void updateScanningState(IMediaRouter2Manager manager, @JavaPassthrough(annotation="@android.media.MediaRouter2.ScanningState") int scanningState); void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId, - in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route, - in UserHandle transferInitiatorUserHandle, in String transferInitiatorPackageName); + in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route); void selectRouteWithManager(IMediaRouter2Manager manager, int requestId, String sessionId, in MediaRoute2Info route); void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId, diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java index b84990b54bd5..3499c438086d 100644 --- a/media/java/android/media/MediaRouter2.java +++ b/media/java/android/media/MediaRouter2.java @@ -2770,7 +2770,7 @@ public final class MediaRouter2 { || isSystemRouteReselection) { transferToRoute(sessionInfo, route, mClientUser, mClientPackageName); } else { - requestCreateSession(sessionInfo, route, mClientUser, mClientPackageName); + requestCreateSession(sessionInfo, route); } } @@ -2826,10 +2826,7 @@ public final class MediaRouter2 { * @param route The {@link MediaRoute2Info route} to transfer to. */ private void requestCreateSession( - @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) { if (TextUtils.isEmpty(oldSession.getClientPackageName())) { Log.w(TAG, "requestCreateSession: Can't create a session without package name."); this.onTransferFailed(oldSession, route); @@ -2840,12 +2837,7 @@ public final class MediaRouter2 { try { mMediaRouterService.requestCreateSessionWithManager( - mClient, - requestId, - oldSession, - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + mClient, requestId, oldSession, route); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java index 8fa0e49e8b96..7e1dccf2d366 100644 --- a/media/java/android/media/MediaRouter2Manager.java +++ b/media/java/android/media/MediaRouter2Manager.java @@ -524,8 +524,7 @@ public final class MediaRouter2Manager { transferToRoute( sessionInfo, route, transferInitiatorUserHandle, transferInitiatorPackageName); } else { - requestCreateSession(sessionInfo, route, transferInitiatorUserHandle, - transferInitiatorPackageName); + requestCreateSession(sessionInfo, route); } } @@ -914,9 +913,7 @@ public final class MediaRouter2Manager { } } - private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiationPackageName) { + private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route) { if (TextUtils.isEmpty(oldSession.getClientPackageName())) { Log.w(TAG, "requestCreateSession: Can't create a session without package name."); notifyTransferFailed(oldSession, route); @@ -927,8 +924,7 @@ public final class MediaRouter2Manager { try { mMediaRouterService.requestCreateSessionWithManager( - mClient, requestId, oldSession, route, transferInitiatorUserHandle, - transferInitiationPackageName); + mClient, requestId, oldSession, route); } catch (RemoteException ex) { throw ex.rethrowFromSystemServer(); } diff --git a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java index b2861826a103..601e001f48c2 100644 --- a/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java +++ b/packages/SettingsLib/ActionButtonsPreference/src/com/android/settingslib/widget/ActionButtonsPreference.java @@ -57,7 +57,7 @@ import java.util.List; * 1. User sets invisible for button. ex: ActionButtonPreference.setButton1Visible(false) * 2. User doesn't set any title or icon for button. */ -public class ActionButtonsPreference extends Preference { +public class ActionButtonsPreference extends Preference implements GroupSectionDividerMixin { private static final String TAG = "ActionButtonPreference"; private static final boolean mIsAtLeastS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S; diff --git a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java index 6cd777e878fe..10769ecfbe42 100644 --- a/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java +++ b/packages/SettingsLib/BannerMessagePreference/src/com/android/settingslib/widget/BannerMessagePreference.java @@ -43,7 +43,7 @@ import com.android.settingslib.widget.preference.banner.R; * Banner message is a banner displaying important information (permission request, page error etc), * and provide actions for user to address. It requires a user action to be dismissed. */ -public class BannerMessagePreference extends Preference { +public class BannerMessagePreference extends Preference implements GroupSectionDividerMixin { public enum AttentionLevel { HIGH(0, R.color.banner_background_attention_high, R.color.banner_accent_attention_high), diff --git a/packages/SettingsLib/CardPreference/src/com/android/settingslib/widget/CardPreference.kt b/packages/SettingsLib/CardPreference/src/com/android/settingslib/widget/CardPreference.kt index eb14746a0f22..84ff1bbef65d 100644 --- a/packages/SettingsLib/CardPreference/src/com/android/settingslib/widget/CardPreference.kt +++ b/packages/SettingsLib/CardPreference/src/com/android/settingslib/widget/CardPreference.kt @@ -30,7 +30,7 @@ class CardPreference @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : Preference(context, attrs, defStyleAttr, defStyleRes) { +) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin { init { layoutResource = R.layout.settingslib_expressive_preference_card diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml index 33519cba2940..dd7eac776583 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/layout-v31/non_collapsing_toolbar_content_layout.xml @@ -26,12 +26,12 @@ android:outlineAmbientShadowColor="@android:color/transparent" android:outlineSpotShadowColor="@android:color/transparent" android:background="@android:color/transparent" - android:theme="@style/Theme.CollapsingToolbar.Settings"> + android:theme="@style/ThemeOverlay.MaterialComponents.PlatformBridge.CollapsingToolbar"> <Toolbar android:id="@+id/action_bar" android:layout_width="match_parent" - android:layout_height="?attr/actionBarSize" + android:layout_height="?android:attr/actionBarSize" android:theme="?android:attr/actionBarTheme" android:transitionName="shared_element_view" app:layout_collapseMode="pin"/> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml index c20beaf9bf93..02f171cf0d9e 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-night-v31/themes.xml @@ -21,4 +21,15 @@ <item name="colorPrimary">@color/settingslib_primary_dark_device_default_settings</item> <item name="colorAccent">@color/settingslib_accent_device_default_dark</item> </style> -</resources>
\ No newline at end of file + + <!-- + ~ TODO(b/349675008): Remove this theme overlay once the platform bridge theme properly sets + ~ the MaterialComponents colors based on the platform theme. + --> + <style name="ThemeOverlay.MaterialComponents.PlatformBridge.CollapsingToolbar"> + <item name="elevationOverlayEnabled">true</item> + <item name="elevationOverlayColor">?attr/colorPrimary</item> + <item name="colorPrimary">@color/settingslib_primary_dark_device_default_settings</item> + <item name="colorAccent">@color/settingslib_accent_device_default_dark</item> + </style> +</resources> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml index 9ecc297c6d36..403931764d7e 100644 --- a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes.xml @@ -21,4 +21,15 @@ <item name="colorPrimary">@color/settingslib_primary_device_default_settings_light</item> <item name="colorAccent">@color/settingslib_accent_device_default_light</item> </style> -</resources>
\ No newline at end of file + + <!-- + ~ TODO(b/349675008): Remove this theme overlay once the platform bridge theme properly sets + ~ the MaterialComponents colors based on the platform theme. + --> + <style name="ThemeOverlay.MaterialComponents.PlatformBridge.CollapsingToolbar"> + <item name="elevationOverlayEnabled">true</item> + <item name="elevationOverlayColor">?attr/colorPrimary</item> + <item name="colorPrimary">@color/settingslib_primary_device_default_settings_light</item> + <item name="colorAccent">@color/settingslib_accent_device_default_light</item> + </style> +</resources> diff --git a/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml new file mode 100644 index 000000000000..bcb9baf94706 --- /dev/null +++ b/packages/SettingsLib/CollapsingToolbarBaseActivity/res/values-v31/themes_bridge.xml @@ -0,0 +1,176 @@ +<?xml version="1.0" encoding="utf-8"?> + +<!-- + Copyright (C) 2024 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. +--> + +<!-- See appcompat/appcompat/THEMES for the theme structure. --> +<resources> + <!-- + ~ Bridge theme overlay to simulate AppCompat themes based on a platform theme. + ~ Only non-widget attributes are included here since we should still use the platform widgets. + ~ Only public theme attributes (as in platform public-final.xml) can be referenced here since + ~ this is used in modules. + --> + <style name="Base.V31.ThemeOverlay.AppCompat.PlatformBridge" parent=""> + <!-- START Base.V7.Theme.AppCompat --> + + <item name="colorBackgroundFloating">?android:colorBackgroundFloating</item> + + <item name="isLightTheme">?android:isLightTheme</item> + + <item name="selectableItemBackground">?android:selectableItemBackground</item> + <item name="selectableItemBackgroundBorderless">?android:selectableItemBackgroundBorderless</item> + <item name="homeAsUpIndicator">?android:homeAsUpIndicator</item> + + <item name="dividerVertical">?android:dividerVertical</item> + <item name="dividerHorizontal">?android:dividerHorizontal</item> + + <!-- List attributes --> + <item name="textAppearanceListItem">?android:textAppearanceListItem</item> + <item name="textAppearanceListItemSmall">?android:textAppearanceListItemSmall</item> + <item name="textAppearanceListItemSecondary">?android:textAppearanceListItemSecondary</item> + <item name="listPreferredItemHeight">?android:listPreferredItemHeight</item> + <item name="listPreferredItemHeightSmall">?android:listPreferredItemHeightSmall</item> + <item name="listPreferredItemHeightLarge">?android:listPreferredItemHeightLarge</item> + <item name="listPreferredItemPaddingLeft">?android:listPreferredItemPaddingLeft</item> + <item name="listPreferredItemPaddingRight">?android:listPreferredItemPaddingRight</item> + <item name="listPreferredItemPaddingStart">?android:listPreferredItemPaddingStart</item> + <item name="listPreferredItemPaddingEnd">?android:listPreferredItemPaddingEnd</item> + + <!-- Color palette --> + <item name="colorPrimaryDark">?android:colorPrimaryDark</item> + <item name="colorPrimary">?android:colorPrimary</item> + <item name="colorAccent">?android:colorAccent</item> + + <item name="colorControlNormal">?android:colorControlNormal</item> + <item name="colorControlActivated">?android:colorControlActivated</item> + <item name="colorControlHighlight">?android:colorControlHighlight</item> + <item name="colorButtonNormal">?android:colorButtonNormal</item> + + <item name="colorError">?android:colorError</item> + + <!-- END Base.V7.Theme.AppCompat --> + </style> + <style name="Base.ThemeOverlay.AppCompat.PlatformBridge" parent="Base.V31.ThemeOverlay.AppCompat.PlatformBridge" /> + <style name="ThemeOverlay.AppCompat.PlatformBridge" parent="Base.ThemeOverlay.AppCompat.PlatformBridge" /> + + <!-- + ~ Bridge theme overlay to simulate MaterialComponents themes based on a platform theme. + --> + <style name="Base.V31.ThemeOverlay.MaterialComponents.PlatformBridge" parent="ThemeOverlay.AppCompat.PlatformBridge"> + <!-- START Base.V14.Theme.MaterialComponents.Bridge --> + <!-- + ~ This is copied as-is from the original bridge theme since it is guaranteed to not affect + ~ existing widgets. + --> + + <item name="isMaterialTheme">true</item> + + <item name="colorPrimaryVariant">@color/design_dark_default_color_primary_variant</item> + <item name="colorSecondary">@color/design_dark_default_color_secondary</item> + <item name="colorSecondaryVariant">@color/design_dark_default_color_secondary_variant</item> + <item name="colorSurface">@color/design_dark_default_color_surface</item> + <item name="colorPrimarySurface">?attr/colorSurface</item> + <item name="colorOnPrimary">@color/design_dark_default_color_on_primary</item> + <item name="colorOnSecondary">@color/design_dark_default_color_on_secondary</item> + <item name="colorOnBackground">@color/design_dark_default_color_on_background</item> + <item name="colorOnError">@color/design_dark_default_color_on_error</item> + <item name="colorOnSurface">@color/design_dark_default_color_on_surface</item> + <item name="colorOnPrimarySurface">?attr/colorOnSurface</item> + + <item name="scrimBackground">@color/mtrl_scrim_color</item> + <item name="popupMenuBackground">@drawable/mtrl_popupmenu_background_overlay</item> + + <item name="minTouchTargetSize">@dimen/mtrl_min_touch_target_size</item> + + <!-- MaterialComponents Widget styles --> + <item name="badgeStyle">@style/Widget.MaterialComponents.Badge</item> + <item name="bottomAppBarStyle">@style/Widget.MaterialComponents.BottomAppBar</item> + <item name="chipStyle">@style/Widget.MaterialComponents.Chip.Action</item> + <item name="chipGroupStyle">@style/Widget.MaterialComponents.ChipGroup</item> + <item name="chipStandaloneStyle">@style/Widget.MaterialComponents.Chip.Entry</item> + <item name="circularProgressIndicatorStyle">@style/Widget.MaterialComponents.CircularProgressIndicator</item> + <item name="extendedFloatingActionButtonStyle">@style/Widget.MaterialComponents.ExtendedFloatingActionButton.Icon</item> + <item name="linearProgressIndicatorStyle">@style/Widget.MaterialComponents.LinearProgressIndicator</item> + <item name="materialButtonStyle">@style/Widget.MaterialComponents.Button</item> + <item name="materialButtonOutlinedStyle">@style/Widget.MaterialComponents.Button.OutlinedButton</item> + <item name="materialButtonToggleGroupStyle">@style/Widget.MaterialComponents.MaterialButtonToggleGroup</item> + <item name="materialCardViewStyle">@style/Widget.MaterialComponents.CardView</item> + <item name="navigationRailStyle">@style/Widget.MaterialComponents.NavigationRailView</item> + <item name="sliderStyle">@style/Widget.MaterialComponents.Slider</item> + + <!-- Type styles --> + <item name="textAppearanceHeadline1">@style/TextAppearance.MaterialComponents.Headline1</item> + <item name="textAppearanceHeadline2">@style/TextAppearance.MaterialComponents.Headline2</item> + <item name="textAppearanceHeadline3">@style/TextAppearance.MaterialComponents.Headline3</item> + <item name="textAppearanceHeadline4">@style/TextAppearance.MaterialComponents.Headline4</item> + <item name="textAppearanceHeadline5">@style/TextAppearance.MaterialComponents.Headline5</item> + <item name="textAppearanceHeadline6">@style/TextAppearance.MaterialComponents.Headline6</item> + <item name="textAppearanceSubtitle1">@style/TextAppearance.MaterialComponents.Subtitle1</item> + <item name="textAppearanceSubtitle2">@style/TextAppearance.MaterialComponents.Subtitle2</item> + <item name="textAppearanceBody1">@style/TextAppearance.MaterialComponents.Body1</item> + <item name="textAppearanceBody2">@style/TextAppearance.MaterialComponents.Body2</item> + <item name="textAppearanceCaption">@style/TextAppearance.MaterialComponents.Caption</item> + <item name="textAppearanceButton">@style/TextAppearance.MaterialComponents.Button</item> + <item name="textAppearanceOverline">@style/TextAppearance.MaterialComponents.Overline</item> + + <!-- Shape styles --> + <item name="shapeAppearanceSmallComponent"> + @style/ShapeAppearance.MaterialComponents.SmallComponent + </item> + <item name="shapeAppearanceMediumComponent"> + @style/ShapeAppearance.MaterialComponents.MediumComponent + </item> + <item name="shapeAppearanceLargeComponent"> + @style/ShapeAppearance.MaterialComponents.LargeComponent + </item> + + <!-- Motion --> + <item name="motionEasingStandard">@string/material_motion_easing_standard</item> + <item name="motionEasingEmphasized">@string/material_motion_easing_emphasized</item> + <item name="motionEasingDecelerated">@string/material_motion_easing_decelerated</item> + <item name="motionEasingAccelerated">@string/material_motion_easing_accelerated</item> + <item name="motionEasingLinear">@string/material_motion_easing_linear</item> + + <item name="motionDurationShort1">@integer/material_motion_duration_short_1</item> + <item name="motionDurationShort2">@integer/material_motion_duration_short_2</item> + <item name="motionDurationMedium1">@integer/material_motion_duration_medium_1</item> + <item name="motionDurationMedium2">@integer/material_motion_duration_medium_2</item> + <item name="motionDurationLong1">@integer/material_motion_duration_long_1</item> + <item name="motionDurationLong2">@integer/material_motion_duration_long_2</item> + + <item name="motionPath">@integer/material_motion_path</item> + + <!-- Elevation Overlays --> + <item name="elevationOverlayEnabled">true</item> + <item name="elevationOverlayColor">?attr/colorOnSurface</item> + + <!-- END Base.V14.Theme.MaterialComponents.Bridge --> + + <!-- START Base.V14.Theme.MaterialComponents --> + <!-- + ~ Only a subset of widget attributes being actually used are included here since there are + ~ too many of them and they need to be investigated on a case-by-case basis. + --> + + <!-- Framework, AppCompat, or Design Widget styles --> + <item name="appBarLayoutStyle">@style/Widget.MaterialComponents.AppBarLayout.Surface</item> + + <!-- END Base.V14.Theme.MaterialComponents --> + </style> + <style name="Base.ThemeOverlay.MaterialComponents.PlatformBridge" parent="Base.V31.ThemeOverlay.AppCompat.PlatformBridge" /> + <style name="ThemeOverlay.MaterialComponents.PlatformBridge" parent="Base.ThemeOverlay.AppCompat.PlatformBridge" /> +</resources> diff --git a/packages/SettingsLib/IllustrationPreference/Android.bp b/packages/SettingsLib/IllustrationPreference/Android.bp index cd8f584953aa..12890feaf56e 100644 --- a/packages/SettingsLib/IllustrationPreference/Android.bp +++ b/packages/SettingsLib/IllustrationPreference/Android.bp @@ -21,6 +21,7 @@ android_library { "SettingsLibColor", "androidx.preference_preference", "lottie", + "SettingsLibSettingsTheme", "settingslib_illustrationpreference_flags_lib", ], diff --git a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java index a0599bb32dd1..adc4f316aca9 100644 --- a/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java +++ b/packages/SettingsLib/IllustrationPreference/src/com/android/settingslib/widget/IllustrationPreference.java @@ -55,7 +55,7 @@ import java.io.InputStream; /** * IllustrationPreference is a preference that can play lottie format animation */ -public class IllustrationPreference extends Preference { +public class IllustrationPreference extends Preference implements GroupSectionDividerMixin { private static final String TAG = "IllustrationPreference"; diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml index e3f8fbb88a65..2e3ee32e2efb 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v31/settingslib_main_switch_bar.xml @@ -20,8 +20,6 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?android:attr/listPreferredItemHeight" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingTop="@dimen/settingslib_switchbar_margin" android:paddingBottom="@dimen/settingslib_switchbar_margin" android:orientation="vertical"> diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml index 255b2c92e709..3e0e18488f36 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v33/settingslib_main_switch_bar.xml @@ -20,8 +20,6 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?android:attr/listPreferredItemHeight" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" android:paddingTop="@dimen/settingslib_switchbar_margin" android:paddingBottom="@dimen/settingslib_switchbar_margin" android:orientation="vertical"> diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml index 4425ef08d6e3..f75d9b23c49b 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout-v35/settingslib_expressive_main_switch_bar.xml @@ -20,8 +20,6 @@ android:layout_height="wrap_content" android:layout_width="match_parent" android:minHeight="?android:attr/listPreferredItemHeight" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:paddingVertical="@dimen/settingslib_expressive_space_small1" android:orientation="vertical"> diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml index bf34db93298b..7c0eaeaca3de 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_bar.xml @@ -18,11 +18,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" - android:layout_width="match_parent" - android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" - android:paddingStart="?android:attr/listPreferredItemPaddingStart" - android:paddingRight="?android:attr/listPreferredItemPaddingRight" - android:paddingEnd="?android:attr/listPreferredItemPaddingEnd"> + android:layout_width="match_parent"> <TextView android:id="@+id/switch_text" diff --git a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml index bef6e352d854..fa908a4ed6c8 100644 --- a/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml +++ b/packages/SettingsLib/MainSwitchPreference/res/layout/settingslib_main_switch_layout.xml @@ -18,6 +18,10 @@ <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="wrap_content" android:layout_width="match_parent" + android:paddingLeft="?android:attr/listPreferredItemPaddingLeft" + android:paddingStart="?android:attr/listPreferredItemPaddingStart" + android:paddingRight="?android:attr/listPreferredItemPaddingRight" + android:paddingEnd="?android:attr/listPreferredItemPaddingEnd" android:importantForAccessibility="no"> <com.android.settingslib.widget.MainSwitchBar diff --git a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java index d895c874d95e..3394874797e3 100644 --- a/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java +++ b/packages/SettingsLib/MainSwitchPreference/src/com/android/settingslib/widget/MainSwitchPreference.java @@ -36,7 +36,8 @@ import java.util.List; * This component is used as the main switch of the page * to enable or disable the prefereces on the page. */ -public class MainSwitchPreference extends TwoStatePreference implements OnCheckedChangeListener { +public class MainSwitchPreference extends TwoStatePreference + implements OnCheckedChangeListener, GroupSectionDividerMixin { private final List<OnCheckedChangeListener> mSwitchChangeListeners = new ArrayList<>(); diff --git a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt index e27838c4ef4d..89881f4d74bb 100644 --- a/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt +++ b/packages/SettingsLib/Preference/testutils/com/android/settingslib/preference/CatalystScreenTestCase.kt @@ -27,6 +27,7 @@ import androidx.preference.PreferenceScreen import androidx.test.core.app.ApplicationProvider import androidx.test.ext.junit.runners.AndroidJUnit4 import com.google.common.truth.Truth.assertThat +import java.util.concurrent.atomic.AtomicBoolean import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith @@ -36,7 +37,7 @@ import org.junit.runner.RunWith abstract class CatalystScreenTestCase { @get:Rule val setFlagsRule = SetFlagsRule() - protected val context: Context = ApplicationProvider.getApplicationContext() + protected val appContext: Context = ApplicationProvider.getApplicationContext() /** Catalyst screen. */ protected abstract val preferenceScreenCreator: PreferenceScreenCreator @@ -51,12 +52,12 @@ abstract class CatalystScreenTestCase { @Test open fun migration() { enableCatalystScreen() - assertThat(preferenceScreenCreator.isFlagEnabled(context)).isTrue() + assertThat(preferenceScreenCreator.isFlagEnabled(appContext)).isTrue() val catalystScreen = dumpPreferenceScreen() - Log.i("Catalyst", catalystScreen) + Log.i(TAG, catalystScreen) disableCatalystScreen() - assertThat(preferenceScreenCreator.isFlagEnabled(context)).isFalse() + assertThat(preferenceScreenCreator.isFlagEnabled(appContext)).isFalse() val legacyScreen = dumpPreferenceScreen() assertThat(catalystScreen).isEqualTo(legacyScreen) @@ -83,11 +84,26 @@ abstract class CatalystScreenTestCase { } private fun dumpPreferenceScreen(): String { + // Dump threads for troubleshooting when the test thread is stuck. + // Latest junit Timeout rule supports similar feature but it is not yet available on AOSP. + val taskFinished = AtomicBoolean() + Thread { + Thread.sleep(20000) + if (!taskFinished.get()) dumpThreads() + } + .apply { + isDaemon = true + start() + } + @Suppress("UNCHECKED_CAST") val clazz = preferenceScreenCreator.fragmentClass() as Class<PreferenceFragmentCompat> val builder = StringBuilder() FragmentScenario.launch(clazz).use { - it.onFragment { fragment -> fragment.preferenceScreen.toString(builder) } + it.onFragment { fragment -> + taskFinished.set(true) + fragment.preferenceScreen.toString(builder) + } } return builder.toString() } @@ -120,4 +136,16 @@ abstract class CatalystScreenTestCase { } builder.append(indent).append("}\n") } + + companion object { + const val TAG = "CatalystScreenTestCase" + + fun dumpThreads() { + for ((thread, stack) in Thread.getAllStackTraces()) { + Log.i(TAG, "$thread") + for (frame in stack) Log.i(TAG, " $frame") + Log.i(TAG, "") + } + } + } } diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/GroupSectionDividerMixin.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/GroupSectionDividerMixin.kt new file mode 100644 index 000000000000..ba5f5cf855be --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/GroupSectionDividerMixin.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2024 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.widget + +/** + * A base interface to indicate that a class should not have rounded corners. + * + * Classes implementing this interface will be treated as already handle rounded corners. + */ +interface GroupSectionDividerMixin
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt new file mode 100644 index 000000000000..535d80f609fb --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsBasePreferenceFragment.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 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.widget + +import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceScreen +import androidx.recyclerview.widget.RecyclerView + +/** Base class for Settings to use PreferenceFragmentCompat */ +open abstract class SettingsBasePreferenceFragment : PreferenceFragmentCompat() { + + override fun onCreateAdapter(preferenceScreen: PreferenceScreen): RecyclerView.Adapter<*> { + if (SettingsThemeHelper.isExpressiveTheme(requireContext())) + return SettingsPreferenceGroupAdapter(preferenceScreen) + return super.onCreateAdapter(preferenceScreen) + } +}
\ No newline at end of file diff --git a/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt new file mode 100644 index 000000000000..98b7f7664b1b --- /dev/null +++ b/packages/SettingsLib/SettingsTheme/src/com/android/settingslib/widget/SettingsPreferenceGroupAdapter.kt @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2024 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.widget + +import android.os.Handler +import android.os.Looper +import androidx.annotation.DrawableRes +import androidx.preference.Preference +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceGroup +import androidx.preference.PreferenceGroupAdapter +import androidx.preference.PreferenceViewHolder +import com.android.settingslib.widget.theme.R + +/** + * A custom adapter for displaying settings preferences in a list, handling rounded corners + * for preference items within a group. + */ +open class SettingsPreferenceGroupAdapter @JvmOverloads constructor( + preferenceGroup: PreferenceGroup +) : PreferenceGroupAdapter(preferenceGroup) { + + private val mPreferenceGroup = preferenceGroup + private var mRoundCornerMappingList: ArrayList<Int> = ArrayList() + + private var mNormalPaddingStart = 0 + private var mGroupPaddingStart = 0 + private var mNormalPaddingEnd = 0 + private var mGroupPaddingEnd = 0 + + private val mHandler = Handler(Looper.getMainLooper()) + + private val syncRunnable = Runnable { updatePreferences() } + + init { + val context = preferenceGroup.context + mNormalPaddingStart = + context.resources.getDimensionPixelSize(R.dimen.settingslib_expressive_space_small1) + mGroupPaddingStart = mNormalPaddingStart * 2 + mNormalPaddingEnd = + context.resources.getDimensionPixelSize(R.dimen.settingslib_expressive_space_small1) + mGroupPaddingEnd = mNormalPaddingEnd * 2 + updatePreferences() + } + + override fun onPreferenceHierarchyChange(preference: Preference) { + super.onPreferenceHierarchyChange(preference) + + // Post after super class has posted their sync runnable to update preferences. + mHandler.removeCallbacks(syncRunnable) + mHandler.post(syncRunnable) + } + + override fun onBindViewHolder(holder: PreferenceViewHolder, position: Int) { + super.onBindViewHolder(holder, position) + updateBackground(holder, position) + } + + private fun updatePreferences() { + val oldList = ArrayList(mRoundCornerMappingList) + mRoundCornerMappingList = ArrayList() + mappingPreferenceGroup(mRoundCornerMappingList, mPreferenceGroup) + if (mRoundCornerMappingList != oldList) { + notifyDataSetChanged() + } + } + + private fun mappingPreferenceGroup(cornerStyles: MutableList<Int>, group: PreferenceGroup) { + cornerStyles.clear() + cornerStyles.addAll(MutableList(itemCount) { 0 }) + + // the first item in to group + var startIndex = -1 + // the last item in the group + var endIndex = -1 + var currentParent: PreferenceGroup? = group + for (i in 0 until itemCount) { + when (val pref = getItem(i)) { + // the preference has round corner background, so we don't need to handle it. + is GroupSectionDividerMixin -> { + cornerStyles[i] = 0 + startIndex = -1 + endIndex = -1 + } + + // PreferenceCategory should not have round corner background. + is PreferenceCategory -> { + cornerStyles[i] = 0 + startIndex = -1 + endIndex = -1 + currentParent = pref + } + + // ExpandablePreference is PreferenceGroup but it should handle round corner + is Expandable -> { + // When ExpandablePreference is expanded, we treat is as the first item. + if (pref.isExpanded()) { + currentParent = pref as? PreferenceGroup + startIndex = i + cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_TOP or ROUND_CORNER_CENTER + endIndex = -1 + } + } + + else -> { + val parent = pref?.parent + + // item in the group should have round corner background. + cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_CENTER + if (parent === currentParent) { + // find the first item in the group + if (startIndex == -1) { + startIndex = i + cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_TOP + } + + // find the last item in the group, if we find the new last item, we should + // remove the old last item round corner. + if (endIndex == -1 || endIndex < i) { + if (endIndex != -1) { + cornerStyles[endIndex] = + cornerStyles[endIndex] and ROUND_CORNER_BOTTOM.inv() + } + endIndex = i + cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_BOTTOM + } + } else { + // this item is new group, we should reset the index. + currentParent = parent + startIndex = i + cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_TOP + endIndex = i + cornerStyles[i] = cornerStyles[i] or ROUND_CORNER_BOTTOM + } + } + } + } + } + + /** handle roundCorner background */ + private fun updateBackground(holder: PreferenceViewHolder, position: Int) { + @DrawableRes val backgroundRes = getRoundCornerDrawableRes(position, false /* isSelected*/) + + val v = holder.itemView + val paddingStart = if (backgroundRes == 0) mNormalPaddingStart else mGroupPaddingStart + val paddingEnd = if (backgroundRes == 0) mNormalPaddingEnd else mGroupPaddingEnd + + v.setPaddingRelative(paddingStart, v.paddingTop, paddingEnd, v.paddingBottom) + v.setBackgroundResource(backgroundRes) + } + + @DrawableRes + protected fun getRoundCornerDrawableRes(position: Int, isSelected: Boolean): Int { + val cornerType = mRoundCornerMappingList[position] + + if ((cornerType and ROUND_CORNER_CENTER) == 0) { + return 0 + } + + return when { + (cornerType and ROUND_CORNER_TOP) != 0 && (cornerType and ROUND_CORNER_BOTTOM) == 0 -> { + // the first + if (isSelected) R.drawable.settingslib_round_background_top_selected + else R.drawable.settingslib_round_background_top + } + + (cornerType and ROUND_CORNER_BOTTOM) != 0 && (cornerType and ROUND_CORNER_TOP) == 0 -> { + // the last + if (isSelected) R.drawable.settingslib_round_background_bottom_selected + else R.drawable.settingslib_round_background_bottom + } + + (cornerType and ROUND_CORNER_TOP) != 0 && (cornerType and ROUND_CORNER_BOTTOM) != 0 -> { + // the only one preference + if (isSelected) R.drawable.settingslib_round_background_selected + else R.drawable.settingslib_round_background + } + + else -> { + // in the center + if (isSelected) R.drawable.settingslib_round_background_center_selected + else R.drawable.settingslib_round_background_center + } + } + } + + companion object { + private const val ROUND_CORNER_CENTER: Int = 1 + private const val ROUND_CORNER_TOP: Int = 1 shl 1 + private const val ROUND_CORNER_BOTTOM: Int = 1 shl 2 + } +}
\ No newline at end of file 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 dfd296fe006f..8636524ed23c 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 @@ -23,6 +23,7 @@ import com.android.settingslib.spa.framework.common.SpaEnvironment import com.android.settingslib.spa.framework.common.createSettingsPage import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider import com.android.settingslib.spa.gallery.banner.BannerPageProvider +import com.android.settingslib.spa.gallery.card.CardPageProvider import com.android.settingslib.spa.gallery.chart.ChartPageProvider import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider import com.android.settingslib.spa.gallery.dialog.NavDialogProvider @@ -109,6 +110,7 @@ class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) { TopIntroPreferencePageProvider, CheckBoxPreferencePageProvider, TwoTargetButtonPreferencePageProvider, + CardPageProvider, ), rootPages = listOf( HomePageProvider.createSettingsPage(), diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt new file mode 100644 index 000000000000..5659e2f33156 --- /dev/null +++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt @@ -0,0 +1,104 @@ +/* + * 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.card + +import android.os.Bundle +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Stars +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.common.SettingsPageProvider +import com.android.settingslib.spa.framework.compose.navigator +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.widget.card.SuggestionCard +import com.android.settingslib.spa.widget.card.SuggestionCardModel +import com.android.settingslib.spa.widget.preference.Preference +import com.android.settingslib.spa.widget.preference.PreferenceModel +import com.android.settingslib.spa.widget.scaffold.RegularScaffold + +object CardPageProvider : SettingsPageProvider { + override val name = "Card" + + override fun getTitle(arguments: Bundle?) = TITLE + + @Composable + override fun Page(arguments: Bundle?) { + RegularScaffold(title = TITLE) { + SuggestionCard() + SuggestionCardWithLongTitle() + SuggestionCardDismissible() + } + } + + @Composable + private fun SuggestionCard() { + SuggestionCard( + SuggestionCardModel( + title = "Suggestion card", + description = "Suggestion card description", + imageVector = Icons.Filled.Stars, + ) + ) + } + + @Composable + private fun SuggestionCardWithLongTitle() { + SuggestionCard( + SuggestionCardModel( + title = "Top level suggestion card with a really, really long title", + imageVector = Icons.Filled.Stars, + onClick = {}, + ) + ) + } + + @Composable + private fun SuggestionCardDismissible() { + var isVisible by rememberSaveable { mutableStateOf(true) } + SuggestionCard( + SuggestionCardModel( + title = "Suggestion card", + description = "Suggestion card description", + imageVector = Icons.Filled.Stars, + onDismiss = { isVisible = false }, + isVisible = isVisible, + ) + ) + } + + @Composable + fun Entry() { + Preference( + object : PreferenceModel { + override val title = TITLE + override val onClick = navigator(name) + } + ) + } + + private const val TITLE = "Sample Card" +} + +@Preview +@Composable +private fun CardPagePreview() { + SettingsTheme { CardPageProvider.Page(null) } +} 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 4d77ea173a85..ebfc0c536868 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 @@ -27,6 +27,7 @@ import com.android.settingslib.spa.gallery.R import com.android.settingslib.spa.gallery.SettingsPageProviderEnum import com.android.settingslib.spa.gallery.banner.BannerPageProvider import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider +import com.android.settingslib.spa.gallery.card.CardPageProvider import com.android.settingslib.spa.gallery.chart.ChartPageProvider import com.android.settingslib.spa.gallery.dialog.DialogMainPageProvider import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider @@ -80,6 +81,7 @@ object HomePageProvider : SettingsPageProvider { DialogMainPageProvider.Entry() EditorMainPageProvider.Entry() BannerPageProvider.Entry() + CardPageProvider.Entry() CopyablePageProvider.Entry() } } 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 395748384b85..08bedf99519d 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 @@ -28,6 +28,7 @@ object SettingsDimension { val paddingExtraSmall6 = 12.dp val paddingLarge = 16.dp val paddingExtraLarge = 24.dp + val paddingExtraLarge1 = 28.dp val spinnerHorizontalPadding = paddingExtraLarge val spinnerVerticalPadding = paddingLarge @@ -37,6 +38,7 @@ object SettingsDimension { val actionIconPadding = 4.dp val itemIconSize = 24.dp + val itemIconContainerSizeSmall = 40.dp val itemIconContainerSize = 72.dp val itemPaddingStart = if (isSpaExpressiveEnabled) paddingLarge else paddingExtraLarge val itemPaddingEnd = paddingLarge @@ -47,6 +49,12 @@ object SettingsDimension { end = itemPaddingEnd, bottom = itemPaddingVertical, ) + val footerItemPadding = PaddingValues( + start = paddingExtraLarge1, + top = itemPaddingVertical, + end = itemPaddingEnd, + bottom = itemPaddingVertical, + ) val textFieldPadding = PaddingValues( start = itemPaddingStart, end = itemPaddingEnd, diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt index 86ba6864574c..61607bc8ae8a 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsShape.kt @@ -29,4 +29,6 @@ object SettingsShape { val CornerLarge = RoundedCornerShape(24.dp) val CornerExtraLarge = RoundedCornerShape(28.dp) + + val CornerExtraLarge1 = RoundedCornerShape(40.dp) } diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt new file mode 100644 index 000000000000..2126634ebd4d --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SuggestionCard.kt @@ -0,0 +1,166 @@ +/* + * Copyright (C) 2024 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.card + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Stars +import androidx.compose.material.icons.outlined.Stars +import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.tooling.preview.Preview +import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.SettingsShape +import com.android.settingslib.spa.framework.theme.SettingsTheme +import com.android.settingslib.spa.framework.theme.toSemiBoldWeight + +data class SuggestionCardModel( + val title: String, + val description: String? = null, + val imageVector: ImageVector, + + /** + * A dismiss button will be displayed if this is not null. + * + * And this callback will be called when user clicks the button. + */ + val onDismiss: (() -> Unit)? = null, + val isVisible: Boolean = true, + val onClick: (() -> Unit)? = null, +) + +@Composable +fun SuggestionCard(model: SuggestionCardModel) { + AnimatedVisibility(visible = model.isVisible) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = + Modifier.padding( + horizontal = SettingsDimension.paddingLarge, + vertical = SettingsDimension.paddingSmall, + ) + .fillMaxWidth() + .heightIn(min = SettingsDimension.preferenceMinHeight) + .clip(SettingsShape.CornerExtraLarge1) + .background(MaterialTheme.colorScheme.secondaryContainer) + .then(model.onClick?.let { Modifier.clickable(onClick = it) } ?: Modifier) + .padding(SettingsDimension.paddingExtraSmall6), + ) { + SuggestionCardIcon(model.imageVector) + Spacer(Modifier.padding(SettingsDimension.paddingSmall)) + Column(modifier = Modifier.weight(1f).semantics(mergeDescendants = true) {}) { + SuggestionCardTitle(model.title) + if (model.description != null) SuggestionCardDescription(model.description) + } + if (model.onDismiss != null) { + Spacer(Modifier.padding(SettingsDimension.paddingSmall)) + SuggestionCardDismissButton(model.onDismiss) + } + } + } +} + +@Composable +private fun SuggestionCardIcon(imageVector: ImageVector) { + Box( + modifier = + Modifier.padding(SettingsDimension.paddingSmall) + .size(SettingsDimension.itemIconContainerSizeSmall) + .clip(CircleShape) + .background(MaterialTheme.colorScheme.secondary), + contentAlignment = Alignment.Center, + ) { + Icon( + imageVector = imageVector, + contentDescription = null, + modifier = Modifier.size(SettingsDimension.itemIconSize), + tint = MaterialTheme.colorScheme.onSecondary, + ) + } +} + +@Composable +private fun SuggestionCardTitle(title: String) { + Text( + text = title, + style = MaterialTheme.typography.titleMedium.toSemiBoldWeight(), + modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny), + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) +} + +@OptIn(ExperimentalMaterial3ExpressiveApi::class) +@Composable +private fun SuggestionCardDescription(description: String) { + Text( + text = description, + style = MaterialTheme.typography.bodySmallEmphasized, + modifier = Modifier.padding(vertical = SettingsDimension.paddingTiny), + color = MaterialTheme.colorScheme.onSecondaryContainer, + ) +} + +@Composable +private fun SuggestionCardDismissButton(onDismiss: () -> Unit) { + IconButton(shape = CircleShape, onClick = onDismiss) { + Icon( + imageVector = Icons.Filled.Close, + contentDescription = + stringResource(androidx.compose.material3.R.string.m3c_snackbar_dismiss), + modifier = Modifier.size(SettingsDimension.itemIconSize), + tint = MaterialTheme.colorScheme.onSecondaryContainer, + ) + } +} + +@Preview +@Composable +private fun SuggestionCardPreview() { + SettingsTheme { + SuggestionCard( + SuggestionCardModel( + title = "Suggestion card", + description = "Suggestion card description", + imageVector = Icons.Outlined.Stars, + onDismiss = {}, + onClick = {}, + ) + ) + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt index acbdec0b30aa..66680fa547b1 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/ui/Category.kt @@ -49,7 +49,9 @@ fun CategoryTitle(title: String) { text = title, modifier = Modifier.padding( - start = SettingsDimension.itemPaddingStart, + start = + if (isSpaExpressiveEnabled) SettingsDimension.paddingSmall + else SettingsDimension.itemPaddingStart, top = 20.dp, end = if (isSpaExpressiveEnabled) SettingsDimension.paddingSmall @@ -67,16 +69,16 @@ fun CategoryTitle(title: String) { */ @Composable fun Category(title: String? = null, content: @Composable ColumnScope.() -> Unit) { + var displayTitle by remember { mutableStateOf(false) } Column( modifier = - if (isSpaExpressiveEnabled) + if (isSpaExpressiveEnabled && displayTitle) Modifier.padding( horizontal = SettingsDimension.paddingLarge, vertical = SettingsDimension.paddingSmall, ) else Modifier ) { - var displayTitle by remember { mutableStateOf(false) } if (title != null && displayTitle) CategoryTitle(title = title) Column( modifier = diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsBannerTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsBannerTest.kt index a8479b01a861..a8479b01a861 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsBannerTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsBannerTest.kt diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleBannerTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsCollapsibleBannerTest.kt index 1080fdea9455..1080fdea9455 100644 --- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleBannerTest.kt +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/banner/SettingsCollapsibleBannerTest.kt diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt new file mode 100644 index 000000000000..96bfb3d71642 --- /dev/null +++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SuggestionCardTest.kt @@ -0,0 +1,84 @@ +/* + * 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.card + +import android.content.Context +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Star +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.isNotDisplayed +import androidx.compose.ui.test.junit4.createComposeRule +import androidx.compose.ui.test.onNodeWithContentDescription +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class SuggestionCardTest { + @get:Rule val composeTestRule = createComposeRule() + + private val context: Context = ApplicationProvider.getApplicationContext() + + @Test + fun suggestionCard_contentDisplayed() { + setContent() + + composeTestRule.onNodeWithText(TITLE).assertIsDisplayed() + composeTestRule.onNodeWithText(DESCRIPTION).assertIsDisplayed() + } + + @Test + fun suggestionCard_dismiss() { + setContent() + composeTestRule + .onNodeWithContentDescription( + context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss) + ) + .performClick() + + composeTestRule.onNodeWithText(TITLE).isNotDisplayed() + composeTestRule.onNodeWithText(DESCRIPTION).isNotDisplayed() + } + + private fun setContent() { + composeTestRule.setContent { + var isVisible by rememberSaveable { mutableStateOf(true) } + SuggestionCard( + SuggestionCardModel( + title = TITLE, + description = DESCRIPTION, + imageVector = Icons.Outlined.Star, + isVisible = isVisible, + onDismiss = { isVisible = false }, + ) + ) + } + } + + private companion object { + const val TITLE = "Title" + const val DESCRIPTION = "Description" + } +} diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt index f306918ec72f..d89d3977cac3 100644 --- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt +++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppInfo.kt @@ -39,6 +39,8 @@ import androidx.compose.ui.unit.Dp import com.android.settingslib.development.DevelopmentSettingsEnabler import com.android.settingslib.spa.framework.compose.rememberDrawablePainter import com.android.settingslib.spa.framework.theme.SettingsDimension +import com.android.settingslib.spa.framework.theme.isSpaExpressiveEnabled +import com.android.settingslib.spa.widget.preference.IntroAppPreference import com.android.settingslib.spa.widget.ui.CopyableBody import com.android.settingslib.spa.widget.ui.SettingsBody import com.android.settingslib.spa.widget.ui.SettingsTitle @@ -48,23 +50,53 @@ import com.android.settingslib.spaprivileged.model.app.rememberAppRepository class AppInfoProvider(private val packageInfo: PackageInfo) { @Composable fun AppInfo(displayVersion: Boolean = false, isClonedAppPage: Boolean = false) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding( - horizontal = SettingsDimension.itemPaddingStart, - vertical = SettingsDimension.itemPaddingVertical, - ) - .semantics(mergeDescendants = true) {}, - horizontalAlignment = Alignment.CenterHorizontally, - ) { + if (isSpaExpressiveEnabled) { + val appRepository = rememberAppRepository() val app = checkNotNull(packageInfo.applicationInfo) - Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { - AppIcon(app = app, size = SettingsDimension.appIconInfoSize) + val title = appRepository.produceLabel(app, isClonedAppPage).value + + val descriptions = mutableListOf<String>() + if (app.isInstantApp) { + descriptions.add( + stringResource( + com.android.settingslib.widget.preference.app.R.string.install_type_instant + ) + ) + } + if (displayVersion) { + val versionName = packageInfo.versionNameBidiWrapped + if (versionName != null) descriptions.add(versionName) + } + + IntroAppPreference( + title = title, + descriptions = descriptions, + appIcon = { + Image( + painter = rememberDrawablePainter(appRepository.produceIcon(app).value), + contentDescription = appRepository.produceIconContentDescription(app).value, + ) + }, + ) + } else { + Column( + modifier = + Modifier.fillMaxWidth() + .padding( + horizontal = SettingsDimension.itemPaddingStart, + vertical = SettingsDimension.itemPaddingVertical, + ) + .semantics(mergeDescendants = true) {}, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val app = checkNotNull(packageInfo.applicationInfo) + Box(modifier = Modifier.padding(SettingsDimension.itemPaddingAround)) { + AppIcon(app = app, size = SettingsDimension.appIconInfoSize) + } + AppLabel(app, isClonedAppPage) + InstallType(app) + if (displayVersion) AppVersion() } - AppLabel(app, isClonedAppPage) - InstallType(app) - if (displayVersion) AppVersion() } } @@ -89,19 +121,24 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { @Composable fun FooterAppVersion(showPackageName: Boolean = rememberIsDevelopmentSettingsEnabled()) { val context = LocalContext.current - val footer = remember(packageInfo, showPackageName) { - val list = mutableListOf<String>() - packageInfo.versionNameBidiWrapped?.let { - list += context.getString(R.string.version_text, it) + val footer = + remember(packageInfo, showPackageName) { + val list = mutableListOf<String>() + packageInfo.versionNameBidiWrapped?.let { + list += context.getString(R.string.version_text, it) + } + if (showPackageName) { + list += packageInfo.packageName + } + list.joinToString(separator = System.lineSeparator()) } - if (showPackageName) { - list += packageInfo.packageName - } - list.joinToString(separator = System.lineSeparator()) - } if (footer.isBlank()) return HorizontalDivider() - Column(modifier = Modifier.padding(SettingsDimension.itemPadding)) { + Column( + modifier = + if (isSpaExpressiveEnabled) Modifier.padding(SettingsDimension.footerItemPadding) + else Modifier.padding(SettingsDimension.itemPadding) + ) { CopyableBody(footer) } } @@ -109,9 +146,7 @@ class AppInfoProvider(private val packageInfo: PackageInfo) { @Composable private fun rememberIsDevelopmentSettingsEnabled(): Boolean { val context = LocalContext.current - return remember { - DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context) - } + return remember { DevelopmentSettingsEnabler.isDevelopmentSettingsEnabled(context) } } private companion object { diff --git a/packages/SettingsLib/ZeroStatePreference/src/com/android/settingslib/widget/ZeroStatePreference.kt b/packages/SettingsLib/ZeroStatePreference/src/com/android/settingslib/widget/ZeroStatePreference.kt index 9b1ccef9dadf..62573fe18357 100644 --- a/packages/SettingsLib/ZeroStatePreference/src/com/android/settingslib/widget/ZeroStatePreference.kt +++ b/packages/SettingsLib/ZeroStatePreference/src/com/android/settingslib/widget/ZeroStatePreference.kt @@ -32,7 +32,7 @@ class ZeroStatePreference @JvmOverloads constructor( attrs: AttributeSet? = null, defStyleAttr: Int = 0, defStyleRes: Int = 0 -) : Preference(context, attrs, defStyleAttr, defStyleRes) { +) : Preference(context, attrs, defStyleAttr, defStyleRes), GroupSectionDividerMixin { private val iconTint: Int = context.getColor( com.android.settingslib.widget.theme.R.color.settingslib_materialColorOnSecondaryContainer diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 79c3ff9ce989..5a8763f4db6e 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -149,3 +149,13 @@ flag { purpose: PURPOSE_BUGFIX } } + +flag { + name: "audio_sharing_developer_option" + namespace: "cross_device_experiences" + description: "Gates whether to enable audio sharing developer option" + bug: "368401233" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml index abc58ee99904..fd2a1cb14edd 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -819,6 +819,11 @@ <!-- Summary of checkbox setting that enables the terminal app. [CHAR LIMIT=64] --> <string name="enable_terminal_summary">Enable terminal app that offers local shell access</string> + <!-- Title of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=32] --> + <string name="enable_linux_terminal_title">Linux development environment</string> + <!-- Summary of checkbox setting that enables the Linux terminal app. [CHAR LIMIT=64] --> + <string name="enable_linux_terminal_summary">Run Linux terminal on Android</string> + <!-- HDCP checking title, used for debug purposes only. [CHAR LIMIT=25] --> <string name="hdcp_checking_title">HDCP checking</string> <!-- HDCP checking dialog title, used for debug purposes only. [CHAR LIMIT=25] --> diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java index 6335e712f904..83ee9751329f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InputMediaDevice.java @@ -125,7 +125,9 @@ public class InputMediaDevice extends MediaDevice { ? mProductName : mContext.getString(R.string.media_transfer_usb_device_mic_name); case TYPE_BLUETOOTH_SCO -> - mContext.getString(R.string.media_transfer_bt_device_mic_name); + mProductName != null + ? mProductName + : mContext.getString(R.string.media_transfer_bt_device_mic_name); default -> mContext.getString(R.string.media_transfer_this_device_name_desktop); }; } diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java index 6c1cb7015225..7775b912e51d 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InputMediaDeviceTest.java @@ -44,6 +44,7 @@ public class InputMediaDeviceTest { private static final String PRODUCT_NAME_BUILTIN_MIC = "Built-in Mic"; private static final String PRODUCT_NAME_WIRED_HEADSET = "My Wired Headset"; private static final String PRODUCT_NAME_USB_HEADSET = "My USB Headset"; + private static final String PRODUCT_NAME_BT_HEADSET = "My Bluetooth Headset"; @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @@ -142,6 +143,21 @@ public class InputMediaDeviceTest { MAX_VOLUME, CURRENT_VOLUME, IS_VOLUME_FIXED, + PRODUCT_NAME_BT_HEADSET); + assertThat(btMediaDevice).isNotNull(); + assertThat(btMediaDevice.getName()).isEqualTo(PRODUCT_NAME_BT_HEADSET); + } + + @Test + public void getName_returnCorrectName_btHeadset_nullProductName() { + InputMediaDevice btMediaDevice = + InputMediaDevice.create( + mContext, + String.valueOf(BT_HEADSET_ID), + AudioDeviceInfo.TYPE_BLUETOOTH_SCO, + MAX_VOLUME, + CURRENT_VOLUME, + IS_VOLUME_FIXED, null); assertThat(btMediaDevice).isNotNull(); assertThat(btMediaDevice.getName()) diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 408ed1e861c3..b385aaa5c7d9 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -924,6 +924,7 @@ <!-- Permission required for CTS test - CtsPackageManagerTestCases--> <uses-permission android:name="android.permission.DOMAIN_VERIFICATION_AGENT" /> + <uses-permission android:name="android.permission.VERIFICATION_AGENT" /> <!-- Permission required for Cts test - CtsInputTestCases --> <uses-permission diff --git a/packages/StatementService/Parser/Android.bp b/packages/StatementService/Parser/Android.bp new file mode 100644 index 000000000000..c8af1344f4c7 --- /dev/null +++ b/packages/StatementService/Parser/Android.bp @@ -0,0 +1,26 @@ +// Copyright (C) 2024 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 { + default_applicable_licenses: ["frameworks_base_license"], +} + +android_library { + name: "StatementServiceParser", + use_resource_processor: true, + srcs: [ + "src/**/*.java", + "src/**/*.kt", + ], + target_sdk_version: "29", +} diff --git a/packages/StatementService/Parser/AndroidManifest.xml b/packages/StatementService/Parser/AndroidManifest.xml new file mode 100644 index 000000000000..a3a99ac0552d --- /dev/null +++ b/packages/StatementService/Parser/AndroidManifest.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.statementservice.parser"> +</manifest>
\ No newline at end of file diff --git a/packages/StatementService/Parser/src/com/android/statementservice/parser/DalComponentParser.kt b/packages/StatementService/Parser/src/com/android/statementservice/parser/DalComponentParser.kt new file mode 100644 index 000000000000..4314f86fad37 --- /dev/null +++ b/packages/StatementService/Parser/src/com/android/statementservice/parser/DalComponentParser.kt @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2024 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:JvmName("DalComponentParser") + +package com.android.statementservice.parser + +import android.os.PatternMatcher.PATTERN_ADVANCED_GLOB +import android.os.PatternMatcher.PATTERN_LITERAL +import android.os.PatternMatcher.PATTERN_PREFIX +import android.os.PatternMatcher.PATTERN_SIMPLE_GLOB + +/** + * Parses a DAL component matching expression to Android's {@link android.os.PatternMatcher} type + * and pattern. Matching expressions support the following wildcards: + * + * 1) An asterisk (*) matches zero to as many characters as possible + * 2) A question mark (?) matches any single character. + * + * Matching one to many characters can be done with a question mark followed by an asterisk (?+). + * + * @param expression A matching expression string from a DAL relation extension component used for + * matching a URI part. This must be a non-empty string and all characters in the + * string should be decoded. + * + * @return Returns a Pair containing a {@link android.os.PatternMatcher} type and pattern. + */ +fun parseMatchingExpression(expression: String): Pair<Int, String> { + if (expression.isNullOrEmpty()) { + throw IllegalArgumentException("Matching expressions cannot be an empty string") + } + var count = 0 + var isAdvanced = expression.contains("?*") + val pattern = buildString { + for (char in expression) { + when (char) { + '*' -> { + if (this.endsWith('.') && !this.endsWith("\\.")) { + append('+') + } else { + count += 1 + append(".*") + } + } + '?' -> { + count += 1 + append('.') + } + '.' -> { + append("\\.") + } + '[', ']', '{', '}' -> { + if (isAdvanced) { + append('\\') + } + append(char) + } + else -> append(char) + } + } + } + if (count == 0) { + return Pair(PATTERN_LITERAL, pattern) + } + if (count == 1 && pattern.endsWith(".*")) { + return Pair(PATTERN_PREFIX, pattern.dropLast(2)) + } + if (isAdvanced) { + return Pair(PATTERN_ADVANCED_GLOB, pattern) + } + return Pair(PATTERN_SIMPLE_GLOB, pattern) +}
\ No newline at end of file diff --git a/packages/StatementService/TEST_MAPPING b/packages/StatementService/TEST_MAPPING new file mode 100644 index 000000000000..0714c9366665 --- /dev/null +++ b/packages/StatementService/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit" : [ + { + "name": "StatementServiceTests" + } + ] +}
\ No newline at end of file diff --git a/packages/StatementService/tests/Android.bp b/packages/StatementService/tests/Android.bp new file mode 100644 index 000000000000..ec1bd96a0bc0 --- /dev/null +++ b/packages/StatementService/tests/Android.bp @@ -0,0 +1,30 @@ +// Copyright (C) 2024 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 { + default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_framework_android_packages", +} + +android_test { + name: "StatementServiceTests", + use_resource_processor: true, + test_suites: ["general-tests"], + srcs: ["src/**/*.kt"], + static_libs: [ + "androidx.test.ext.junit", + "androidx.test.runner", + "StatementServiceParser", + "truth", + ], +} diff --git a/packages/StatementService/tests/AndroidManifest.xml b/packages/StatementService/tests/AndroidManifest.xml new file mode 100644 index 000000000000..bde0f953252c --- /dev/null +++ b/packages/StatementService/tests/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.statementservice.test"> + + <application> + <uses-library android:name="android.test.runner" /> + </application> + + <instrumentation + android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.statementservice.test" /> +</manifest> diff --git a/packages/StatementService/tests/src/com/android/statementservice/parser/DalComponentParserTest.kt b/packages/StatementService/tests/src/com/android/statementservice/parser/DalComponentParserTest.kt new file mode 100644 index 000000000000..44a56ec91458 --- /dev/null +++ b/packages/StatementService/tests/src/com/android/statementservice/parser/DalComponentParserTest.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2024 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.statementservice.parser + +import android.os.PatternMatcher.PATTERN_ADVANCED_GLOB +import android.os.PatternMatcher.PATTERN_LITERAL +import android.os.PatternMatcher.PATTERN_PREFIX +import android.os.PatternMatcher.PATTERN_SIMPLE_GLOB +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class DalComponentParserTest { + + @Test + fun parseExpressions() { + validateParsedExpression("foobar", PATTERN_LITERAL, "foobar") + validateParsedExpression("foo.bar", PATTERN_LITERAL, "foo\\.bar") + validateParsedExpression("foo*", PATTERN_PREFIX, "foo") + validateParsedExpression("*bar", PATTERN_SIMPLE_GLOB, ".*bar") + validateParsedExpression("foo*bar", PATTERN_SIMPLE_GLOB, "foo.*bar") + validateParsedExpression("foo.*bar", PATTERN_SIMPLE_GLOB, "foo\\..*bar") + validateParsedExpression("*foo*bar", PATTERN_SIMPLE_GLOB, ".*foo.*bar") + validateParsedExpression("foo?bar", PATTERN_SIMPLE_GLOB, "foo.bar") + validateParsedExpression("foo.?bar", PATTERN_SIMPLE_GLOB, "foo\\..bar") + validateParsedExpression("?bar", PATTERN_SIMPLE_GLOB, ".bar") + validateParsedExpression("foo?", PATTERN_SIMPLE_GLOB, "foo.") + validateParsedExpression("fo?b*r", PATTERN_SIMPLE_GLOB, "fo.b.*r") + validateParsedExpression("?*bar", PATTERN_ADVANCED_GLOB, ".+bar") + validateParsedExpression("foo?*bar", PATTERN_ADVANCED_GLOB, "foo.+bar") + validateParsedExpression("foo?*bar*", PATTERN_ADVANCED_GLOB, "foo.+bar.*") + validateParsedExpression("foo*?bar", PATTERN_SIMPLE_GLOB, "foo.*.bar") + + // set matches are not supported in DAL + validateParsedExpression("foo[a-z]", PATTERN_LITERAL, "foo[a-z]") + validateParsedExpression("foo[a-z]+", PATTERN_LITERAL, "foo[a-z]+") + validateParsedExpression("foo[a-z]*", PATTERN_PREFIX, "foo[a-z]") + validateParsedExpression("[a-z]*bar", PATTERN_SIMPLE_GLOB, "[a-z].*bar") + validateParsedExpression("foo[a-z]?bar", PATTERN_SIMPLE_GLOB, "foo[a-z].bar") + validateParsedExpression("foo[a-z]?*bar", PATTERN_ADVANCED_GLOB, "foo\\[a-z\\].+bar") + + // range matches are not supported in DAL + validateParsedExpression("fo{2}", PATTERN_LITERAL, "fo{2}") + validateParsedExpression("fo{2}+", PATTERN_LITERAL, "fo{2}+") + validateParsedExpression("fo{2}*", PATTERN_PREFIX, "fo{2}") + validateParsedExpression("fo{2}*bar", PATTERN_SIMPLE_GLOB, "fo{2}.*bar") + validateParsedExpression("fo{2}?*", PATTERN_ADVANCED_GLOB, "fo\\{2\\}.+") + validateParsedExpression("foo{2}?*bar", PATTERN_ADVANCED_GLOB, "foo\\{2\\}.+bar") + } + + @Test(expected = IllegalArgumentException::class) + fun parseEmptyExpression() { + parseMatchingExpression("") + } + + private fun validateParsedExpression(given: String, expectedType: Int, expectedFilter: String) { + val (type, filter) = parseMatchingExpression(given) + assertThat(filter).isEqualTo(expectedFilter) + assertThat(type).isEqualTo(expectedType) + assertThat(filter).isEqualTo(expectedFilter) + } +}
\ No newline at end of file diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 28635318236d..3c560fdcadb6 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -87,7 +87,6 @@ filegroup { srcs: [ "tests/src/**/systemui/broadcast/BroadcastDispatcherTest.kt", "tests/src/**/systemui/broadcast/ActionReceiverTest.kt", - "tests/src/**/systemui/doze/DozeMachineTest.java", "tests/src/**/systemui/globalactions/GlobalActionsDialogLiteTest.java", "tests/src/**/systemui/globalactions/GlobalActionsImeTest.java", "tests/src/**/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt", @@ -175,7 +174,6 @@ filegroup { "tests/src/**/systemui/controls/management/ControlsEditingActivityTest.kt", "tests/src/**/systemui/controls/management/ControlsRequestDialogTest.kt", "tests/src/**/systemui/controls/ui/DetailDialogTest.kt", - "tests/src/**/systemui/doze/DozeMachineTest.kt", "tests/src/**/systemui/fontscaling/FontScalingDialogDelegateTest.kt", "tests/src/**/systemui/keyguard/CustomizationProviderTest.kt", "tests/src/**/systemui/globalactions/GlobalActionsColumnLayoutTest.java", @@ -318,9 +316,6 @@ filegroup { "tests/src/**/systemui/stylus/StylusUsiPowerStartableTest.kt", "tests/src/**/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt", "tests/src/**/keyguard/ClockEventControllerTest.kt", - "tests/src/**/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt", - "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.kt", - "tests/src/**/keyguard/LegacyLockIconViewControllerTest.java", "tests/src/**/systemui/animation/TransitionAnimatorTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/BluetoothAutoOnRepositoryTest.kt", "tests/src/**/systemui/bluetooth/qsdialog/BluetoothStateInteractorTest.kt", @@ -417,7 +412,6 @@ filegroup { "tests/src/**/systemui/stylus/StylusUsiPowerUiTest.kt", "tests/src/**/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt", "tests/src/**/keyguard/KeyguardUpdateMonitorTest.java", - "tests/src/**/keyguard/LegacyLockIconViewControllerBaseTest.java", "tests/src/**/keyguard/CarrierTextManagerTest.java", "tests/src/**/systemui/ScreenDecorationsTest.java", "tests/src/**/systemui/temporarydisplay/chipbar/SwipeChipbarAwayGestureHandlerTest.kt", @@ -899,9 +893,9 @@ android_robolectric_test { "androidx.compose.runtime_runtime", ], libs: [ - "android.test.runner.stubs.system", - "android.test.base.stubs.system", - "android.test.mock.stubs.system", + "android.test.runner.impl", + "android.test.base.impl", + "android.test.mock.impl", "truth", ], @@ -936,9 +930,9 @@ android_robolectric_test { "androidx.compose.runtime_runtime", ], libs: [ - "android.test.runner.stubs.system", - "android.test.base.stubs.system", - "android.test.mock.stubs.system", + "android.test.runner.impl", + "android.test.base.impl", + "android.test.mock.impl", "truth", ], @@ -974,9 +968,9 @@ android_ravenwood_test { "androidx.compose.runtime_runtime", ], libs: [ - "android.test.runner.stubs.system", - "android.test.base.stubs.system", - "android.test.mock.stubs.system", + "android.test.runner.impl", + "android.test.base.impl", + "android.test.mock.impl", ], auto_gen_config: true, plugins: [ diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index a21a80506279..651244a9e52a 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -395,9 +395,9 @@ flag { } flag { - name: "status_bar_ron_chips" + name: "status_bar_notification_chips" namespace: "systemui" - description: "Show rich ongoing notifications as chips in the status bar" + description: "Show promoted ongoing notifications as chips in the status bar" bug: "361346412" } @@ -1481,3 +1481,29 @@ flag { bug: "370863642" } +flag { + name: "notes_role_qs_tile" + namespace: "systemui" + description: "Enables notes role qs tile which opens default notes role app in app bubbles" + bug: "357863750" +} + +flag { + name: "media_projection_request_attribution_fix" + namespace: "systemui" + description: "Ensure MediaProjection consent requests are properly attributed" + bug: "373581993" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { + name: "secondary_user_widget_host" + namespace: "systemui" + description: "Host communal widgets in the current secondary user on HSUM." + bug: "373874416" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt index 93a99bd948f1..18f40c98fe04 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityTransitionAnimator.kt @@ -136,20 +136,16 @@ constructor( ) /** - * The timings when animating a View into an app using a spring animator. - * - * Important: since springs don't have fixed durations, these timings represent fractions of - * the progress between the spring's initial value and its final value. - * - * TODO(b/372858592): make this a separate class explicitly using percentages. + * The timings when animating a View into an app using a spring animator. These timings + * represent fractions of the progress between the spring's initial value and its final + * value. */ val SPRING_TIMINGS = - TransitionAnimator.Timings( - totalDuration = 1000L, - contentBeforeFadeOutDelay = 0L, - contentBeforeFadeOutDuration = 800L, - contentAfterFadeInDelay = 850L, - contentAfterFadeInDuration = 135L, + TransitionAnimator.SpringTimings( + contentBeforeFadeOutDelay = 0f, + contentBeforeFadeOutDuration = 0.8f, + contentAfterFadeInDelay = 0.85f, + contentAfterFadeInDuration = 0.135f, ) /** diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt index 1d8ff77ac719..4cf264253bf8 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TransitionAnimator.kt @@ -52,7 +52,7 @@ class TransitionAnimator( private val interpolators: Interpolators, /** [springTimings] and [springInterpolators] must either both be null or both not null. */ - private val springTimings: Timings? = null, + private val springTimings: SpringTimings? = null, private val springInterpolators: Interpolators? = null, private val springParams: SpringParams = DEFAULT_SPRING_PARAMS, ) { @@ -83,8 +83,22 @@ class TransitionAnimator( delay: Long, duration: Long, ): Float { + return getProgressInternal( + timings.totalDuration.toFloat(), + linearProgress, + delay.toFloat(), + duration.toFloat(), + ) + } + + private fun getProgressInternal( + totalDuration: Float, + linearProgress: Float, + delay: Float, + duration: Float, + ): Float { return MathUtils.constrain( - (linearProgress * timings.totalDuration - delay) / duration, + (linearProgress * totalDuration - delay) / duration, 0.0f, 1.0f, ) @@ -367,6 +381,25 @@ class TransitionAnimator( val contentAfterFadeInDuration: Long, ) + /** + * The timings (durations and delays) used by the multi-spring animator. These are expressed as + * fractions of 1, similar to how the progress of an animator can be expressed as a float value + * between 0 and 1. + */ + class SpringTimings( + /** The portion of animation to wait before fading out the expanding content. */ + val contentBeforeFadeOutDelay: Float, + + /** The portion of animation during which the expanding content fades out. */ + val contentBeforeFadeOutDuration: Float, + + /** The portion of animation to wait before fading in the expanded content. */ + val contentAfterFadeInDelay: Float, + + /** The portion of animation during which the expanded content fades in. */ + val contentAfterFadeInDuration: Float, + ) + /** The interpolators used by this animator. */ data class Interpolators( /** The interpolator used for the Y position, width, height and corner radius. */ @@ -453,8 +486,8 @@ class TransitionAnimator( endState: State, windowBackgroundLayer: GradientDrawable, fadeWindowBackgroundLayer: Boolean = true, - useSpring: Boolean = false, drawHole: Boolean = false, + useSpring: Boolean = false, ): Animation { val transitionContainer = controller.transitionContainer val transitionContainerOverlay = transitionContainer.overlay @@ -576,18 +609,14 @@ class TransitionAnimator( } override fun onAnimationEnd(animation: Animator) { - if (DEBUG) { - Log.d(TAG, "Animation ended") - } - - // TODO(b/330672236): Post this to the main thread instead so that it does not - // flicker with Flexiglass enabled. - controller.onTransitionAnimationEnd(isExpandingFullyAbove) - transitionContainerOverlay.remove(windowBackgroundLayer) - - if (moveBackgroundLayerWhenAppVisibilityChanges && controller.isLaunching) { - openingWindowSyncViewOverlay?.remove(windowBackgroundLayer) - } + onAnimationEnd( + controller, + isExpandingFullyAbove, + windowBackgroundLayer, + transitionContainerOverlay, + openingWindowSyncViewOverlay, + moveBackgroundLayerWhenAppVisibilityChanges, + ) } } ) @@ -1021,34 +1050,47 @@ class TransitionAnimator( cornerRadii[7] = state.bottomCornerRadius drawable.cornerRadii = cornerRadii - val timings: Timings val interpolators: Interpolators + val fadeInProgress: Float + val fadeOutProgress: Float if (useSpring) { - timings = springTimings!! interpolators = springInterpolators!! + val timings = springTimings!! + fadeInProgress = + getProgressInternal( + totalDuration = 1f, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration, + ) + fadeOutProgress = + getProgressInternal( + totalDuration = 1f, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration, + ) } else { - timings = this.timings interpolators = this.interpolators + fadeInProgress = + getProgress( + timings, + linearProgress, + timings.contentBeforeFadeOutDelay, + timings.contentBeforeFadeOutDuration, + ) + fadeOutProgress = + getProgress( + timings, + linearProgress, + timings.contentAfterFadeInDelay, + timings.contentAfterFadeInDuration, + ) } // We first fade in the background layer to hide the expanding view, then fade it out with // SRC mode to draw a hole punch in the status bar and reveal the opening window (if // needed). If !isLaunching, the reverse happens. - val fadeInProgress = - getProgress( - timings, - linearProgress, - timings.contentBeforeFadeOutDelay, - timings.contentBeforeFadeOutDuration, - ) - val fadeOutProgress = - getProgress( - timings, - linearProgress, - timings.contentAfterFadeInDelay, - timings.contentAfterFadeInDuration, - ) - if (isLaunching) { if (fadeInProgress < 1) { val alpha = diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt index 96c47cc5fb6f..4ab526188ffe 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt @@ -765,7 +765,7 @@ private fun BoxScope.CommunalHubLazyGrid( alpha = { outlineAlpha }, modifier = Modifier.requiredSize(dpSize).thenIf( - dragDropState.draggingItemIndex != index + dragDropState.draggingItemKey != item.key ) { Modifier.animateItem( placementSpec = spring(stiffness = Spring.StiffnessMediumLow) @@ -778,7 +778,7 @@ private fun BoxScope.CommunalHubLazyGrid( dragDropState = dragDropState, selected = selected, enabled = item.isWidgetContent(), - index = index, + key = item.key, ) { isDragging -> CommunalContent( modifier = Modifier.fillMaxSize(), diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt index 101385f88825..0718bc331d41 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt @@ -38,8 +38,9 @@ import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalLayoutDirection -import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.IntRect import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.round import androidx.compose.ui.unit.toOffset import androidx.compose.ui.unit.toSize import com.android.systemui.Flags.communalWidgetResizing @@ -93,7 +94,7 @@ internal constructor( private val scope: CoroutineScope, private val updateDragPositionForRemove: (offset: Offset) -> Boolean, ) { - var draggingItemIndex by mutableStateOf<Int?>(null) + var draggingItemKey by mutableStateOf<Any?>(null) private set var isDraggingToRemove by mutableStateOf(false) @@ -105,6 +106,8 @@ internal constructor( private var draggingItemInitialOffset by mutableStateOf(Offset.Zero) private var dragStartPointerOffset by mutableStateOf(Offset.Zero) + private var previousTargetItemKey: Any? = null + internal val draggingItemOffset: Offset get() = draggingItemLayoutInfo?.let { item -> @@ -112,7 +115,7 @@ internal constructor( } ?: Offset.Zero private val draggingItemLayoutInfo: LazyGridItemInfo? - get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex } + get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.key == draggingItemKey } /** * Called when dragging is initiated. @@ -137,7 +140,7 @@ internal constructor( .firstItemAtOffset(normalizedOffset - contentOffset) ?.apply { dragStartPointerOffset = normalizedOffset - this.offset.toOffset() - draggingItemIndex = index + draggingItemKey = key draggingItemInitialOffset = this.offset.toOffset() return true } @@ -146,16 +149,19 @@ internal constructor( } internal fun onDragInterrupted() { - draggingItemIndex?.let { + draggingItemKey?.let { if (isDraggingToRemove) { - contentListState.onRemove(it) + contentListState.onRemove( + contentListState.list.indexOfFirst { it.key == draggingItemKey } + ) isDraggingToRemove = false updateDragPositionForRemove(Offset.Zero) } // persist list editing changes on dragging ends contentListState.onSaveList() - draggingItemIndex = null + draggingItemKey = null } + previousTargetItemKey = null draggingItemDraggedDelta = Offset.Zero draggingItemInitialOffset = Offset.Zero dragStartPointerOffset = Offset.Zero @@ -170,15 +176,29 @@ internal constructor( val startOffset = draggingItem.offset.toOffset() + draggingItemOffset val endOffset = startOffset + draggingItem.size.toSize() val middleOffset = startOffset + (endOffset - startOffset) / 2f + val draggingBoundingBox = + IntRect(draggingItem.offset + draggingItemOffset.round(), draggingItem.size) val targetItem = - state.layoutInfo.visibleItemsInfo - .asSequence() - .filter { item -> contentListState.isItemEditable(item.index) } - .filter { item -> draggingItem.index != item.index } - .firstItemAtOffset(middleOffset) + if (communalWidgetResizing()) { + state.layoutInfo.visibleItemsInfo.findLast { item -> + val itemBoundingBox = IntRect(item.offset, item.size) + draggingItemKey != item.key && + contentListState.isItemEditable(item.index) && + draggingBoundingBox.contains(itemBoundingBox.center) + } + } else { + state.layoutInfo.visibleItemsInfo + .asSequence() + .filter { item -> contentListState.isItemEditable(item.index) } + .filter { item -> draggingItem.index != item.index } + .firstItemAtOffset(middleOffset) + } - if (targetItem != null) { + if ( + targetItem != null && + (!communalWidgetResizing() || targetItem.key != previousTargetItemKey) + ) { val scrollToIndex = if (targetItem.index == state.firstVisibleItemIndex) { draggingItem.index @@ -187,6 +207,14 @@ internal constructor( } else { null } + if (communalWidgetResizing()) { + // Keep track of the previous target item, to avoid rapidly oscillating between + // items if the target item doesn't visually move as a result of the index change. + // In this case, even after the index changes, we'd still be colliding with the + // element, so it would be selected as the target item the next time this function + // runs again, which would trigger us to revert the index change we recently made. + previousTargetItemKey = targetItem.key + } if (scrollToIndex != null) { scope.launch { // this is needed to neutralize automatic keeping the first item first. @@ -196,20 +224,17 @@ internal constructor( } else { contentListState.onMove(draggingItem.index, targetItem.index) } - draggingItemIndex = targetItem.index isDraggingToRemove = false - } else { + } else if (targetItem == null) { val overscroll = checkForOverscroll(startOffset, endOffset) if (overscroll != 0f) { scrollChannel.trySend(overscroll) } isDraggingToRemove = checkForRemove(startOffset) + previousTargetItemKey = null } } - private val LazyGridItemInfo.offsetEnd: IntOffset - get() = this.offset + this.size - /** Calculate the amount dragged out of bound on both sides. Returns 0f if not overscrolled */ private fun checkForOverscroll(startOffset: Offset, endOffset: Offset): Float { return when { @@ -237,7 +262,7 @@ fun Modifier.dragContainer( viewModel: BaseCommunalViewModel, ): Modifier { return this.then( - pointerInput(dragDropState, contentOffset) { + Modifier.pointerInput(dragDropState, contentOffset) { detectDragGesturesAfterLongPress( onDrag = { change, offset -> change.consume() @@ -273,7 +298,7 @@ fun Modifier.dragContainer( @Composable fun LazyGridItemScope.DraggableItem( dragDropState: GridDragDropState, - index: Int, + key: Any, enabled: Boolean, selected: Boolean, modifier: Modifier = Modifier, @@ -283,7 +308,7 @@ fun LazyGridItemScope.DraggableItem( return content(false) } - val dragging = index == dragDropState.draggingItemIndex + val dragging = key == dragDropState.draggingItemKey val itemAlpha: Float by animateFloatAsState( targetValue = if (dragDropState.isDraggingToRemove) 0.5f else 1f, diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt index 9444664885c8..71230f9cde12 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt @@ -17,8 +17,9 @@ package com.android.systemui.keyguard.ui.composable.modifier import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.graphicsLayer @@ -41,15 +42,17 @@ fun Modifier.burnInAware( params: BurnInParameters, isClock: Boolean = false, ): Modifier { - val translationYState = remember { mutableStateOf(0F) } - viewModel.updateBurnInParams(params.copy(translationY = { translationYState.value })) + val cachedYTranslation = remember { mutableFloatStateOf(0f) } + LaunchedEffect(Unit) { + viewModel.updateBurnInParams(params.copy(translationY = { cachedYTranslation.floatValue })) + } val burnIn = viewModel.movement val translationX by burnIn.map { it.translationX.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f) val translationY by burnIn.map { it.translationY.toFloat() }.collectAsStateWithLifecycle(initialValue = 0f) - translationYState.value = translationY + cachedYTranslation.floatValue = translationY val scaleViewModel by burnIn .map { BurnInScaleViewModel(scale = it.scale, scaleClockOnly = it.scaleClockOnly) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt index 8b9e9274b448..e4c60e166fd5 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationScrimNestedScrollConnection.kt @@ -18,6 +18,8 @@ package com.android.systemui.notifications.ui.composable import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.util.fastCoerceAtLeast +import androidx.compose.ui.util.fastCoerceAtMost import com.android.compose.nestedscroll.PriorityNestedScrollConnection /** @@ -44,7 +46,7 @@ fun NotificationScrimNestedScrollConnection( orientation = Orientation.Vertical, // scrolling up and inner content is taller than the scrim, so scrim needs to // expand; content can scroll once scrim is at the minScrimOffset. - canStartPreScroll = { offsetAvailable, offsetBeforeStart -> + canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> offsetAvailable < 0 && offsetBeforeStart == 0f && contentHeight() > minVisibleScrimHeight() && @@ -52,36 +54,38 @@ fun NotificationScrimNestedScrollConnection( }, // scrolling down and content is done scrolling to top. After that, the scrim // needs to collapse; collapse the scrim until it is at the maxScrimOffset. - canStartPostScroll = { offsetAvailable, _ -> + canStartPostScroll = { offsetAvailable, _, _ -> offsetAvailable > 0 && (scrimOffset() < maxScrimOffset || isCurrentGestureOverscroll()) }, canStartPostFling = { false }, - canContinueScroll = { - val currentHeight = scrimOffset() - minScrimOffset() < currentHeight && currentHeight < maxScrimOffset - }, - canScrollOnFling = true, + canStopOnPreFling = { false }, onStart = { offsetAvailable -> onStart(offsetAvailable) }, - onScroll = { offsetAvailable -> + onScroll = { offsetAvailable, _ -> val currentHeight = scrimOffset() val amountConsumed = if (offsetAvailable > 0) { val amountLeft = maxScrimOffset - currentHeight - offsetAvailable.coerceAtMost(amountLeft) + offsetAvailable.fastCoerceAtMost(amountLeft) } else { val amountLeft = minScrimOffset() - currentHeight - offsetAvailable.coerceAtLeast(amountLeft) + offsetAvailable.fastCoerceAtLeast(amountLeft) } snapScrimOffset(currentHeight + amountConsumed) amountConsumed }, - // Don't consume the velocity on pre/post fling onStop = { velocityAvailable -> onStop(velocityAvailable) if (scrimOffset() < minScrimOffset()) { animateScrimOffset(minScrimOffset()) } - { 0f } + // Don't consume the velocity on pre/post fling + 0f + }, + onCancel = { + onStop(0f) + if (scrimOffset() < minScrimOffset()) { + animateScrimOffset(minScrimOffset()) + } }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt index a706585deebc..edb05ebd77d1 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationStackNestedScrollConnection.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp +import androidx.compose.ui.util.fastCoerceAtLeast import com.android.compose.nestedscroll.PriorityNestedScrollConnection import kotlin.math.max import kotlin.math.roundToInt @@ -86,21 +87,25 @@ fun NotificationStackNestedScrollConnection( ): PriorityNestedScrollConnection { return PriorityNestedScrollConnection( orientation = Orientation.Vertical, - canStartPreScroll = { _, _ -> false }, - canStartPostScroll = { offsetAvailable, offsetBeforeStart -> + canStartPreScroll = { _, _, _ -> false }, + canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> offsetAvailable < 0f && offsetBeforeStart < 0f && !canScrollForward() }, canStartPostFling = { velocityAvailable -> velocityAvailable < 0f && !canScrollForward() }, - canContinueScroll = { stackOffset() > 0f }, - canScrollOnFling = true, + canStopOnPreFling = { false }, onStart = { offsetAvailable -> onStart(offsetAvailable) }, - onScroll = { offsetAvailable -> - onScroll(offsetAvailable) - offsetAvailable + onScroll = { offsetAvailable, _ -> + val minOffset = 0f + val consumed = offsetAvailable.fastCoerceAtLeast(minOffset - stackOffset()) + if (consumed != 0f) { + onScroll(consumed) + } + consumed }, onStop = { velocityAvailable -> onStop(velocityAvailable) - suspend { velocityAvailable } + velocityAvailable }, + onCancel = { onStop(0f) }, ) } diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt index 54497f621c73..2a91bd8b1d73 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeOverlay.kt @@ -133,7 +133,14 @@ fun SceneScope.QuickSettingsLayout( Column( verticalArrangement = Arrangement.spacedBy(QuickSettingsShade.Dimensions.Padding), horizontalAlignment = Alignment.CenterHorizontally, - modifier = modifier.fillMaxWidth().padding(QuickSettingsShade.Dimensions.Padding), + modifier = + modifier + .fillMaxWidth() + .padding( + start = QuickSettingsShade.Dimensions.Padding, + end = QuickSettingsShade.Dimensions.Padding, + top = QuickSettingsShade.Dimensions.Padding, + ), ) { BrightnessSliderContainer( viewModel = viewModel.brightnessSliderViewModel, diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt index 085157ac72b9..9f99c372e2a8 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/DraggableHandler.kt @@ -27,9 +27,10 @@ import com.android.compose.animation.scene.content.Content import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified import com.android.compose.nestedscroll.PriorityNestedScrollConnection -import com.android.compose.nestedscroll.SuspendedValue import kotlin.math.absoluteValue +internal typealias SuspendedValue<T> = suspend () -> T + internal interface DraggableHandler { /** * Start a drag in the given [startedPosition], with the given [overSlop] and number of @@ -612,7 +613,7 @@ internal class NestedScrollHandlerImpl( return PriorityNestedScrollConnection( orientation = orientation, - canStartPreScroll = { offsetAvailable, offsetBeforeStart -> + canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> canChangeScene = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f @@ -630,7 +631,13 @@ internal class NestedScrollHandlerImpl( return@PriorityNestedScrollConnection false } - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo // If the current swipe transition is *not* closed to 0f or 1f, then we want the // scroll events to intercept the current transition to continue the scene @@ -638,7 +645,7 @@ internal class NestedScrollHandlerImpl( isIntercepting = true true }, - canStartPostScroll = { offsetAvailable, offsetBeforeStart -> + canStartPostScroll = { offsetAvailable, offsetBeforeStart, _ -> val behavior: NestedScrollBehavior = when { offsetAvailable > 0f -> topOrLeftBehavior @@ -649,7 +656,12 @@ internal class NestedScrollHandlerImpl( val isZeroOffset = if (isExternalOverscrollGesture()) false else offsetBeforeStart == 0f - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo val canStart = when (behavior) { @@ -684,7 +696,12 @@ internal class NestedScrollHandlerImpl( // We could start an overscroll animation canChangeScene = false - _lastPointersInfo = pointersInfoOwner.pointersInfo() + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection false + } + _lastPointersInfo = pointersInfo val canStart = behavior.canStartOnPostFling && hasNextScene(velocityAvailable) if (canStart) { @@ -693,8 +710,7 @@ internal class NestedScrollHandlerImpl( canStart }, - canContinueScroll = { true }, - canScrollOnFling = false, + canStopOnPreFling = { true }, onStart = { offsetAvailable -> val pointersInfo = pointersInfo() dragController = @@ -704,19 +720,33 @@ internal class NestedScrollHandlerImpl( overSlop = if (isIntercepting) 0f else offsetAvailable, ) }, - onScroll = { offsetAvailable -> + onScroll = { offsetAvailable, _ -> val controller = dragController ?: error("Should be called after onStart") + val pointersInfo = pointersInfoOwner.pointersInfo() + if (pointersInfo.isMouseWheel) { + // Do not support mouse wheel interactions + return@PriorityNestedScrollConnection 0f + } + // TODO(b/297842071) We should handle the overscroll or slow drag if the gesture is // initiated in a nested child. controller.onDrag(delta = offsetAvailable) }, onStop = { velocityAvailable -> val controller = dragController ?: error("Should be called after onStart") - - controller - .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene) - .also { dragController = null } + try { + controller + .onStop(velocity = velocityAvailable, canChangeContent = canChangeScene) + .invoke() + } finally { + dragController = null + } + }, + onCancel = { + val controller = dragController ?: error("Should be called after onStart") + controller.onStop(velocity = 0f, canChangeContent = canChangeScene) + dragController = null }, ) } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt index ca68c256fd73..772872719ebe 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ElementMatcher.kt @@ -22,19 +22,52 @@ interface ElementMatcher { fun matches(key: ElementKey, content: ContentKey): Boolean } +/** Returns an [ElementMatcher] that matches any element in [content]. */ +fun inContent(content: ContentKey): ElementMatcher { + val matcherContent = content + return object : ElementMatcher { + override fun matches(key: ElementKey, content: ContentKey): Boolean { + return content == matcherContent + } + } +} + +/** Returns an [ElementMatcher] that matches all elements not matching [this] matcher. */ +operator fun ElementMatcher.not(): ElementMatcher { + val delegate = this + return object : ElementMatcher { + override fun matches(key: ElementKey, content: ContentKey): Boolean { + return !delegate.matches(key, content) + } + } +} + +/** + * Returns an [ElementMatcher] that matches all elements matching both [this] matcher and [other]. + */ +infix fun ElementMatcher.and(other: ElementMatcher): ElementMatcher { + val delegate = this + return object : ElementMatcher { + override fun matches(key: ElementKey, content: ContentKey): Boolean { + return delegate.matches(key, content) && other.matches(key, content) + } + } +} + /** - * Returns an [ElementMatcher] that matches elements in [content] also matching [this] - * [ElementMatcher]. + * Returns an [ElementMatcher] that matches all elements either [this] matcher, or [other], or both. */ -fun ElementMatcher.inContent(content: ContentKey): ElementMatcher { +infix fun ElementMatcher.or(other: ElementMatcher): ElementMatcher { val delegate = this - val matcherScene = content return object : ElementMatcher { override fun matches(key: ElementKey, content: ContentKey): Boolean { - return content == matcherScene && delegate.matches(key, content) + return delegate.matches(key, content) || other.matches(key, content) } } } -@Deprecated("Use inContent() instead", replaceWith = ReplaceWith("inContent(scene)")) -fun ElementMatcher.inScene(scene: SceneKey) = inContent(scene) +@Deprecated( + "Use `this and inContent()` instead", + replaceWith = ReplaceWith("this and inContent(scene)"), +) +fun ElementMatcher.inScene(scene: SceneKey) = this and inContent(scene) 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 aa70a0ce156b..8613f6da0f62 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 @@ -29,6 +29,7 @@ import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.input.pointer.AwaitPointerEventScope import androidx.compose.ui.input.pointer.PointerEvent import androidx.compose.ui.input.pointer.PointerEventPass +import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.PointerId import androidx.compose.ui.input.pointer.PointerInputChange import androidx.compose.ui.input.pointer.PointerInputScope @@ -184,6 +185,7 @@ internal class MultiPointerDraggableNode( private var startedPosition: Offset? = null private var pointersDown: Int = 0 + private var isMouseWheel: Boolean = false internal fun pointersInfo(): PointersInfo { return PointersInfo( @@ -191,6 +193,7 @@ internal class MultiPointerDraggableNode( startedPosition = startedPosition, // We could have 0 pointers during fling or for other reasons. pointersDown = pointersDown.coerceAtLeast(1), + isMouseWheel = isMouseWheel, ) } @@ -202,8 +205,15 @@ internal class MultiPointerDraggableNode( // [requireAncestorPointersInfoOwner], to our descendants. while (currentContext.isActive) { // During the Initial pass, we receive the event after our ancestors. - val changes = awaitPointerEvent(PointerEventPass.Initial).changes + val pointerEvent = awaitPointerEvent(PointerEventPass.Initial) + + // Ignore cursor has entered the input region. + // This will only be sent after the cursor is hovering when in the input region. + if (pointerEvent.type == PointerEventType.Enter) continue + + val changes = pointerEvent.changes pointersDown = changes.countDown() + isMouseWheel = pointerEvent.type == PointerEventType.Scroll when { // There are no more pointers down. @@ -223,7 +233,8 @@ internal class MultiPointerDraggableNode( // The first pointer down, startedPosition was not set. startedPosition == null -> { - val firstPointerDown = changes.single() + // Mouse wheel could start with multiple pointer down + val firstPointerDown = changes.first() velocityPointerId = firstPointerDown.id velocityTracker.resetTracking() velocityTracker.addPointerInputChange(firstPointerDown) @@ -647,4 +658,8 @@ internal fun interface PointersInfoOwner { fun pointersInfo(): PointersInfo } -internal data class PointersInfo(val startedPosition: Offset?, val pointersDown: Int) +internal data class PointersInfo( + val startedPosition: Offset?, + val pointersDown: Int, + val isMouseWheel: Boolean, +) diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt index 2657d7cf8156..3c3c612c028b 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt @@ -430,6 +430,10 @@ internal class MutableSceneTransitionLayoutStateImpl( // Replace the transition. transitionStates = transitionStates.subList(0, transitionStates.lastIndex) + transition + + // Make sure it is removed from the finishedTransitions set if it was already + // finished. + finishedTransitions.remove(currentState) } else { // Append the new transition. transitionStates = transitionStates + transition diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt index 205267da151a..f0043e1e89b0 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeAnimation.kt @@ -27,7 +27,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.unit.IntSize import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState.HasOverscrollProperties.Companion.DistanceUnspecified -import com.android.compose.nestedscroll.SuspendedValue import kotlin.math.absoluteValue import kotlinx.coroutines.CompletableDeferred 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 061583a64702..a3f2a434cff7 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 @@ -81,16 +81,16 @@ private class SwipeToSceneRootNode( draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector, ) : DelegatingNode() { - private var delegate = delegate(SwipeToSceneNode(draggableHandler, swipeDetector)) + private var delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector)) fun update(draggableHandler: DraggableHandlerImpl, swipeDetector: SwipeDetector) { - if (draggableHandler == delegate.draggableHandler) { + if (draggableHandler == delegateNode.draggableHandler) { // Simple update, just update the swipe detector directly and keep the node. - delegate.swipeDetector = swipeDetector + delegateNode.swipeDetector = swipeDetector } else { // The draggableHandler changed, force recreate the underlying SwipeToSceneNode. - undelegate(delegate) - delegate = delegate(SwipeToSceneNode(draggableHandler, swipeDetector)) + undelegate(delegateNode) + delegateNode = delegate(SwipeToSceneNode(draggableHandler, swipeDetector)) } } } diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt index 4ae323517b26..ecf64b771d1f 100644 --- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt +++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/LargeTopAppBarNestedScrollConnection.kt @@ -18,6 +18,8 @@ package com.android.compose.nestedscroll import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.input.nestedscroll.NestedScrollConnection +import androidx.compose.ui.util.fastCoerceAtLeast +import androidx.compose.ui.util.fastCoerceAtMost /** * A [NestedScrollConnection] that listens for all vertical scroll events and responds in the @@ -43,35 +45,32 @@ fun LargeTopAppBarNestedScrollConnection( orientation = Orientation.Vertical, // When swiping up, the LargeTopAppBar will shrink (to [minHeight]) and the content will // expand. Then, you can then scroll down the content. - canStartPreScroll = { offsetAvailable, offsetBeforeStart -> + canStartPreScroll = { offsetAvailable, offsetBeforeStart, _ -> offsetAvailable < 0 && offsetBeforeStart == 0f && height() > minHeight() }, // When swiping down, the content will scroll up until it reaches the top. Then, the // LargeTopAppBar will expand until it reaches its [maxHeight]. - canStartPostScroll = { offsetAvailable, _ -> + canStartPostScroll = { offsetAvailable, _, _ -> offsetAvailable > 0 && height() < maxHeight() }, canStartPostFling = { false }, - canContinueScroll = { - val currentHeight = height() - minHeight() < currentHeight && currentHeight < maxHeight() - }, - canScrollOnFling = true, + canStopOnPreFling = { false }, onStart = { /* do nothing */ }, - onScroll = { offsetAvailable -> + onScroll = { offsetAvailable, _ -> val currentHeight = height() val amountConsumed = if (offsetAvailable > 0) { val amountLeft = maxHeight() - currentHeight - offsetAvailable.coerceAtMost(amountLeft) + offsetAvailable.fastCoerceAtMost(amountLeft) } else { val amountLeft = minHeight() - currentHeight - offsetAvailable.coerceAtLeast(amountLeft) + offsetAvailable.fastCoerceAtLeast(amountLeft) } onHeightChanged(currentHeight + amountConsumed) amountConsumed }, // Don't consume the velocity on pre/post fling - onStop = { { 0f } }, + onStop = { 0f }, + onCancel = { /* do nothing */ }, ) } 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 a3641e6635e7..636c55799ff2 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 @@ -16,37 +16,59 @@ package com.android.compose.nestedscroll +import androidx.compose.animation.core.AnimationState +import androidx.compose.animation.core.DecayAnimationSpec +import androidx.compose.animation.core.animateDecay import androidx.compose.foundation.gestures.Orientation import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.NestedScrollSource import androidx.compose.ui.unit.Velocity import com.android.compose.ui.util.SpaceVectorConverter +import kotlin.math.abs import kotlin.math.sign - -internal typealias SuspendedValue<T> = suspend () -> T +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.coroutineScope /** - * This [NestedScrollConnection] waits for a child to scroll ([onPreScroll] or [onPostScroll]), and - * then decides (via [canStartPreScroll] or [canStartPostScroll]) if it should take over scrolling. - * If it does, it will scroll before its children, until [canContinueScroll] allows it. + * A [NestedScrollConnection] that intercepts scroll events in priority mode. * - * Note: Call [reset] before destroying this object to make sure you always get a call to [onStop] - * after [onStart]. + * Priority mode allows this connection to take control over scroll events within a nested scroll + * hierarchy. When in priority mode, this connection consumes scroll events before its children, + * enabling custom scrolling behaviors like sticky headers. * + * @param orientation The orientation of the scroll. + * @param canStartPreScroll lambda that returns true if the connection can start consuming scroll + * events in pre-scroll mode. + * @param canStartPostScroll lambda that returns true if the connection can start consuming scroll + * events in post-scroll mode. + * @param canStartPostFling lambda that returns true if the connection can start consuming scroll + * events in post-fling mode. + * @param canStopOnPreFling lambda that returns true if the connection can stop consuming scroll + * events in pre-fling (i.e. as soon as the user lifts their fingers). + * @param onStart lambda that is called when the connection starts consuming scroll events. + * @param onScroll lambda that is called when the connection consumes a scroll event and returns the + * consumed amount. + * @param onStop lambda that is called when the connection stops consuming scroll events and returns + * the consumed velocity. + * @param onCancel lambda that is called when the connection is cancelled. * @sample LargeTopAppBarNestedScrollConnection * @sample com.android.compose.animation.scene.NestedScrollHandlerImpl.nestedScrollConnection */ class PriorityNestedScrollConnection( orientation: Orientation, - private val canStartPreScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean, - private val canStartPostScroll: (offsetAvailable: Float, offsetBeforeStart: Float) -> Boolean, + private val canStartPreScroll: + (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean, + private val canStartPostScroll: + (offsetAvailable: Float, offsetBeforeStart: Float, source: NestedScrollSource) -> Boolean, private val canStartPostFling: (velocityAvailable: Float) -> Boolean, - private val canContinueScroll: (source: NestedScrollSource) -> Boolean, - private val canScrollOnFling: Boolean, + private val canStopOnPreFling: () -> Boolean, private val onStart: (offsetAvailable: Float) -> Unit, - private val onScroll: (offsetAvailable: Float) -> Float, - private val onStop: (velocityAvailable: Float) -> SuspendedValue<Float>, + private val onScroll: (offsetAvailable: Float, source: NestedScrollSource) -> Float, + private val onStop: suspend (velocityAvailable: Float) -> Float, + private val onCancel: () -> Unit, ) : NestedScrollConnection, SpaceVectorConverter by SpaceVectorConverter(orientation) { /** In priority mode [onPreScroll] events are first consumed by the parent, via [onScroll]. */ @@ -54,6 +76,9 @@ class PriorityNestedScrollConnection( private var offsetScrolledBeforePriorityMode = 0f + /** This job allows us to interrupt the onStop animation */ + private var onStopJob: Deferred<Float> = CompletableDeferred(0f) + override fun onPostScroll( consumed: Offset, available: Offset, @@ -64,62 +89,48 @@ class PriorityNestedScrollConnection( // the beginning or from the last fling gesture. val offsetBeforeStart = offsetScrolledBeforePriorityMode - availableFloat - if ( - isPriorityMode || - (source == NestedScrollSource.SideEffect && !canScrollOnFling) || - !canStartPostScroll(availableFloat, offsetBeforeStart) - ) { + if (isPriorityMode || !canStartPostScroll(availableFloat, offsetBeforeStart, source)) { // The priority mode cannot start so we won't consume the available offset. return Offset.Zero } - return onPriorityStart(availableFloat).toOffset() + return start(availableFloat, source).toOffset() } override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset { if (!isPriorityMode) { - if (source == NestedScrollSource.UserInput || canScrollOnFling) { - val availableFloat = available.toFloat() - if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode)) { - return onPriorityStart(availableFloat).toOffset() - } - // We want to track the amount of offset consumed before entering priority mode - offsetScrolledBeforePriorityMode += availableFloat + val availableFloat = available.toFloat() + if (canStartPreScroll(availableFloat, offsetScrolledBeforePriorityMode, source)) { + return start(availableFloat, source).toOffset() } - - return Offset.Zero - } - - val availableFloat = available.toFloat() - if (!canContinueScroll(source)) { - // Step 3a: We have lost priority and we no longer need to intercept scroll events. - onPriorityStop(velocity = 0f) - - // We've just reset offsetScrolledBeforePriorityMode to 0f // We want to track the amount of offset consumed before entering priority mode offsetScrolledBeforePriorityMode += availableFloat - return Offset.Zero } - // Step 2: We have the priority and can consume the scroll events. - return onScroll(availableFloat).toOffset() + return scroll(available.toFloat(), source).toOffset() } override suspend fun onPreFling(available: Velocity): Velocity { - if (isPriorityMode && canScrollOnFling) { - // We don't want to consume the velocity, we prefer to continue receiving scroll events. + if (!isPriorityMode) { + resetOffsetTracker() return Velocity.Zero } - // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the speed - // of the fling gesture. - return onPriorityStop(velocity = available.toFloat()).invoke().toVelocity() + + if (canStopOnPreFling()) { + // Step 3b: The finger is lifted, we can stop intercepting scroll events and use the + // velocity of the fling gesture. + return stop(velocityAvailable = available.toFloat()).toVelocity() + } + + // We don't want to consume the velocity, we prefer to continue receiving scroll events. + return Velocity.Zero } override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity { val availableFloat = available.toFloat() if (isPriorityMode) { - return onPriorityStop(velocity = availableFloat).invoke().toVelocity() + return stop(velocityAvailable = availableFloat).toVelocity() } if (!canStartPostFling(availableFloat)) { @@ -131,10 +142,14 @@ class PriorityNestedScrollConnection( // TODO(b/291053278): Remove canStartPostFling() and instead make it possible to define the // overscroll behavior on the Scene level. val smallOffset = availableFloat.sign - onPriorityStart(availableOffset = smallOffset) + start( + availableOffset = smallOffset, + source = NestedScrollSource.SideEffect, + skipScroll = true, + ) // This is the last event of a scroll gesture. - return onPriorityStop(availableFloat).invoke().toVelocity() + return stop(availableFloat).toVelocity() } /** @@ -143,36 +158,76 @@ class PriorityNestedScrollConnection( * TODO(b/303224944) This method should be removed. */ fun reset() { - // Step 3c: To ensure that an onStop is always called for every onStart. - onPriorityStop(velocity = 0f) + if (isPriorityMode) { + // Step 3c: To ensure that an onStop (or onCancel) is always called for every onStart. + cancel() + } else { + resetOffsetTracker() + } } - private fun onPriorityStart(availableOffset: Float): Float { - if (isPriorityMode) { - error("This should never happen, onPriorityStart() was called when isPriorityMode") + private fun shouldStop(consumed: Float): Boolean { + return consumed == 0f + } + + private fun start( + availableOffset: Float, + source: NestedScrollSource, + skipScroll: Boolean = false, + ): Float { + check(!isPriorityMode) { + "This should never happen, start() was called when isPriorityMode" } // Step 1: It's our turn! We start capturing scroll events when one of our children has an // available offset following a scroll event. isPriorityMode = true + onStopJob.cancel() + // 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(availableOffset) - return onScroll(availableOffset) + return if (skipScroll) 0f else scroll(availableOffset, source) } - private fun onPriorityStop(velocity: Float): SuspendedValue<Float> { - // We can restart tracking the consumed offsets from scratch. - offsetScrolledBeforePriorityMode = 0f + private fun scroll(offsetAvailable: Float, source: NestedScrollSource): Float { + // Step 2: We have the priority and can consume the scroll events. + val consumedByScroll = onScroll(offsetAvailable, source) - if (!isPriorityMode) { - return { 0f } + if (shouldStop(consumedByScroll)) { + // Step 3a: We have lost priority and we no longer need to intercept scroll events. + cancel() + + // We've just reset offsetScrolledBeforePriorityMode to 0f + // We want to track the amount of offset consumed before entering priority mode + offsetScrolledBeforePriorityMode += offsetAvailable - consumedByScroll } + return consumedByScroll + } + + /** Reset the tracking of consumed offsets before entering in priority mode. */ + private fun resetOffsetTracker() { + offsetScrolledBeforePriorityMode = 0f + } + + private suspend fun stop(velocityAvailable: Float): Float { + check(isPriorityMode) { "This should never happen, stop() was called before start()" } isPriorityMode = false + resetOffsetTracker() - return onStop(velocity) + return coroutineScope { + onStopJob = async { onStop(velocityAvailable) } + onStopJob.await() + } + } + + private fun cancel() { + check(isPriorityMode) { "This should never happen, cancel() was called before start()" } + isPriorityMode = false + resetOffsetTracker() + onCancel() } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt index ecef6be49df8..a0fed9064181 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/DraggableHandlerTest.kt @@ -39,7 +39,6 @@ import com.android.compose.animation.scene.TestScenes.SceneC import com.android.compose.animation.scene.content.state.TransitionState import com.android.compose.animation.scene.content.state.TransitionState.Transition import com.android.compose.animation.scene.subjects.assertThat -import com.android.compose.nestedscroll.SuspendedValue import com.android.compose.test.MonotonicClockTestScope import com.android.compose.test.runMonotonicClockTest import com.android.compose.test.transition @@ -128,7 +127,7 @@ class DraggableHandlerTest { val horizontalDraggableHandler = layoutImpl.draggableHandler(Orientation.Horizontal) var pointerInfoOwner: () -> PointersInfo = { - PointersInfo(startedPosition = Offset.Zero, pointersDown = 1) + PointersInfo(startedPosition = Offset.Zero, pointersDown = 1, isMouseWheel = false) } fun nestedScrollConnection( @@ -1188,7 +1187,9 @@ class DraggableHandlerTest { val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) // Drag from the **top** of the screen - pointerInfoOwner = { PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1) } + pointerInfoOwner = { + PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = false) + } assertIdle(currentScene = SceneC) nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) @@ -1206,7 +1207,11 @@ class DraggableHandlerTest { // Drag from the **bottom** of the screen pointerInfoOwner = { - PointersInfo(startedPosition = Offset(0f, SCREEN_SIZE), pointersDown = 1) + PointersInfo( + startedPosition = Offset(0f, SCREEN_SIZE), + pointersDown = 1, + isMouseWheel = false, + ) } assertIdle(currentScene = SceneC) @@ -1221,6 +1226,22 @@ class DraggableHandlerTest { } @Test + fun ignoreMouseWheel() = runGestureTest { + // Start at scene C. + navigateToSceneC() + val nestedScroll = nestedScrollConnection(nestedScrollBehavior = EdgeAlways) + + // Use mouse wheel + pointerInfoOwner = { + PointersInfo(startedPosition = Offset(0f, 0f), pointersDown = 1, isMouseWheel = true) + } + assertIdle(currentScene = SceneC) + + nestedScroll.scroll(available = upOffset(fractionOfScreen = 0.1f)) + assertIdle(currentScene = SceneC) + } + + @Test fun transitionIsImmediatelyUpdatedWhenReleasingFinger() = runGestureTest { // Swipe up from the middle to transition to scene B. val middle = Offset(SCREEN_SIZE / 2f, SCREEN_SIZE / 2f) diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt new file mode 100644 index 000000000000..af0962361fb2 --- /dev/null +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementMatcherTest.kt @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2024 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.compose.animation.scene + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.android.compose.animation.scene.TestElements.Bar +import com.android.compose.animation.scene.TestElements.Foo +import com.android.compose.animation.scene.TestScenes.SceneA +import com.android.compose.animation.scene.TestScenes.SceneB +import com.google.common.truth.Truth.assertThat +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class ElementMatcherTest { + @Test + fun and() { + val matcher = Foo and inContent(SceneA) + assertThat(matcher.matches(Foo, SceneA)).isTrue() + assertThat(matcher.matches(Foo, SceneB)).isFalse() + assertThat(matcher.matches(Bar, SceneA)).isFalse() + assertThat(matcher.matches(Bar, SceneB)).isFalse() + } + + @Test + fun or() { + val matcher = Foo or inContent(SceneA) + assertThat(matcher.matches(Foo, SceneA)).isTrue() + assertThat(matcher.matches(Foo, SceneB)).isTrue() + assertThat(matcher.matches(Bar, SceneA)).isTrue() + assertThat(matcher.matches(Bar, SceneB)).isFalse() + } + + @Test + fun not() { + val matcher = !Foo + assertThat(matcher.matches(Foo, SceneA)).isFalse() + assertThat(matcher.matches(Foo, SceneB)).isFalse() + assertThat(matcher.matches(Bar, SceneA)).isTrue() + assertThat(matcher.matches(Bar, SceneB)).isTrue() + } +} diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt index 39d46990dc4b..ee807e6a7ede 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt @@ -2221,8 +2221,8 @@ class ElementTest { // In A => B, Foo is not shared and first fades out from A then fades in // B. sharedElement(TestElements.Foo, enabled = false) - fractionRange(end = 0.5f) { fade(TestElements.Foo.inContent(SceneA)) } - fractionRange(start = 0.5f) { fade(TestElements.Foo.inContent(SceneB)) } + fractionRange(end = 0.5f) { fade(TestElements.Foo.inScene(SceneA)) } + fractionRange(start = 0.5f) { fade(TestElements.Foo.inScene(SceneB)) } } from(SceneB, to = SceneA) { diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt index c8f6e6d99933..3df608717414 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MultiPointerDraggableTest.kt @@ -46,7 +46,6 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Velocity import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.modifiers.thenIf -import com.android.compose.nestedscroll.SuspendedValue import com.google.common.truth.Truth.assertThat import kotlin.properties.Delegates import kotlinx.coroutines.coroutineScope diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt index cae6617cb11b..7ea414d6b8cd 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/OverlayTest.kt @@ -35,6 +35,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.SemanticsMatcher import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsNotDisplayed import androidx.compose.ui.test.assertPositionInRootIsEqualTo @@ -205,7 +206,8 @@ class OverlayTest { val key = MovableElementKey("MovableBar", contents = setOf(SceneA, OverlayA, OverlayB)) val elementChildTag = "elementChildTag" - fun elementChild(content: ContentKey) = hasTestTag(elementChildTag) and inContent(content) + fun elementChild(content: ContentKey) = + hasTestTag(elementChildTag) and SemanticsMatcher.inContent(content) @Composable fun ContentScope.MovableBar() { @@ -773,7 +775,7 @@ class OverlayTest { // Overscroll on Overlay A. scope.launch { state.startTransition(transition(SceneA, OverlayA, progress = { 1.5f })) } rule - .onNode(hasTestTag(movableElementChildTag) and inContent(SceneA)) + .onNode(hasTestTag(movableElementChildTag) and SemanticsMatcher.inContent(SceneA)) .assertPositionInRootIsEqualTo(0.dp, 0.dp) .assertSizeIsEqualTo(100.dp) .assertIsDisplayed() diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt index f3a34884c756..f5bb5ba032c2 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt @@ -727,4 +727,45 @@ class SceneTransitionLayoutStateTest { // The previous job is cancelled and does not infinitely collect the progress. job.join() } + + @Test + fun replacedTransitionIsRemovedFromFinishedTransitions() = runTest { + val state = MutableSceneTransitionLayoutState(SceneA) + + val aToB = + transition( + SceneA, + SceneB, + onFreezeAndAnimate = { + // Do nothing, so that this transition stays in the transitionStates list and we + // can finish() it manually later. + }, + ) + val replacingAToB = transition(SceneB, SceneC) + val replacingBToC = transition(SceneB, SceneC, replacedTransition = replacingAToB) + + // Start A => B. + val aToBJob = state.startTransitionImmediately(animationScope = this, aToB) + + // Start B => C and immediately finish it. It will be flagged as finished in + // STLState.finishedTransitions given that A => B is not finished yet. + val bToCJob = state.startTransitionImmediately(animationScope = this, replacingAToB) + replacingAToB.finish() + bToCJob.join() + + // Start a new B => C that replaces the previously finished B => C. + val replacingBToCJob = + state.startTransitionImmediately(animationScope = this, replacingBToC) + + // Finish A => B. + aToB.finish() + aToBJob.join() + + // Finish the new B => C. + replacingBToC.finish() + replacingBToCJob.join() + + assertThat(state.transitionState).isIdle() + assertThat(state.transitionState).hasCurrentScene(SceneC) + } } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt index 28d0a473935d..3001505d0e02 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SwipeToSceneTest.kt @@ -17,6 +17,8 @@ package com.android.compose.animation.scene import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.gestures.rememberScrollableState +import androidx.compose.foundation.gestures.scrollable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -39,12 +41,14 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalViewConfiguration import androidx.compose.ui.platform.testTag +import androidx.compose.ui.test.ScrollWheel import androidx.compose.ui.test.assertPositionInRootIsEqualTo import androidx.compose.ui.test.assertTextEquals import androidx.compose.ui.test.junit4.createComposeRule import androidx.compose.ui.test.onNodeWithTag import androidx.compose.ui.test.onRoot import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performMouseInput import androidx.compose.ui.test.performTouchInput import androidx.compose.ui.test.swipeRight import androidx.compose.ui.test.swipeUp @@ -102,26 +106,22 @@ class SwipeToSceneTest { modifier = Modifier.size(LayoutWidth, LayoutHeight).testTag(TestElements.Foo.debugName), ) { scene( - SceneA, + key = SceneA, userActions = if (swipesEnabled()) - mapOf( - Swipe.Left to SceneB, - Swipe.Down to TestScenes.SceneC, - Swipe.Up to SceneB, - ) + mapOf(Swipe.Left to SceneB, Swipe.Down to SceneC, Swipe.Up to SceneB) else emptyMap(), ) { Box(Modifier.fillMaxSize()) } scene( - SceneB, + key = SceneB, userActions = if (swipesEnabled()) mapOf(Swipe.Right to SceneA) else emptyMap(), ) { Box(Modifier.fillMaxSize()) } scene( - TestScenes.SceneC, + key = SceneC, userActions = if (swipesEnabled()) mapOf( @@ -196,7 +196,7 @@ class SwipeToSceneTest { // Drag is in progress, so currentScene = SceneA and progress = 56dp / LayoutHeight transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) assertThat(transition).hasCurrentScene(SceneA) assertThat(transition).hasProgress(56.dp / LayoutHeight) assertThat(transition).isInitiatedByUserInput() @@ -206,15 +206,15 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) - assertThat(transition).hasCurrentScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) + assertThat(transition).hasCurrentScene(SceneC) assertThat(transition).hasProgress(56.dp / LayoutHeight) assertThat(transition).isInitiatedByUserInput() // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test @@ -271,20 +271,20 @@ class SwipeToSceneTest { // We should be animating to C (currentScene = SceneC). transition = assertThat(layoutState.transitionState).isSceneTransition() assertThat(transition).hasFromScene(SceneA) - assertThat(transition).hasToScene(TestScenes.SceneC) - assertThat(transition).hasCurrentScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) + assertThat(transition).hasCurrentScene(SceneC) assertThat(transition).hasProgress(55.dp / LayoutHeight) // Wait for the animation to finish. We should now be in scene C. rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test fun multiPointerSwipe() { // Start at scene C. - val layoutState = layoutState(TestScenes.SceneC) + val layoutState = layoutState(SceneC) // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. @@ -295,7 +295,7 @@ class SwipeToSceneTest { } assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe down with two fingers. rule.onRoot().performTouchInput { @@ -307,7 +307,7 @@ class SwipeToSceneTest { // We are transitioning to B because we used 2 fingers. val transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -315,13 +315,13 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { repeat(2) { i -> up(pointerId = i) } } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test fun defaultEdgeSwipe() { // Start at scene C. - val layoutState = layoutState(TestScenes.SceneC) + val layoutState = layoutState(SceneC) // The draggable touch slop, i.e. the min px distance a touch pointer must move before it is // detected as a drag event. @@ -332,7 +332,7 @@ class SwipeToSceneTest { } assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe down from the top edge. rule.onRoot().performTouchInput { @@ -342,7 +342,7 @@ class SwipeToSceneTest { // We are transitioning to B (and not A) because we started from the top edge. var transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -350,7 +350,7 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) // Swipe right from the left edge. rule.onRoot().performTouchInput { @@ -360,7 +360,7 @@ class SwipeToSceneTest { // We are transitioning to B (and not A) because we started from the left edge. transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasFromScene(TestScenes.SceneC) + assertThat(transition).hasFromScene(SceneC) assertThat(transition).hasToScene(SceneB) // Release the fingers and wait for the animation to end. We are back to C because we only @@ -368,7 +368,7 @@ class SwipeToSceneTest { rule.onRoot().performTouchInput { up() } rule.waitForIdle() assertThat(layoutState.transitionState).isIdle() - assertThat(layoutState.transitionState).hasCurrentScene(TestScenes.SceneC) + assertThat(layoutState.transitionState).hasCurrentScene(SceneC) } @Test @@ -434,7 +434,7 @@ class SwipeToSceneTest { // We should still correctly compute that we are swiping down to scene C. var transition = assertThat(layoutState.transitionState).isSceneTransition() - assertThat(transition).hasToScene(TestScenes.SceneC) + assertThat(transition).hasToScene(SceneC) // Release the finger, animating back to scene A. rule.onRoot().performTouchInput { up() } @@ -502,6 +502,89 @@ class SwipeToSceneTest { } @Test + fun mouseWheel_pointerInputApi_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + TestContent(layoutState) + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + } + + @Test + fun mouseWheel_scrollableCannotScroll_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Box( + Modifier.fillMaxSize() + // A scrollable that does not consume the scroll gesture + .scrollable(rememberScrollableState { 0f }, Orientation.Vertical) + ) + } + scene(SceneB) { Box(Modifier.fillMaxSize()) } + } + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + } + + @Test + fun mouseWheel_scrollableConsume_ignoredByStl() { + val layoutState = layoutState() + var touchSlop = 0f + var lastScroll = 0f + rule.setContent { + touchSlop = LocalViewConfiguration.current.touchSlop + SceneTransitionLayout(layoutState, Modifier.size(LayoutWidth, LayoutHeight)) { + scene(SceneA, userActions = mapOf(Swipe.Down to SceneB)) { + Box( + Modifier.fillMaxSize() + // A scrollable that consumes the scroll gesture + .scrollable( + rememberScrollableState { + lastScroll = it + it + }, + Orientation.Vertical, + ) + ) + } + scene(SceneB) { Box(Modifier.fillMaxSize()) } + } + } + + rule.onRoot().performMouseInput { + enter(middle) + scroll(touchSlop * 10, ScrollWheel.Vertical) + } + + // Mouse wheel scroll are ignored + assertThat(layoutState.currentTransition).isNull() + + // Mouse wheel scroll can still be consumed by the scrollable + assertThat(lastScroll).isNotEqualTo(0f) + assertThat(touchSlop).isNotEqualTo(0f) + } + + @Test fun transitionKey() { val transitionkey = TransitionKey(debugName = "foo") val state = diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt index 4877cd610875..2e3a934c2701 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/transformation/SharedElementTest.kt @@ -31,7 +31,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.android.compose.animation.scene.Edge import com.android.compose.animation.scene.TestElements import com.android.compose.animation.scene.TestScenes -import com.android.compose.animation.scene.inContent +import com.android.compose.animation.scene.inScene import com.android.compose.animation.scene.testTransition import com.android.compose.test.assertSizeIsEqualTo import org.junit.Rule @@ -125,10 +125,10 @@ class SharedElementTest { sharedElement(TestElements.Foo, enabled = false) // In SceneA, Foo leaves to the left edge. - translate(TestElements.Foo.inContent(TestScenes.SceneA), Edge.Left) + translate(TestElements.Foo.inScene(TestScenes.SceneA), Edge.Left) // In SceneB, Foo comes from the bottom edge. - translate(TestElements.Foo.inContent(TestScenes.SceneB), Edge.Bottom) + translate(TestElements.Foo.inScene(TestScenes.SceneB), Edge.Bottom) }, ) { before { onElement(TestElements.Foo).assertPositionInRootIsEqualTo(10.dp, 50.dp) } diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt index badc43bd3e0f..1a3b86b936df 100644 --- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt +++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/nestedscroll/PriorityNestedScrollConnectionTest.kt @@ -34,30 +34,31 @@ class PriorityNestedScrollConnectionTest { private var canStartPreScroll = false private var canStartPostScroll = false private var canStartPostFling = false - private var canContinueScroll = false + private var canStopOnPreFling = true private var isStarted = false private var lastScroll: Float? = null - private var returnOnScroll = 0f + private var consumeScroll = true private var lastStop: Float? = null - private var returnOnStop = 0f + private var isCancelled: Boolean = false + private var consumeStop = true private val scrollConnection = PriorityNestedScrollConnection( orientation = Orientation.Vertical, - canStartPreScroll = { _, _ -> canStartPreScroll }, - canStartPostScroll = { _, _ -> canStartPostScroll }, + canStartPreScroll = { _, _, _ -> canStartPreScroll }, + canStartPostScroll = { _, _, _ -> canStartPostScroll }, canStartPostFling = { canStartPostFling }, - canContinueScroll = { canContinueScroll }, - canScrollOnFling = false, + canStopOnPreFling = { canStopOnPreFling }, onStart = { isStarted = true }, - onScroll = { - lastScroll = it - returnOnScroll + onScroll = { offsetAvailable, _ -> + lastScroll = offsetAvailable + if (consumeScroll) offsetAvailable else 0f }, onStop = { lastStop = it - { returnOnStop } + if (consumeStop) it else 0f }, + onCancel = { isCancelled = true }, ) @Test @@ -85,7 +86,7 @@ class PriorityNestedScrollConnectionTest { canStartPostScroll = true scrollConnection.onPostScroll( consumed = Offset.Zero, - available = Offset.Zero, + available = Offset(1f, 1f), source = UserInput, ) } @@ -136,45 +137,55 @@ class PriorityNestedScrollConnectionTest { @Test fun step2_onPriorityMode_shouldContinueIfAllowed() { startPriorityModePostScroll() - canContinueScroll = true - scrollConnection.onPreScroll(available = Offset(1f, 1f), source = UserInput) + val scroll1 = scrollConnection.onPreScroll(available = Offset(0f, 1f), source = UserInput) assertThat(lastScroll).isEqualTo(1f) + assertThat(scroll1.y).isEqualTo(1f) - canContinueScroll = false - scrollConnection.onPreScroll(available = Offset(2f, 2f), source = UserInput) - assertThat(lastScroll).isNotEqualTo(2f) - assertThat(lastScroll).isEqualTo(1f) + consumeScroll = false + val scroll2 = scrollConnection.onPreScroll(available = Offset(0f, 2f), source = UserInput) + assertThat(lastScroll).isEqualTo(2f) + assertThat(scroll2.y).isEqualTo(0f) } @Test - fun step3a_onPriorityMode_shouldStopIfCannotContinue() { + fun step3a_onPriorityMode_shouldCancelIfCannotContinue() { startPriorityModePostScroll() - canContinueScroll = false + consumeScroll = false - scrollConnection.onPreScroll(available = Offset.Zero, source = UserInput) + scrollConnection.onPreScroll(available = Offset(0f, 1f), source = UserInput) - assertThat(lastStop).isNotNull() + assertThat(isCancelled).isTrue() } @Test fun step3b_onPriorityMode_shouldStopOnFling() = runTest { startPriorityModePostScroll() - canContinueScroll = true scrollConnection.onPreFling(available = Velocity.Zero) - assertThat(lastStop).isNotNull() + assertThat(lastStop).isEqualTo(0f) + } + + @Test + fun ifCannotStopOnPreFling_shouldStopOnPostFling() = runTest { + startPriorityModePostScroll() + canStopOnPreFling = false + + scrollConnection.onPreFling(available = Velocity.Zero) + assertThat(lastStop).isNull() + + scrollConnection.onPostFling(consumed = Velocity.Zero, available = Velocity.Zero) + assertThat(lastStop).isEqualTo(0f) } @Test - fun step3c_onPriorityMode_shouldStopOnReset() { + fun step3c_onPriorityMode_shouldCancelOnReset() { startPriorityModePostScroll() - canContinueScroll = true scrollConnection.reset() - assertThat(lastStop).isNotNull() + assertThat(isCancelled).isTrue() } @Test diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt index 22450d32ea62..b3201d0daffe 100644 --- a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt +++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/TestMatchers.kt @@ -25,11 +25,11 @@ fun isElement(element: ElementKey, content: ContentKey? = null): SemanticsMatche return if (content == null) { hasTestTag(element.testTag) } else { - hasTestTag(element.testTag) and inContent(content) + hasTestTag(element.testTag) and SemanticsMatcher.inContent(content) } } /** A [SemanticsMatcher] that matches anything inside [content]. */ -fun inContent(content: ContentKey): SemanticsMatcher { +fun SemanticsMatcher.Companion.inContent(content: ContentKey): SemanticsMatcher { return hasAnyAncestor(hasTestTag(content.testTag)) } diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md index 234c7a032d2e..bf15b4feadfb 100644 --- a/packages/SystemUI/docs/scene.md +++ b/packages/SystemUI/docs/scene.md @@ -63,7 +63,7 @@ the instructions below to turn it on. NOTE: in case these instructions become stale and don't actually enable the framework, please make sure `SceneContainerFlag.isEnabled` in the [`SceneContainerFlag.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt) -file evalutes to `true`. +file evaluates to `true`. 1. Set a collection of **aconfig flags** to `true` by running the following commands: @@ -73,7 +73,6 @@ file evalutes to `true`. $ adb shell device_config override systemui com.android.systemui.migrate_clocks_to_blueprint true $ adb shell device_config override systemui com.android.systemui.notification_avalanche_throttle_hun true $ adb shell device_config override systemui com.android.systemui.predictive_back_sysui true - $ adb shell device_config override systemui com.android.systemui.device_entry_udfps_refactor true $ adb shell device_config override systemui com.android.systemui.scene_container true ``` 2. **Restart** System UI by issuing the following command: diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java index 662815ee7cbe..fcb433b5db4e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegateTest.java @@ -46,7 +46,9 @@ import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableLooper; import android.view.View; +import android.view.ViewGroup; import android.widget.LinearLayout; +import android.widget.Space; import android.widget.Spinner; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -226,7 +228,7 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { mDialog.show(); LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); - assertThat(relatedToolsView.getChildCount()).isEqualTo(1); + assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(1); } @Test @@ -244,12 +246,13 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { when(mActivityInfo.loadLabel(mPackageManager)).thenReturn(TEST_LABEL); when(mActivityInfo.loadIcon(mPackageManager)).thenReturn(mDrawable); when(mActivityInfo.getComponentName()).thenReturn(TEST_COMPONENT); + when(mDrawable.mutate()).thenReturn(mDrawable); setUpPairNewDeviceDialog(); mDialog.show(); LinearLayout relatedToolsView = (LinearLayout) getRelatedToolsView(mDialog); - assertThat(relatedToolsView.getChildCount()).isEqualTo(2); + assertThat(countChildWithoutSpace(relatedToolsView)).isEqualTo(2); } @Test @@ -364,6 +367,16 @@ public class HearingDevicesDialogDelegateTest extends SysuiTestCase { return dialog.requireViewById(R.id.preset_spinner); } + private int countChildWithoutSpace(ViewGroup viewGroup) { + int spaceCount = 0; + for (int i = 0; i < viewGroup.getChildCount(); i++) { + if (viewGroup.getChildAt(i) instanceof Space) { + spaceCount++; + } + } + return viewGroup.getChildCount() - spaceCount; + } + @After public void reset() { if (mDialogDelegate != null) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java index 17ce1ddee87a..77369f7ec881 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParserTest.java @@ -85,7 +85,7 @@ public class HearingDevicesToolItemParserTest extends SysuiTestCase { } @Test - public void parseStringArray_noString_emptyResult() { + public void parseStringArray_noToolName_emptyResult() { assertThat(HearingDevicesToolItemParser.parseStringArray(mContext, new String[]{}, new String[]{})).isEqualTo(emptyList()); } @@ -103,14 +103,14 @@ public class HearingDevicesToolItemParserTest extends SysuiTestCase { } @Test - public void parseStringArray_fourToolName_maxThreeToolItem() { + public void parseStringArray_threeToolNames_maxTwoToolItems() { String componentNameString = TEST_PKG + "/" + TEST_CLS; - String[] fourToolName = - new String[]{componentNameString, componentNameString, componentNameString, - componentNameString}; + String[] threeToolNames = + new String[]{componentNameString, componentNameString, componentNameString}; List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, - fourToolName, new String[]{}); + threeToolNames, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(HearingDevicesToolItemParser.MAX_NUM); } @@ -120,6 +120,7 @@ public class HearingDevicesToolItemParserTest extends SysuiTestCase { List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, wrongFormatToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(0); } @@ -129,6 +130,7 @@ public class HearingDevicesToolItemParserTest extends SysuiTestCase { List<ToolItem> toolItemList = HearingDevicesToolItemParser.parseStringArray(mContext, notExistToolName, new String[]{}); + assertThat(toolItemList.size()).isEqualTo(0); } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt deleted file mode 100644 index 13306becf6d2..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt +++ /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.systemui.biometrics - -import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogManager -import org.junit.Assert.assertFalse -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 - -@SmallTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper -class UdfpsBpViewControllerTest : SysuiTestCase() { - - @JvmField @Rule var rule = MockitoJUnit.rule() - - @Mock lateinit var udfpsBpView: UdfpsBpView - @Mock lateinit var statusBarStateController: StatusBarStateController - @Mock lateinit var shadeInteractor: ShadeInteractor - @Mock lateinit var systemUIDialogManager: SystemUIDialogManager - @Mock lateinit var dumpManager: DumpManager - @Mock lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor - - private lateinit var udfpsBpViewController: UdfpsBpViewController - - @Before - fun setup() { - udfpsBpViewController = - UdfpsBpViewController( - udfpsBpView, - statusBarStateController, - shadeInteractor, - systemUIDialogManager, - dumpManager, - udfpsOverlayInteractor, - ) - } - - @TestableLooper.RunWithLooper(setAsMainLooper = true) - @Test - fun testShouldNeverPauseAuth() { - assertFalse(udfpsBpViewController.shouldPauseAuth()) - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java index 437a4b357372..21c6583d4e84 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java @@ -16,34 +16,17 @@ package com.android.systemui.biometrics; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD; -import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START; -import static android.view.MotionEvent.ACTION_DOWN; -import static android.view.MotionEvent.ACTION_MOVE; -import static android.view.MotionEvent.ACTION_UP; - -import static com.android.internal.util.FunctionalUtils.ThrowingConsumer; -import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION; - -import static junit.framework.Assert.assertFalse; -import static junit.framework.Assert.assertTrue; - import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyBoolean; -import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.graphics.Rect; -import android.hardware.biometrics.BiometricFingerprintConstants; import android.hardware.biometrics.BiometricRequestConstants; import android.hardware.biometrics.ComponentInfoInternal; import android.hardware.biometrics.SensorProperties; @@ -58,13 +41,9 @@ import android.os.Handler; import android.os.PowerManager; import android.os.RemoteException; import android.testing.TestableLooper.RunWithLooper; -import android.util.Pair; -import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; -import android.view.MotionEvent; import android.view.Surface; import android.view.View; -import android.view.ViewGroup; import android.view.ViewRootImpl; import android.view.accessibility.AccessibilityManager; @@ -75,15 +54,11 @@ import com.android.app.viewcapture.ViewCaptureAwareWindowManager; import com.android.internal.logging.InstanceIdSequence; import com.android.internal.util.LatencyTracker; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; -import com.android.systemui.biometrics.udfps.InteractionEvent; -import com.android.systemui.biometrics.udfps.NormalizedTouchData; import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor; -import com.android.systemui.biometrics.udfps.TouchProcessorResult; import com.android.systemui.biometrics.ui.viewmodel.DefaultUdfpsTouchOverlayViewModel; import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlayViewModel; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -102,7 +77,6 @@ import com.android.systemui.power.data.repository.FakePowerRepository; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.power.shared.model.WakeSleepReason; import com.android.systemui.power.shared.model.WakefulnessState; -import com.android.systemui.res.R; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.VibratorHelper; @@ -130,7 +104,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; -import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; @@ -204,16 +177,6 @@ public class UdfpsControllerTest extends SysuiTestCase { private FeatureFlags mFeatureFlags; // Stuff for configuring mocks @Mock - private UdfpsView mUdfpsView; - @Mock - private UdfpsBpView mBpView; - @Mock - private UdfpsFpmEmptyView mFpmEmptyView; - @Mock - private UdfpsKeyguardViewLegacy mKeyguardView; - private final UdfpsAnimationViewController mUdfpsKeyguardViewController = - mock(UdfpsKeyguardViewControllerLegacy.class); - @Mock private SystemUIDialogManager mSystemUIDialogManager; @Mock private ActivityTransitionAnimator mActivityTransitionAnimator; @@ -241,8 +204,6 @@ public class UdfpsControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor<View> mViewCaptor; @Captor - private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor; - @Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor; @Captor private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor; @@ -287,18 +248,9 @@ public class UdfpsControllerTest extends SysuiTestCase { mContext.getOrCreateTestableResources() .addOverride(com.android.internal.R.bool.config_ignoreUdfpsVote, false); - when(mLayoutInflater.inflate(R.layout.udfps_view, null, false)) - .thenReturn(mUdfpsView); - when(mLayoutInflater.inflate(R.layout.udfps_keyguard_view_legacy, null)) - .thenReturn(mKeyguardView); // for showOverlay REASON_AUTH_FPM_KEYGUARD - when(mLayoutInflater.inflate(R.layout.udfps_bp_view, null)) - .thenReturn(mBpView); - when(mLayoutInflater.inflate(R.layout.udfps_fpm_empty_view, null)) - .thenReturn(mFpmEmptyView); when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); when(mSessionTracker.getSessionId(anyInt())).thenReturn( (new InstanceIdSequence(1 << 20)).newInstanceId()); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); final List<ComponentInfoInternal> componentInfo = new ArrayList<>(); componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */, @@ -327,7 +279,6 @@ public class UdfpsControllerTest extends SysuiTestCase { // Create a fake background executor. mBiometricExecutor = new FakeExecutor(new FakeSystemClock()); - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); initUdfpsController(mOpticalProps); } @@ -349,7 +300,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mFalsingManager, mPowerManager, mAccessibilityManager, - mLockscreenShadeTransitionController, mScreenLifecycle, mVibrator, mUdfpsHapticsSimulator, @@ -390,101 +340,6 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void dozeTimeTick() throws RemoteException { - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - mUdfpsController.dozeTimeTick(); - verify(mUdfpsView).dozeTimeTick(); - } - - @Test - public void onActionDownTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException { - // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = obtainMotionEvent(ACTION_DOWN, 0, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN notify keyguard authenticate to dismiss the keyguard - verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); - } - - @Test - public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice() throws RemoteException { - onActionMoveTouch_whenCanDismissLockScreen_entersDevice(false /* stale */); - } - - @Test - public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice_ignoreStale() - throws RemoteException { - onActionMoveTouch_whenCanDismissLockScreen_entersDevice(true /* stale */); - } - - public void onActionMoveTouch_whenCanDismissLockScreen_entersDevice(boolean stale) - throws RemoteException { - // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // WHEN ACTION_MOVE is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - if (stale) { - mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId); - mFgExecutor.runAllReady(); - } - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN notify keyguard authenticate to dismiss the keyguard - verify(mStatusBarKeyguardViewManager, stale ? never() : times(1)) - .notifyKeyguardAuthenticated(anyBoolean()); - } - - @Test - public void onMultipleTouch_whenCanDismissLockScreen_entersDeviceOnce() throws RemoteException { - // GIVEN can dismiss lock screen and the current animation is an UdfpsKeyguardViewController - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController); - - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false); - - // GIVEN that the overlay is showing - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0); - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN notify keyguard authenticate to dismiss the keyguard - verify(mStatusBarKeyguardViewManager).notifyKeyguardAuthenticated(anyBoolean()); - } - - @Test public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException { // GIVEN overlay was showing and the udfps bouncer is showing mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, @@ -524,61 +379,6 @@ public class UdfpsControllerTest extends SysuiTestCase { } @Test - public void updateOverlayParams_recreatesOverlay_ifParamsChanged() throws Exception { - final Rect[] sensorBounds = new Rect[]{new Rect(10, 10, 20, 20), new Rect(5, 5, 25, 25)}; - final int[] displayWidth = new int[]{1080, 1440}; - final int[] displayHeight = new int[]{1920, 2560}; - final float[] scaleFactor = new float[]{1f, displayHeight[1] / (float) displayHeight[0]}; - final int[] rotation = new int[]{Surface.ROTATION_0, Surface.ROTATION_90}; - final UdfpsOverlayParams oldParams = new UdfpsOverlayParams(sensorBounds[0], - sensorBounds[0], displayWidth[0], displayHeight[0], scaleFactor[0], rotation[0], - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); - - for (int i1 = 0; i1 <= 1; ++i1) { - for (int i2 = 0; i2 <= 1; ++i2) { - for (int i3 = 0; i3 <= 1; ++i3) { - for (int i4 = 0; i4 <= 1; ++i4) { - for (int i5 = 0; i5 <= 1; ++i5) { - final UdfpsOverlayParams newParams = new UdfpsOverlayParams( - sensorBounds[i1], sensorBounds[i1], displayWidth[i2], - displayHeight[i3], scaleFactor[i4], rotation[i5], - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL); - - if (newParams.equals(oldParams)) { - continue; - } - - // Initialize the overlay with old parameters. - mUdfpsController.updateOverlayParams(mOpticalProps, oldParams); - - // Show the overlay. - reset(mWindowManager); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, - mOpticalProps.sensorId, - BiometricRequestConstants.REASON_ENROLL_ENROLLING, - mUdfpsOverlayControllerCallback); - - mFgExecutor.runAllReady(); - verify(mWindowManager).addView(mViewCaptor.capture(), any()); - when(mViewCaptor.getValue().getParent()) - .thenReturn(mock(ViewGroup.class)); - - // Update overlay parameters. - reset(mWindowManager); - mUdfpsController.updateOverlayParams(mOpticalProps, newParams); - mFgExecutor.runAllReady(); - - // Ensure the overlay was recreated. - verify(mWindowManager).removeView(any()); - verify(mWindowManager).addView(any(), any()); - } - } - } - } - } - } - - @Test public void updateOverlayParams_doesNothing_ifParamsDidntChange() throws Exception { final Rect sensorBounds = new Rect(10, 10, 20, 20); final int displayWidth = 1080; @@ -595,7 +395,6 @@ public class UdfpsControllerTest extends SysuiTestCase { mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); mFgExecutor.runAllReady(); - verify(mWindowManager).addView(any(), any()); // Update overlay with the same parameters. mUdfpsController.updateOverlayParams(mOpticalProps, @@ -606,870 +405,4 @@ public class UdfpsControllerTest extends SysuiTestCase { // Ensure the overlay was not recreated. verify(mWindowManager, never()).removeView(any()); } - - private static MotionEvent obtainMotionEvent(int action, float x, float y, float minor, - float major) { - MotionEvent.PointerProperties pp = new MotionEvent.PointerProperties(); - pp.id = 1; - MotionEvent.PointerCoords pc = new MotionEvent.PointerCoords(); - pc.x = x; - pc.y = y; - pc.touchMinor = minor; - pc.touchMajor = major; - return MotionEvent.obtain(0, 0, action, 1, new MotionEvent.PointerProperties[]{pp}, - new MotionEvent.PointerCoords[]{pc}, 0, 0, 1f, 1f, 0, 0, 0, 0); - } - - private static class TestParams { - public final FingerprintSensorPropertiesInternal sensorProps; - - TestParams(FingerprintSensorPropertiesInternal sensorProps) { - this.sensorProps = sensorProps; - } - } - - private void runWithAllParams(ThrowingConsumer<TestParams> testParamsConsumer) { - for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps, - mUltrasonicProps)) { - initUdfpsController(sensorProps); - testParamsConsumer.accept(new TestParams(sensorProps)); - } - } - - @Test - public void onTouch_propagatesTouchInNativeOrientationAndResolution() { - runWithAllParams( - this::onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized); - } - - private void onTouch_propagatesTouchInNativeOrientationAndResolutionParameterized( - TestParams testParams) throws RemoteException { - reset(mUdfpsView); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - final Rect sensorBounds = new Rect(1000, 1900, 1080, 1920); // Bottom right corner. - final int pointerId = 0; - final int displayWidth = 1080; - final int displayHeight = 1920; - final float scaleFactor = 1f; // This means the native resolution is 1440x2560. - final float touchMinor = 10f; - final float touchMajor = 20f; - final float orientation = 30f; - - // Expecting a touch at the very bottom right corner in native orientation and resolution. - final float expectedX = displayWidth / scaleFactor; - final float expectedY = displayHeight / scaleFactor; - final float expectedMinor = touchMinor / scaleFactor; - final float expectedMajor = touchMajor / scaleFactor; - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - - // GIVEN a valid touch on sensor - NormalizedTouchData touchData = new NormalizedTouchData(pointerId, displayWidth, - displayHeight, touchMinor, touchMajor, orientation, 0L, 0L); - TouchProcessorResult processorDownResult = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.DOWN, 1, touchData); - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorDownResult); - - // Show the overlay. - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // Test ROTATION_0 - mUdfpsController.updateOverlayParams(testParams.sensorProps, - new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, - scaleFactor, Surface.ROTATION_0, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)); - MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, - touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), - anyBoolean()); - - // Test ROTATION_90 - reset(mFingerprintManager); - mUdfpsController.updateOverlayParams(testParams.sensorProps, - new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, - scaleFactor, Surface.ROTATION_90, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)); - event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), - anyBoolean()); - - // Test ROTATION_270 - reset(mFingerprintManager); - mUdfpsController.updateOverlayParams(testParams.sensorProps, - new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, - scaleFactor, Surface.ROTATION_270, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)); - event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), - anyBoolean()); - - // Test ROTATION_180 - reset(mFingerprintManager); - mUdfpsController.updateOverlayParams(testParams.sensorProps, - new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight, - scaleFactor, Surface.ROTATION_180, - FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)); - // ROTATION_180 is not supported. It should be treated like ROTATION_0. - event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor, touchMajor); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - verify(mFingerprintManager).onPointerDown(eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId), eq(pointerId), eq(expectedX), eq(expectedY), - eq(expectedMinor), eq(expectedMajor), eq(orientation), anyLong(), anyLong(), - anyBoolean()); - } - - @Test - public void fingerDown() { - runWithAllParams(this::fingerDownParameterized); - } - - private void fingerDownParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView, mFingerprintManager, mLatencyTracker, - mKeyguardUpdateMonitor); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.DOWN, 1 /* pointerId */, touchData); - - initUdfpsController(testParams.sensorProps); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - // WHEN ACTION_DOWN is received - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN the touch provider is notified about onPointerDown. - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - - // AND display configuration begins - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); - } else { - verify(mLatencyTracker, never()).onActionStart( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - verify(mUdfpsView, never()).configureDisplay(any()); - } - verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // AND onDisplayConfigured notifies FingerprintManager about onUiReady - mOnDisplayConfiguredCaptor.getValue().run(); - mBiometricExecutor.runAllReady(); - InOrder inOrder = inOrder(mFingerprintManager, mLatencyTracker); - inOrder.verify(mFingerprintManager).onUdfpsUiEvent( - eq(FingerprintManager.UDFPS_UI_READY), eq(TEST_REQUEST_ID), - eq(testParams.sensorProps.sensorId)); - inOrder.verify(mLatencyTracker).onActionEnd( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - } else { - verify(mFingerprintManager, never()).onUdfpsUiEvent( - eq(FingerprintManager.UDFPS_UI_READY), anyLong(), anyInt()); - verify(mLatencyTracker, never()).onActionEnd( - eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE)); - } - } - - @Test - public void aodInterrupt() { - runWithAllParams(this::aodInterruptParameterized); - } - - private void aodInterruptParameterized(TestParams testParams) throws RemoteException { - mUdfpsController.cancelAodSendFingerUpAction(); - reset(mUdfpsView, mFingerprintManager, mKeyguardUpdateMonitor); - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - // GIVEN that the overlay is showing and screen is on and fp is running - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - // WHEN fingerprint is requested because of AOD interrupt - mUdfpsController.onAodInterrupt(0, 0, 2f, 3f); - mFgExecutor.runAllReady(); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // THEN display configuration begins - // AND onDisplayConfigured notifies FingerprintManager about onUiReady - verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture()); - mOnDisplayConfiguredCaptor.getValue().run(); - } else { - verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture()); - } - mBiometricExecutor.runAllReady(); - - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), anyFloat(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), anyBoolean()); - } - - @Test - public void tryAodSendFingerUp_displayConfigurationChanges() { - runWithAllParams(this::cancelAodInterruptParameterized); - } - - private void cancelAodInterruptParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView); - - // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - // WHEN it is cancelled - mUdfpsController.tryAodSendFingerUp(); - // THEN the display is unconfigured - verify(mUdfpsView).unconfigureDisplay(); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - // WHEN it is cancelled - mUdfpsController.tryAodSendFingerUp(); - // THEN the display configuration is unchanged. - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void onFingerUp_displayConfigurationChange() { - runWithAllParams(this::onFingerUp_displayConfigurationParameterized); - } - - private void onFingerUp_displayConfigurationParameterized(TestParams testParams) - throws RemoteException { - reset(mUdfpsView); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // GIVEN AOD interrupt - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - - // WHEN up-action received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - mFgExecutor.runAllReady(); - - // THEN the display is unconfigured - verify(mUdfpsView).unconfigureDisplay(); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - - // WHEN up-action received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - mFgExecutor.runAllReady(); - - // THEN the display configuration is unchanged. - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void onAcquiredGood_displayConfigurationChange() { - runWithAllParams(this::onAcquiredGood_displayConfigurationParameterized); - } - - private void onAcquiredGood_displayConfigurationParameterized(TestParams testParams) - throws RemoteException { - reset(mUdfpsView); - - // GIVEN overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - // WHEN ACQUIRED_GOOD received - mOverlayController.onAcquired(testParams.sensorProps.sensorId, - BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD); - mFgExecutor.runAllReady(); - // THEN the display is unconfigured - verify(mUdfpsView).unconfigureDisplay(); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - // WHEN ACQUIRED_GOOD received - mOverlayController.onAcquired(testParams.sensorProps.sensorId, - BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD); - mFgExecutor.runAllReady(); - // THEN the display configuration is unchanged. - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void aodInterruptTimeout() { - runWithAllParams(this::aodInterruptTimeoutParameterized); - } - - private void aodInterruptTimeoutParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView); - - // GIVEN AOD interrupt - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - mFgExecutor.runAllReady(); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - } - // WHEN it times out - mFgExecutor.advanceClockToNext(); - mFgExecutor.runAllReady(); - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // THEN the display is unconfigured. - verify(mUdfpsView).unconfigureDisplay(); - } else { - // THEN the display configuration is unchanged. - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void aodInterruptCancelTimeoutActionOnFingerUp() { - runWithAllParams(this::aodInterruptCancelTimeoutActionOnFingerUpParameterized); - } - - private void aodInterruptCancelTimeoutActionOnFingerUpParameterized(TestParams testParams) - throws RemoteException { - reset(mUdfpsView); - when(mUdfpsView.getViewRootImpl()).thenReturn(mViewRootImpl); - - // GIVEN AOD interrupt - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - mFgExecutor.runAllReady(); - - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // Configure UdfpsView to accept the ACTION_UP event - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - } - - // WHEN ACTION_UP is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - MotionEvent upEvent = MotionEvent.obtain(0, 0, ACTION_UP, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, upEvent); - mBiometricExecutor.runAllReady(); - upEvent.recycle(); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - mFgExecutor.runAllReady(); - - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // Configure UdfpsView to accept the finger up event - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - } else { - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - } - - // WHEN it times out - mFgExecutor.advanceClockToNext(); - mFgExecutor.runAllReady(); - - if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) { - // THEN the display should be unconfigured once. If the timeout action is not - // cancelled, the display would be unconfigured twice which would cause two - // FP attempts. - verify(mUdfpsView).unconfigureDisplay(); - } else { - verify(mUdfpsView, never()).unconfigureDisplay(); - } - } - - @Test - public void aodInterruptScreenOff() { - runWithAllParams(this::aodInterruptScreenOffParameterized); - } - - private void aodInterruptScreenOffParameterized(TestParams testParams) throws RemoteException { - reset(mUdfpsView); - - // GIVEN screen off - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOff(); - mFgExecutor.runAllReady(); - - // WHEN aod interrupt is received - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - - // THEN display doesn't get configured because it's off - verify(mUdfpsView, never()).configureDisplay(any()); - } - - @Test - public void aodInterrupt_fingerprintNotRunning() { - runWithAllParams(this::aodInterrupt_fingerprintNotRunningParameterized); - } - - private void aodInterrupt_fingerprintNotRunningParameterized(TestParams testParams) - throws RemoteException { - reset(mUdfpsView); - - // GIVEN showing overlay - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mScreenObserver.onScreenTurnedOn(); - mFgExecutor.runAllReady(); - - // WHEN aod interrupt is received when the fingerprint service isn't running - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); - mUdfpsController.onAodInterrupt(0, 0, 0f, 0f); - - // THEN display doesn't get configured because it's off - verify(mUdfpsView, never()).configureDisplay(any()); - } - - @Test - public void playHapticOnTouchUdfpsArea_a11yTouchExplorationEnabled() throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, true); - - // WHEN ACTION_HOVER is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); - MotionEvent enterEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_HOVER_ENTER, 0, 0, 0); - mHoverListenerCaptor.getValue().onHover(mUdfpsView, enterEvent); - enterEvent.recycle(); - - // THEN context click haptic is played - verify(mVibrator).performHapticFeedback( - any(), - eq(HapticFeedbackConstants.CONTEXT_CLICK) - ); - } - - @Test - public void noHapticOnTouchUdfpsArea_a11yTouchExplorationDisabled() throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN NO haptic played - verify(mVibrator, never()).performHapticFeedback(any(), anyInt()); - } - - @Test - public void fingerDown_falsingManagerInformed() throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN falsing manager is informed of the touch - verify(mFalsingManager).isFalseTouch(UDFPS_AUTHENTICATION); - } - - private Pair<TouchProcessorResult, TouchProcessorResult> givenFingerEvent( - InteractionEvent event1, InteractionEvent event2, boolean a11y) - throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - event1, 1 /* pointerId */, touchData); - final TouchProcessorResult processorResultUp = new TouchProcessorResult.ProcessedTouch( - event2, 1 /* pointerId */, touchData); - - // Configure UdfpsController to use FingerprintManager as opposed to AlternateTouchProvider. - initUdfpsController(mOpticalProps); - - // Configure UdfpsView to accept the ACTION_DOWN event - when(mUdfpsView.isDisplayConfigured()).thenReturn(false); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - if (a11y) { - verify(mUdfpsView).setOnHoverListener(mHoverListenerCaptor.capture()); - } else { - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - } - - return new Pair<>(processorResultDown, processorResultUp); - } - - @Test - public void onTouch_forwardToKeyguard() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = new TouchProcessorResult.ProcessedTouch( - InteractionEvent.UNCHANGED, -1 /* pointerOnSensorId */, touchData); - - // GIVEN that the overlay is showing and a11y touch exploration NOT enabled - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - - // THEN the touch is forwarded to Keyguard - verify(mStatusBarKeyguardViewManager).onTouch(downEvent); - } - - @Test - public void onTouch_pilferPointer() throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UNCHANGED, false); - - // WHEN ACTION_DOWN is received - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent event = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - - // WHEN ACTION_MOVE is received after - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.second); - event.setAction(ACTION_MOVE); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, event); - mBiometricExecutor.runAllReady(); - event.recycle(); - - // THEN only pilfer once on the initial down - verify(mInputManager).pilferPointers(any()); - } - - @Test - public void onTouch_doNotPilferPointer() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultUnchanged = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.UNCHANGED, - -1 /* pointerId */, touchData); - - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN ACTION_DOWN is received and touch is not within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultUnchanged); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN the touch is NOT pilfered - verify(mInputManager, never()).pilferPointers(any()); - } - - @Test - public void onDownTouchReceivedWithoutPreviousUp() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - -1 /* pointerId */, touchData); - - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN ACTION_DOWN is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent); - mBiometricExecutor.runAllReady(); - firstDownEvent.recycle(); - - // And another ACTION_DOWN is received without an ACTION_UP before - MotionEvent secondDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, secondDownEvent); - mBiometricExecutor.runAllReady(); - secondDownEvent.recycle(); - - // THEN the touch is still processed - verify(mFingerprintManager, times(2)).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - @Test - public void onAodDownAndDownTouchReceived() throws RemoteException { - final NormalizedTouchData touchData = new NormalizedTouchData(0, 0f, 0f, 0f, 0f, 0f, 0L, - 0L); - final TouchProcessorResult processorResultDown = - new TouchProcessorResult.ProcessedTouch(InteractionEvent.DOWN, - -1 /* pointerId */, touchData); - - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture()); - - // WHEN fingerprint is requested because of AOD interrupt - // GIVEN there's been an AoD interrupt - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - mScreenObserver.onScreenTurnedOn(); - mUdfpsController.onAodInterrupt(0, 0, 0, 0); - mFgExecutor.runAllReady(); - - // and an ACTION_DOWN is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - processorResultDown); - MotionEvent firstDownEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, firstDownEvent); - mBiometricExecutor.runAllReady(); - firstDownEvent.recycle(); - - // THEN the touch is only processed once - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - @Test - public void onTouch_pilferPointerWhenAltBouncerShowing() - throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false); - - // WHEN alternate bouncer is showing - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // WHEN ACTION_DOWN is received and touch is not within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent); - mBiometricExecutor.runAllReady(); - downEvent.recycle(); - - // THEN the touch is pilfered - verify(mInputManager).pilferPointers(any()); - } - - @Test - public void onTouch_doNotProcessTouchWhenPullingUpBouncer() - throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.UNCHANGED, InteractionEvent.UP, false); - - // GIVEN a swipe up to bring up primary bouncer is in progress or swipe down for QS - when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(true); - when(mLockscreenShadeTransitionController.getFractionToShade()).thenReturn(1f); - - // WHEN ACTION_MOVE is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN the touch is NOT processed - verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - - @Test - public void onTouch_qsDrag_processesTouchWhenAlternateBouncerVisible() - throws RemoteException { - final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult = - givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false); - - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - // GIVEN swipe down for QS - when(mPrimaryBouncerInteractor.isInTransit()).thenReturn(false); - when(mLockscreenShadeTransitionController.getQSDragProgress()).thenReturn(1f); - - // WHEN ACTION_MOVE is received and touch is within sensor - when(mSinglePointerTouchProcessor.processTouch(any(), anyInt(), any())).thenReturn( - touchProcessorResult.first); - MotionEvent moveEvent = MotionEvent.obtain(0, 0, ACTION_MOVE, 0, 0, 0); - mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent); - mBiometricExecutor.runAllReady(); - moveEvent.recycle(); - - // THEN the touch is still processed - verify(mFingerprintManager).onPointerDown(anyLong(), anyInt(), anyInt(), - anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyFloat(), anyLong(), anyLong(), - anyBoolean()); - } - - @Test - public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException { - // GIVEN UDFPS overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - // GIVEN there's been an AoD interrupt - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true); - mScreenObserver.onScreenTurnedOn(); - mUdfpsController.onAodInterrupt(0, 0, 0, 0); - - // THEN finger is considered down - assertTrue(mUdfpsController.isFingerDown()); - - // WHEN udfps receives an ACQUIRED_GOOD after the display is configured - when(mUdfpsView.isDisplayConfigured()).thenReturn(true); - verify(mFingerprintManager).setUdfpsOverlayController( - mUdfpsOverlayControllerCaptor.capture()); - mUdfpsOverlayControllerCaptor.getValue().onAcquired(0, FINGERPRINT_ACQUIRED_GOOD); - mFgExecutor.runAllReady(); - - // THEN is fingerDown should be FALSE - assertFalse(mUdfpsController.isFingerDown()); - } - - @Test - public void playHaptic_onAodInterrupt_onAcquiredBad_performHapticFeedback() - throws RemoteException { - // GIVEN UDFPS overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - // GIVEN there's been an AoD interrupt - when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(false); - mScreenObserver.onScreenTurnedOn(); - mUdfpsController.onAodInterrupt(0, 0, 0, 0); - - // THEN vibrate is used - verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); - } - - @Test - public void onAcquiredCalbacks() { - runWithAllParams( - this::ultrasonicCallbackOnAcquired); - } - - public void ultrasonicCallbackOnAcquired(TestParams testParams) throws RemoteException{ - if (testParams.sensorProps.sensorType - == FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC) { - reset(mUdfpsView); - - UdfpsController.Callback callbackMock = mock(UdfpsController.Callback.class); - mUdfpsController.addCallback(callbackMock); - - // GIVEN UDFPS overlay is showing - mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId, - BiometricRequestConstants.REASON_AUTH_KEYGUARD, - mUdfpsOverlayControllerCallback); - mFgExecutor.runAllReady(); - - verify(mFingerprintManager).setUdfpsOverlayController( - mUdfpsOverlayControllerCaptor.capture()); - mUdfpsOverlayControllerCaptor.getValue().onAcquired(0, FINGERPRINT_ACQUIRED_START); - mFgExecutor.runAllReady(); - - verify(callbackMock).onFingerDown(); - - mUdfpsOverlayControllerCaptor.getValue().onAcquired(0, FINGERPRINT_ACQUIRED_GOOD); - mFgExecutor.runAllReady(); - - verify(callbackMock).onFingerUp(); - } - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java deleted file mode 100644 index 7986051de3e0..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java +++ /dev/null @@ -1,161 +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.systemui.biometrics; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; - -import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.systemui.Flags; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.animation.ActivityTransitionAnimator; -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor; -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.keyguard.KeyguardViewMediator; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.shade.ShadeExpansionChangeEvent; -import com.android.systemui.shade.ShadeExpansionStateManager; -import com.android.systemui.shade.domain.interactor.ShadeInteractor; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; -import com.android.systemui.statusbar.phone.SystemUIDialogManager; -import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.user.domain.interactor.SelectedUserInteractor; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import org.junit.Before; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; - -public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase { - // Dependencies - protected @Mock UdfpsKeyguardViewLegacy mView; - protected @Mock Context mResourceContext; - protected @Mock StatusBarStateController mStatusBarStateController; - protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager; - protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; - protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController; - protected @Mock DumpManager mDumpManager; - protected @Mock DelayableExecutor mExecutor; - protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; - protected @Mock KeyguardStateController mKeyguardStateController; - protected @Mock KeyguardViewMediator mKeyguardViewMediator; - protected @Mock ConfigurationController mConfigurationController; - protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController; - protected @Mock SystemUIDialogManager mDialogManager; - protected @Mock UdfpsController mUdfpsController; - protected @Mock ActivityTransitionAnimator mActivityTransitionAnimator; - protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; - protected @Mock ShadeInteractor mShadeInteractor; - protected @Mock AlternateBouncerInteractor mAlternateBouncerInteractor; - protected @Mock UdfpsKeyguardAccessibilityDelegate mUdfpsKeyguardAccessibilityDelegate; - protected @Mock SelectedUserInteractor mSelectedUserInteractor; - protected @Mock KeyguardTransitionInteractor mKeyguardTransitionInteractor; - protected @Mock UdfpsOverlayInteractor mUdfpsOverlayInteractor; - - protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags(); - - protected UdfpsKeyguardViewControllerLegacy mController; - - // Capture listeners so that they can be used to send events - private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor; - protected StatusBarStateController.StateListener mStatusBarStateListener; - - private @Captor ArgumentCaptor<KeyguardStateController.Callback> - mKeyguardStateControllerCallbackCaptor; - protected KeyguardStateController.Callback mKeyguardStateControllerCallback; - - private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.KeyguardViewManagerCallback> - mKeyguardViewManagerCallbackArgumentCaptor; - protected StatusBarKeyguardViewManager.KeyguardViewManagerCallback mKeyguardViewManagerCallback; - - - @Before - public void setUp() { - MockitoAnnotations.initMocks(this); - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); - when(mView.getContext()).thenReturn(mResourceContext); - when(mResourceContext.getString(anyInt())).thenReturn("test string"); - when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false); - when(mView.getUnpausedAlpha()).thenReturn(255); - when(mShadeExpansionStateManager.addExpansionListener(any())).thenReturn( - new ShadeExpansionChangeEvent(0, false, false)); - mController = createUdfpsKeyguardViewController(); - } - - protected void sendStatusBarStateChanged(int statusBarState) { - mStatusBarStateListener.onStateChanged(statusBarState); - } - - protected void captureStatusBarStateListeners() { - verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture()); - mStatusBarStateListener = mStateListenerCaptor.getValue(); - } - - protected void captureKeyguardStateControllerCallback() { - verify(mKeyguardStateController).addCallback( - mKeyguardStateControllerCallbackCaptor.capture()); - mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue(); - } - - public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() { - return createUdfpsKeyguardViewController(false); - } - - public void captureKeyGuardViewManagerCallback() { - verify(mStatusBarKeyguardViewManager).addCallback( - mKeyguardViewManagerCallbackArgumentCaptor.capture()); - mKeyguardViewManagerCallback = mKeyguardViewManagerCallbackArgumentCaptor.getValue(); - } - - protected UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController( - boolean useModernBouncer) { - UdfpsKeyguardViewControllerLegacy controller = new UdfpsKeyguardViewControllerLegacy( - mView, - mStatusBarStateController, - mStatusBarKeyguardViewManager, - mKeyguardUpdateMonitor, - mDumpManager, - mLockscreenShadeTransitionController, - mConfigurationController, - mKeyguardStateController, - mUnlockedScreenOffAnimationController, - mDialogManager, - mUdfpsController, - mActivityTransitionAnimator, - mPrimaryBouncerInteractor, - mAlternateBouncerInteractor, - mUdfpsKeyguardAccessibilityDelegate, - mSelectedUserInteractor, - mKeyguardTransitionInteractor, - mShadeInteractor, - mUdfpsOverlayInteractor); - return controller; - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java deleted file mode 100644 index 98d8b054716c..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java +++ /dev/null @@ -1,197 +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.systemui.biometrics; - -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.testing.TestableLooper; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.statusbar.StatusBarState; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) - -@TestableLooper.RunWithLooper(setAsMainLooper = true) -public class UdfpsKeyguardViewLegacyControllerTest extends - UdfpsKeyguardViewLegacyControllerBaseTest { - @Override - public UdfpsKeyguardViewControllerLegacy createUdfpsKeyguardViewController() { - return createUdfpsKeyguardViewController(/* useModernBouncer */ false); - } - - @Test - public void testShouldPauseAuth_bouncerShowing() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true); - when(mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()).thenReturn(true); - when(mView.getUnpausedAlpha()).thenReturn(0); - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testRegistersStatusBarStateListenersOnAttached() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - } - - @Test - public void testViewControllerQueriesSBStateOnAttached() { - mController.onViewAttached(); - verify(mStatusBarStateController).getState(); - - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE_LOCKED); - captureStatusBarStateListeners(); - - mController.onViewAttached(); - verify(mView, atLeast(1)).setPauseAuth(true); - } - - @Test - public void testListenersUnregisteredOnDetached() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - captureKeyguardStateControllerCallback(); - mController.onViewDetached(); - - verify(mStatusBarStateController).removeCallback(mStatusBarStateListener); - verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback); - } - - @Test - public void testShouldPauseAuthUnpausedAlpha0() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - when(mView.getUnpausedAlpha()).thenReturn(0); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldNotPauseAuthOnKeyguard() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - assertFalse(mController.shouldPauseAuth()); - } - - @Test - public void onBiometricAuthenticated_pauseAuth() { - // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard) - mController.onViewAttached(); - captureStatusBarStateListeners(); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - // WHEN biometric is authenticated - captureKeyguardStateControllerCallback(); - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardStateControllerCallback.onUnlockedChanged(); - - // THEN pause auth - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldPauseAuthIsLaunchTransitionFadingAway() { - // GIVEN view is attached and we're on the keyguard (see testShouldNotPauseAuthOnKeyguard) - mController.onViewAttached(); - captureStatusBarStateListeners(); - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - - // WHEN isLaunchTransitionFadingAway=true - captureKeyguardStateControllerCallback(); - when(mKeyguardStateController.isLaunchTransitionFadingAway()).thenReturn(true); - mKeyguardStateControllerCallback.onLaunchTransitionFadingAwayChanged(); - - // THEN pause auth - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldPauseAuthOnShadeLocked() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); - - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldPauseAuthOnShade() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - // WHEN not on keyguard yet (shade = home) - sendStatusBarStateChanged(StatusBarState.SHADE); - - // THEN pause auth - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testShouldPauseAuthAnimatingScreenOffFromShade() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - // WHEN transitioning from home/shade => keyguard + animating screen off - mStatusBarStateListener.onStatePreChange(StatusBarState.SHADE, StatusBarState.KEYGUARD); - when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true); - - // THEN pause auth - assertTrue(mController.shouldPauseAuth()); - } - - @Test - public void testDoNotPauseAuthAnimatingScreenOffFromLS() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - // WHEN animating screen off transition from LS => AOD - sendStatusBarStateChanged(StatusBarState.KEYGUARD); - when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(true); - - // THEN don't pause auth - assertFalse(mController.shouldPauseAuth()); - } - - @Test - public void testOverrideShouldPauseAuthOnShadeLocked() { - mController.onViewAttached(); - captureStatusBarStateListeners(); - - sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED); - assertTrue(mController.shouldPauseAuth()); - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt deleted file mode 100644 index 29a6e56891af..000000000000 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt +++ /dev/null @@ -1,643 +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.biometrics - -import android.testing.TestableLooper -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -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.domain.interactor.udfpsOverlayInteractor -import com.android.systemui.bouncer.data.repository.fakeKeyguardBouncerRepository -import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor -import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository -import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor -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.kosmos.testScope -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.testKosmos -import com.android.systemui.util.mockito.whenever -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -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.Mockito -import org.mockito.Mockito.never -import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations - -@RunWith(AndroidJUnit4::class) -@SmallTest -@TestableLooper.RunWithLooper -@kotlinx.coroutines.ExperimentalCoroutinesApi -class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest : - UdfpsKeyguardViewLegacyControllerBaseTest() { - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - private val keyguardBouncerRepository = kosmos.fakeKeyguardBouncerRepository - private val transitionRepository = kosmos.fakeKeyguardTransitionRepository - - @Before - override fun setUp() { - allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread - MockitoAnnotations.initMocks(this) - super.setUp() - } - - override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy { - mPrimaryBouncerInteractor = kosmos.primaryBouncerInteractor - mAlternateBouncerInteractor = kosmos.alternateBouncerInteractor - mKeyguardTransitionInteractor = kosmos.keyguardTransitionInteractor - mUdfpsOverlayInteractor = kosmos.udfpsOverlayInteractor - return createUdfpsKeyguardViewController(/* useModernBouncer */ true) - } - - @Test - fun bouncerExpansionChange_fadeIn() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - captureKeyguardStateControllerCallback() - Mockito.reset(mView) - - // WHEN status bar expansion is 0 - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - // THEN alpha is 0 - verify(mView).unpausedAlpha = 0 - - job.cancel() - } - - @Test - fun bouncerExpansionChange_pauseAuth() = - testScope.runTest { - // GIVEN view is attached + on the keyguard - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - Mockito.reset(mView) - - // WHEN panelViewExpansion changes to hide - whenever(mView.unpausedAlpha).thenReturn(0) - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - // THEN pause auth is updated to PAUSE - verify(mView, Mockito.atLeastOnce()).setPauseAuth(true) - - job.cancel() - } - - @Test - fun bouncerExpansionChange_unpauseAuth() = - testScope.runTest { - // GIVEN view is attached + on the keyguard + panel expansion is 0f - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - Mockito.reset(mView) - - // WHEN panelViewExpansion changes to expanded - whenever(mView.unpausedAlpha).thenReturn(255) - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN) - runCurrent() - - // THEN pause auth is updated to NOT pause - verify(mView, Mockito.atLeastOnce()).setPauseAuth(false) - - job.cancel() - } - - @Test - fun shadeLocked_showAlternateBouncer_unpauseAuth() = - testScope.runTest { - // GIVEN view is attached + on the SHADE_LOCKED (udfps view not showing) - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.SHADE_LOCKED) - - // WHEN alternate bouncer is requested - val job = mController.listenForAlternateBouncerVisibility(this) - keyguardBouncerRepository.setAlternateVisible(true) - runCurrent() - - // THEN udfps view will animate in & pause auth is updated to NOT pause - verify(mView).animateInUdfpsBouncer(any()) - assertFalse(mController.shouldPauseAuth()) - - job.cancel() - } - - /** After migration to MODERN_BOUNCER, replaces UdfpsKeyguardViewControllerTest version */ - @Test - fun shouldPauseAuthBouncerShowing() = - testScope.runTest { - // GIVEN view attached and we're on the keyguard - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - - // WHEN the bouncer expansion is VISIBLE - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - // THEN UDFPS shouldPauseAuth == true - assertTrue(mController.shouldPauseAuth()) - - job.cancel() - } - - @Test - fun shouldHandleTouchesChange() = - testScope.runTest { - val shouldHandleTouches by collectLastValue(mUdfpsOverlayInteractor.shouldHandleTouches) - - // GIVEN view is attached + on the keyguard - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - whenever(mView.setPauseAuth(true)).thenReturn(true) - whenever(mView.unpausedAlpha).thenReturn(0) - - // WHEN panelViewExpansion changes to expanded - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - // THEN UDFPS auth is paused and should not handle touches - assertThat(mController.shouldPauseAuth()).isTrue() - assertThat(shouldHandleTouches!!).isFalse() - - job.cancel() - } - - @Test - fun shouldHandleTouchesOnDetach() = - testScope.runTest { - val shouldHandleTouches by collectLastValue(mUdfpsOverlayInteractor.shouldHandleTouches) - - // GIVEN view is attached + on the keyguard - mController.onViewAttached() - captureStatusBarStateListeners() - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - whenever(mView.setPauseAuth(true)).thenReturn(true) - whenever(mView.unpausedAlpha).thenReturn(0) - - // WHEN panelViewExpansion changes to expanded - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE) - runCurrent() - - mController.onViewDetached() - - // THEN UDFPS auth is paused and should not handle touches - assertThat(mController.shouldPauseAuth()).isTrue() - assertThat(shouldHandleTouches!!).isFalse() - - job.cancel() - } - - @Test - fun fadeFromDialogSuggestedAlpha() = - testScope.runTest { - // GIVEN view is attached and status bar expansion is 1f - mController.onViewAttached() - captureStatusBarStateListeners() - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN) - runCurrent() - Mockito.reset(mView) - - // WHEN dialog suggested alpha is .6f - whenever(mView.dialogSuggestedAlpha).thenReturn(.6f) - sendStatusBarStateChanged(StatusBarState.KEYGUARD) - - // THEN alpha is updated based on dialog suggested alpha - verify(mView).unpausedAlpha = (.6f * 255).toInt() - - job.cancel() - } - - @Test - fun transitionToFullShadeProgress() = - testScope.runTest { - // GIVEN view is attached and status bar expansion is 1f - mController.onViewAttached() - val job = mController.listenForBouncerExpansion(this) - keyguardBouncerRepository.setPrimaryShow(true) - keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN) - runCurrent() - Mockito.reset(mView) - whenever(mView.dialogSuggestedAlpha).thenReturn(1f) - - // WHEN we're transitioning to the full shade - val transitionProgress = .6f - mController.setTransitionToFullShadeProgress(transitionProgress) - - // THEN alpha is between 0 and 255 - verify(mView).unpausedAlpha = ((1f - transitionProgress) * 255).toInt() - - job.cancel() - } - - @Test - fun aodToLockscreen_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForLockscreenAodTransitions(this) - - // WHEN transitioning from lockscreen to aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(.3f), - eq(.3f), - eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(1f), - eq(1f), - eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) - ) - - job.cancel() - } - - @Test - fun lockscreenToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForLockscreenAodTransitions(this) - - // WHEN transitioning from lockscreen to aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(.3f), - eq(.3f), - eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(1f), - eq(1f), - eq(UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN) - ) - - job.cancel() - } - - @Test - fun goneToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForGoneToAodTransition(this) - - // WHEN transitioning from lockscreen to aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(.3f), - eq(.3f), - eq(UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF) - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(1f), - eq(1f), - eq(UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF) - ) - - job.cancel() - } - - @Test - fun aodToOccluded_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForAodToOccludedTransitions(this) - - // WHEN transitioning from aod to occluded - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.OCCLUDED, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged(eq(.7f), eq(.7f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE)) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.OCCLUDED, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged(eq(0f), eq(0f), eq(UdfpsKeyguardViewLegacy.ANIMATION_NONE)) - - job.cancel() - } - - @Test - fun occludedToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForOccludedToAodTransition(this) - - // WHEN transitioning from occluded to aod - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.AOD, - value = .3f, - transitionState = TransitionState.RUNNING - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(.3f), - eq(.3f), - eq(UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF) - ) - - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.OCCLUDED, - to = KeyguardState.AOD, - value = 1f, - transitionState = TransitionState.FINISHED - ) - ) - runCurrent() - // THEN doze amount is updated - verify(mView) - .onDozeAmountChanged( - eq(1f), - eq(1f), - eq(UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF) - ) - - job.cancel() - } - - @Test - fun cancelledAodToLockscreen_dozeAmountChangedToZero() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - val job = mController.listenForLockscreenAodTransitions(this) - runCurrent() - Mockito.reset(mView) - - // WHEN aod to lockscreen transition is cancelled - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.AOD, - to = KeyguardState.LOCKSCREEN, - value = 1f, - transitionState = TransitionState.CANCELED - ) - ) - runCurrent() - // ... and WHEN the next transition is from lockscreen => occluded - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.LOCKSCREEN, - to = KeyguardState.OCCLUDED, - value = .4f, - transitionState = TransitionState.STARTED - ) - ) - runCurrent() - - // THEN doze amount is updated to zero - verify(mView) - .onDozeAmountChanged(eq(0f), eq(0f), eq(ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN)) - job.cancel() - } - - @Test - fun cancelledLockscreenToAod_dozeAmountNotUpdatedToZero() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - val job = mController.listenForLockscreenAodTransitions(this) - runCurrent() - Mockito.reset(mView) - - // 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() - } - - @Test - fun bouncerToAod_dozeAmountChanged() = - testScope.runTest { - // GIVEN view is attached - mController.onViewAttached() - Mockito.reset(mView) - - val job = mController.listenForPrimaryBouncerToAodTransitions(this) - // WHEN alternate bouncer to aod transition in progress - transitionRepository.sendTransitionStep( - TransitionStep( - from = KeyguardState.PRIMARY_BOUNCER, - 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() - } -} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt index 68cfa28dabd7..82ff61795e98 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorTest.kt @@ -16,12 +16,9 @@ package com.android.systemui.bouncer.domain.interactor -import android.platform.test.annotations.DisableFlags -import android.platform.test.annotations.EnableFlags import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.keyguardUpdateMonitor -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository import com.android.systemui.authentication.domain.interactor.authenticationInteractor @@ -61,12 +58,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { underTest = kosmos.alternateBouncerInteractor } - @Test(expected = IllegalStateException::class) - @EnableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun enableUdfpsRefactor_deprecatedShowMethod_throwsIllegalStateException() { - underTest.show() - } - @Test @DisableSceneContainer fun canShowAlternateBouncer_false_dueToTransitionState() = @@ -101,21 +92,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun canShowAlternateBouncerForFingerprint_givenCanShow() { - givenCanShowAlternateBouncer() - assertTrue(underTest.canShowAlternateBouncerForFingerprint()) - } - - @Test - fun canShowAlternateBouncerForFingerprint_alternateBouncerUIUnavailable() { - givenCanShowAlternateBouncer() - kosmos.keyguardBouncerRepository.setAlternateBouncerUIAvailable(false) - - assertFalse(underTest.canShowAlternateBouncerForFingerprint()) - } - - @Test fun canShowAlternateBouncerForFingerprint_ifFingerprintIsNotUsuallyAllowed() { givenCanShowAlternateBouncer() kosmos.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) @@ -140,15 +116,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun show_whenCanShow() { - givenCanShowAlternateBouncer() - - assertTrue(underTest.show()) - assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerVisible.value) - } - - @Test fun canShowAlternateBouncerForFingerprint_butCanDismissLockScreen() { givenCanShowAlternateBouncer() whenever(kosmos.keyguardStateController.isUnlocked).thenReturn(true) @@ -165,15 +132,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @Test - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun show_whenCannotShow() { - givenCannotShowAlternateBouncer() - - assertFalse(underTest.show()) - assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerVisible.value) - } - - @Test fun hide_wasPreviouslyShowing() { kosmos.keyguardBouncerRepository.setAlternateVisible(true) @@ -190,7 +148,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { } @Test - @EnableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) fun canShowAlternateBouncerForFingerprint_rearFps() { givenCanShowAlternateBouncer() kosmos.fingerprintPropertyRepository.supportsRearFps() // does not support alternate bouncer @@ -198,29 +155,6 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { assertFalse(underTest.canShowAlternateBouncerForFingerprint()) } - @Test - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - fun alternateBouncerUiAvailable_fromMultipleSources() { - assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value) - - // GIVEN there are two different sources indicating the alternate bouncer is available - underTest.setAlternateBouncerUIAvailable(true, "source1") - underTest.setAlternateBouncerUIAvailable(true, "source2") - assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value) - - // WHEN one of the sources no longer says the UI is available - underTest.setAlternateBouncerUIAvailable(false, "source1") - - // THEN alternate bouncer UI is still available (from the other source) - assertTrue(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value) - - // WHEN all sources say the UI is not available - underTest.setAlternateBouncerUIAvailable(false, "source2") - - // THEN alternate boucer UI is not available - assertFalse(kosmos.keyguardBouncerRepository.alternateBouncerUIAvailable.value) - } - private fun givenAlternateBouncerSupported() { kosmos.givenAlternateBouncerSupported() } @@ -228,8 +162,4 @@ class AlternateBouncerInteractorTest : SysuiTestCase() { private fun givenCanShowAlternateBouncer() { kosmos.givenCanShowAlternateBouncer() } - - private fun givenCannotShowAlternateBouncer() { - kosmos.givenCannotShowAlternateBouncer() - } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt index ee65fbd810ae..1e8651683ec1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal +import android.os.UserHandle import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.provider.Settings @@ -77,7 +78,11 @@ class CommunalSceneStartableTest : SysuiTestCase() { @Before fun setUp() { with(kosmos) { - fakeSettings.putInt(Settings.System.SCREEN_OFF_TIMEOUT, SCREEN_TIMEOUT) + fakeSettings.putIntForUser( + Settings.System.SCREEN_OFF_TIMEOUT, + SCREEN_TIMEOUT, + UserHandle.USER_CURRENT, + ) kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) underTest = diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt index faaa4c415d28..1dd8ca9221a4 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/window/MultiDisplayStatusBarWindowControllerStoreTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/display/data/repository/PerDisplayStoreImplTest.kt @@ -14,23 +14,15 @@ * limitations under the License. */ -package com.android.systemui.statusbar.window +package com.android.systemui.display.data.repository -import android.platform.test.annotations.EnableFlags import android.view.Display -import android.view.WindowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.app.viewcapture.ViewCaptureAwareWindowManager -import com.android.app.viewcapture.mockViewCaptureAwareWindowManager import com.android.systemui.SysuiTestCase -import com.android.systemui.display.data.repository.displayRepository -import com.android.systemui.display.data.repository.fakeDisplayWindowPropertiesRepository -import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.kosmos.unconfinedTestDispatcher -import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.testKosmos import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.runBlocking @@ -43,28 +35,13 @@ import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) @SmallTest -@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) -class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() { +class PerDisplayStoreImplTest : SysuiTestCase() { private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher } private val testScope = kosmos.testScope private val fakeDisplayRepository = kosmos.displayRepository - private val store = - MultiDisplayStatusBarWindowControllerStore( - backgroundApplicationScope = kosmos.applicationCoroutineScope, - controllerFactory = kosmos.fakeStatusBarWindowControllerFactory, - displayWindowPropertiesRepository = kosmos.fakeDisplayWindowPropertiesRepository, - viewCaptureAwareWindowManagerFactory = - object : ViewCaptureAwareWindowManager.Factory { - override fun create( - windowManager: WindowManager - ): ViewCaptureAwareWindowManager { - return kosmos.mockViewCaptureAwareWindowManager - } - }, - displayRepository = fakeDisplayRepository, - ) + private val store = kosmos.fakePerDisplayStore @Before fun start() { @@ -80,34 +57,52 @@ class MultiDisplayStatusBarWindowControllerStoreTest : SysuiTestCase() { @Test fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() = testScope.runTest { - val controller = store.defaultDisplay + val instance = store.defaultDisplay - assertThat(store.defaultDisplay).isSameInstanceAs(controller) + assertThat(store.defaultDisplay).isSameInstanceAs(instance) } @Test fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() = testScope.runTest { - val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID) - assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller) + assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(instance) } @Test fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() = testScope.runTest { - val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID) fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) fakeDisplayRepository.addDisplay(createDisplay(NON_DEFAULT_DISPLAY_ID)) - assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller) + assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(instance) } @Test(expected = IllegalArgumentException::class) fun forDisplay_nonExistingDisplayId_throws() = testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) } + @Test + fun forDisplay_afterDisplayRemoved_onDisplayRemovalActionInvoked() = + testScope.runTest { + val instance = store.forDisplay(NON_DEFAULT_DISPLAY_ID) + + fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) + + assertThat(store.removalActions).containsExactly(instance) + } + + @Test + fun forDisplay_withoutDisplayRemoval_onDisplayRemovalActionIsNotInvoked() = + testScope.runTest { + store.forDisplay(NON_DEFAULT_DISPLAY_ID) + + assertThat(store.removalActions).isEmpty() + } + private fun createDisplay(displayId: Int): Display = mock { on { getDisplayId() } doReturn displayId } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java index fad52e090c69..ba43a236b8f9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/doze/DozeSuppressorTest.java @@ -38,9 +38,9 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.hardware.display.AmbientDisplayConfiguration; -import android.testing.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt index 2a2a82d53671..b5ea305544ff 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt @@ -39,7 +39,6 @@ import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsReposi import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.FakeTrustRepository import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -100,18 +99,14 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { FakeTrustRepository(), testScope.backgroundScope, mSelectedUserInteractor, - faceAuthInteractor + faceAuthInteractor, ) alternateBouncerInteractor = AlternateBouncerInteractor( - mock(StatusBarStateController::class.java), - mock(KeyguardStateController::class.java), bouncerRepository, FakeFingerprintPropertyRepository(), - biometricSettingsRepository, FakeSystemClock(), - keyguardUpdateMonitor, { mock(DeviceEntryBiometricsAllowedInteractor::class.java) }, { mock(KeyguardInteractor::class.java) }, { mock(KeyguardTransitionInteractor::class.java) }, @@ -121,13 +116,12 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { underTest = DeviceEntrySideFpsOverlayInteractor( - testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, - keyguardUpdateMonitor + keyguardUpdateMonitor, ) } @@ -142,7 +136,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isTrue() } @@ -158,7 +152,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = false, isAnimatingAway = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -169,13 +163,12 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { testScope.runTest { underTest = DeviceEntrySideFpsOverlayInteractor( - testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, - keyguardUpdateMonitor + keyguardUpdateMonitor, ) val showIndicatorForDeviceEntry by @@ -185,7 +178,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { updateBouncerScene( isActive = true, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isTrue() } @@ -196,13 +189,12 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { testScope.runTest { underTest = DeviceEntrySideFpsOverlayInteractor( - testScope.backgroundScope, mContext, deviceEntryFingerprintAuthRepository, kosmos.sceneInteractor, primaryBouncerInteractor, alternateBouncerInteractor, - keyguardUpdateMonitor + keyguardUpdateMonitor, ) val showIndicatorForDeviceEntry by @@ -212,7 +204,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { updateBouncerScene( isActive = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -228,7 +220,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = false, fpsDetectionRunning = false, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -245,7 +237,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = false + isUnlockingWithFpAllowed = false, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -261,7 +253,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { updateBouncerScene( isActive = true, fpsDetectionRunning = false, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -277,7 +269,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { updateBouncerScene( isActive = true, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = false + isUnlockingWithFpAllowed = false, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -294,7 +286,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = true, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) assertThat(showIndicatorForDeviceEntry).isFalse() } @@ -325,7 +317,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { isShowing = true, isAnimatingAway = false, fpsDetectionRunning = true, - isUnlockingWithFpAllowed = true + isUnlockingWithFpAllowed = true, ) // Another request to show indicator for deviceEntryFingerprintAuthRepository update @@ -355,7 +347,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { .thenReturn(isUnlockingWithFpAllowed) mContext.orCreateTestableResources.addOverride( R.bool.config_show_sidefps_hint_on_bouncer, - true + true, ) } @@ -366,7 +358,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { ) { kosmos.sceneInteractor.changeScene( if (isActive) Scenes.Bouncer else Scenes.Lockscreen, - "reason" + "reason", ) whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning) @@ -375,7 +367,7 @@ class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() { .thenReturn(isUnlockingWithFpAllowed) mContext.orCreateTestableResources.addOverride( R.bool.config_show_sidefps_hint_on_bouncer, - true + true, ) runCurrent() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt index c4eabd84e031..380060865282 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinderTest.kt @@ -16,7 +16,6 @@ package com.android.systemui.keyguard.ui.binder -import android.platform.test.annotations.EnableFlags import android.testing.TestableLooper import android.view.View import android.view.layoutInflater @@ -24,7 +23,6 @@ import android.view.mockedLayoutInflater import android.view.windowManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR import com.android.systemui.SysuiTestCase import com.android.systemui.bouncer.domain.interactor.givenCanShowAlternateBouncer import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository @@ -63,7 +61,7 @@ class AlternateBouncerViewBinderTest : SysuiTestCase() { kosmos.mockedLayoutInflater.inflate( eq(R.layout.alternate_bouncer), isNull(), - anyBoolean() + anyBoolean(), ) ) .thenReturn(mockedAltBouncerView) @@ -71,7 +69,6 @@ class AlternateBouncerViewBinderTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) fun addViewToWindowManager() { testScope.runTest { kosmos.givenCanShowAlternateBouncer() @@ -85,7 +82,6 @@ class AlternateBouncerViewBinderTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) fun viewRemovedImmediatelyIfAlreadyAttachedToWindow() { testScope.runTest { kosmos.givenCanShowAlternateBouncer() @@ -107,7 +103,6 @@ class AlternateBouncerViewBinderTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) fun viewNotRemovedUntilAttachedToWindow() { testScope.runTest { kosmos.givenCanShowAlternateBouncer() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt index c1bd37811787..5d9548057bae 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerWindowViewModelTest.kt @@ -19,7 +19,6 @@ package com.android.systemui.keyguard.ui.viewmodel 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.biometrics.data.repository.fakeFingerprintPropertyRepository import com.android.systemui.coroutines.collectLastValue @@ -34,7 +33,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith -import org.junit.runners.JUnit4 @ExperimentalCoroutinesApi @RunWith(AndroidJUnit4::class) @@ -49,7 +47,6 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { @Test fun alternateBouncerTransition_alternateBouncerWindowRequiredTrue() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) fingerprintPropertyRepository.supportsUdfps() @@ -64,28 +61,7 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { assertThat(alternateBouncerWindowRequired).isTrue() transitionRepository.sendTransitionSteps( - listOf( - stepFromAlternateBouncer(1.0f, TransitionState.FINISHED), - ), - testScope, - ) - assertThat(alternateBouncerWindowRequired).isFalse() - } - - @Test - fun deviceEntryUdfpsFlagDisabled_alternateBouncerWindowRequiredFalse() = - testScope.runTest { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - val alternateBouncerWindowRequired by - collectLastValue(underTest.alternateBouncerWindowRequired) - fingerprintPropertyRepository.supportsUdfps() - transitionRepository.sendTransitionSteps( - listOf( - stepFromAlternateBouncer(0f, TransitionState.STARTED), - stepFromAlternateBouncer(.4f), - stepFromAlternateBouncer(.6f), - stepFromAlternateBouncer(1f), - ), + listOf(stepFromAlternateBouncer(1.0f, TransitionState.FINISHED)), testScope, ) assertThat(alternateBouncerWindowRequired).isFalse() @@ -94,7 +70,6 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { @Test fun lockscreenTransition_alternateBouncerWindowRequiredFalse() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) fingerprintPropertyRepository.supportsUdfps() @@ -113,7 +88,6 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { @Test fun rearFps_alternateBouncerWindowRequiredFalse() = testScope.runTest { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) val alternateBouncerWindowRequired by collectLastValue(underTest.alternateBouncerWindowRequired) fingerprintPropertyRepository.supportsRearFps() @@ -131,7 +105,7 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { private fun stepFromAlternateBouncer( value: Float, - state: TransitionState = TransitionState.RUNNING + state: TransitionState = TransitionState.RUNNING, ): TransitionStep { return step( from = KeyguardState.ALTERNATE_BOUNCER, @@ -143,7 +117,7 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { private fun stepFromDozingToLockscreen( value: Float, - state: TransitionState = TransitionState.RUNNING + state: TransitionState = TransitionState.RUNNING, ): TransitionStep { return step( from = KeyguardState.DOZING, @@ -157,14 +131,14 @@ class AlternateBouncerWindowViewModelTest : SysuiTestCase() { from: KeyguardState, to: KeyguardState, value: Float, - transitionState: TransitionState + transitionState: TransitionState, ): TransitionStep { return TransitionStep( from = from, to = to, value = value, transitionState = transitionState, - ownerName = "AlternateBouncerViewModelTest" + ownerName = "AlternateBouncerViewModelTest", ) } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt index 14d60943149f..e5bdc2e56b11 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepositoryTest.kt @@ -54,7 +54,7 @@ class PaginatedGridRepositoryTest : SysuiTestCase() { private fun setRowsInConfig(rows: Int) = with(kosmos) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_settings_max_rows, + R.integer.quick_settings_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt index ae6f576bcf3e..cda3d488cb1e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepositoryTest.kt @@ -54,7 +54,7 @@ class QuickQuickSettingsRowRepositoryTest : SysuiTestCase() { private fun setRowsInConfig(rows: Int) = with(kosmos) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_qs_panel_max_rows, + R.integer.quick_qs_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt index a1c0ef2789d5..2c894f9aa20f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/viewmodel/QuickQuickSettingsViewModelTest.kt @@ -151,7 +151,7 @@ class QuickQuickSettingsViewModelTest : SysuiTestCase() { private fun Kosmos.setRows(rows: Int) { testCase.context.orCreateTestableResources.addOverride( - R.integer.quick_qs_panel_max_rows, + R.integer.quick_qs_paginated_grid_num_rows, rows, ) fakeConfigurationRepository.onConfigurationChange() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java index 7798f46fdb46..b23262a87e2e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java @@ -34,9 +34,9 @@ import android.graphics.drawable.Drawable; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.service.quicksettings.Tile; -import android.testing.UiThreadTest; import android.widget.ImageView; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt new file mode 100644 index 000000000000..cdf6bda91301 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapperTest.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2024 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.hearingdevices.domain + +import android.graphics.drawable.TestStubDrawable +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.hearingdevices.domain.model.HearingDevicesTileModel +import com.android.systemui.qs.tiles.impl.hearingdevices.qsHearingDevicesTileConfig +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 HearingDevicesTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val qsTileConfig = kosmos.qsHearingDevicesTileConfig + private val mapper by lazy { + HearingDevicesTileMapper( + context.orCreateTestableResources + .apply { addOverride(R.drawable.qs_hearing_devices_icon, TestStubDrawable()) } + .resources, + context.theme, + ) + } + + @Test + fun map_anyActiveHearingDevice_anyPairedHearingDevice_activeState() { + val tileState: QSTileState = + mapper.map( + qsTileConfig, + HearingDevicesTileModel( + isAnyActiveHearingDevice = true, + isAnyPairedHearingDevice = true, + ), + ) + val expectedState = + createHearingDevicesTileState( + QSTileState.ActivationState.ACTIVE, + context.getString(R.string.quick_settings_hearing_devices_connected), + ) + QSTileStateSubject.assertThat(tileState).isEqualTo(expectedState) + } + + @Test + fun map_noActiveHearingDevice_anyPairedHearingDevice_inactiveState() { + val tileState: QSTileState = + mapper.map( + qsTileConfig, + HearingDevicesTileModel( + isAnyActiveHearingDevice = false, + isAnyPairedHearingDevice = true, + ), + ) + val expectedState = + createHearingDevicesTileState( + QSTileState.ActivationState.INACTIVE, + context.getString(R.string.quick_settings_hearing_devices_disconnected), + ) + QSTileStateSubject.assertThat(tileState).isEqualTo(expectedState) + } + + @Test + fun map_noActiveHearingDevice_noPairedHearingDevice_inactiveState() { + val tileState: QSTileState = + mapper.map( + qsTileConfig, + HearingDevicesTileModel( + isAnyActiveHearingDevice = false, + isAnyPairedHearingDevice = false, + ), + ) + val expectedState = + createHearingDevicesTileState(QSTileState.ActivationState.INACTIVE, secondaryLabel = "") + QSTileStateSubject.assertThat(tileState).isEqualTo(expectedState) + } + + private fun createHearingDevicesTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String, + ): QSTileState { + val label = context.getString(R.string.quick_settings_hearing_devices_label) + val iconRes = R.drawable.qs_hearing_devices_icon + return QSTileState( + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + iconRes, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + label, + null, + QSTileState.SideViewIcon.Chevron, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName, + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt new file mode 100644 index 000000000000..1dfa2cd26491 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractorTest.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2024 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.hearingdevices.domain.interactor + +import android.os.UserHandle +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.annotations.EnabledOnRavenwood +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.accessibility.hearingaid.HearingDevicesChecker +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.coroutines.collectValues +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel +import com.android.systemui.statusbar.policy.fakeBluetoothController +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +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 +import org.mockito.kotlin.whenever + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class HearingDevicesTileDataInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val testUser = UserHandle.of(1) + + private val controller = kosmos.fakeBluetoothController + private lateinit var underTest: HearingDevicesTileDataInteractor + + @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var checker: HearingDevicesChecker + + @Before + fun setup() { + underTest = HearingDevicesTileDataInteractor(testScope.testScheduler, controller, checker) + } + + @EnableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG) + @Test + fun availability_flagEnabled_returnTrue() = + testScope.runTest { + val availability by collectLastValue(underTest.availability(testUser)) + + assertThat(availability).isTrue() + } + + @DisableFlags(Flags.FLAG_HEARING_AIDS_QS_TILE_DIALOG) + @Test + fun availability_flagDisabled_returnFalse() = + testScope.runTest { + val availability by collectLastValue(underTest.availability(testUser)) + + assertThat(availability).isFalse() + } + + @Test + fun tileData_bluetoothStateChanged_dataMatchesChecker() = + testScope.runTest { + val flowValues: List<HearingDevicesTileModel> by + collectValues( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + assertThat(flowValues.size).isEqualTo(1) // from addCallback in setup() + + whenever(checker.isAnyPairedHearingDevice).thenReturn(false) + whenever(checker.isAnyActiveHearingDevice).thenReturn(false) + controller.isBluetoothEnabled = false + runCurrent() + assertThat(flowValues.size).isEqualTo(1) // model unchanged, no new flow value + + whenever(checker.isAnyPairedHearingDevice).thenReturn(true) + whenever(checker.isAnyActiveHearingDevice).thenReturn(false) + controller.isBluetoothEnabled = true + runCurrent() + assertThat(flowValues.size).isEqualTo(2) + + whenever(checker.isAnyPairedHearingDevice).thenReturn(true) + whenever(checker.isAnyActiveHearingDevice).thenReturn(true) + controller.isBluetoothEnabled = true + runCurrent() + assertThat(flowValues.size).isEqualTo(3) + + assertThat(flowValues.map { it.isAnyPairedHearingDevice }) + .containsExactly(false, true, true) + .inOrder() + assertThat(flowValues.map { it.isAnyActiveHearingDevice }) + .containsExactly(false, false, true) + .inOrder() + } + + @Test + fun tileData_bluetoothDeviceChanged_dataMatchesChecker() = + testScope.runTest { + val flowValues: List<HearingDevicesTileModel> by + collectValues( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + assertThat(flowValues.size).isEqualTo(1) // from addCallback in setup() + + whenever(checker.isAnyPairedHearingDevice).thenReturn(false) + whenever(checker.isAnyActiveHearingDevice).thenReturn(false) + controller.onBluetoothDevicesChanged() + runCurrent() + assertThat(flowValues.size).isEqualTo(1) // model unchanged, no new flow value + + whenever(checker.isAnyPairedHearingDevice).thenReturn(true) + whenever(checker.isAnyActiveHearingDevice).thenReturn(false) + controller.onBluetoothDevicesChanged() + runCurrent() + assertThat(flowValues.size).isEqualTo(2) + + whenever(checker.isAnyPairedHearingDevice).thenReturn(true) + whenever(checker.isAnyActiveHearingDevice).thenReturn(true) + controller.onBluetoothDevicesChanged() + runCurrent() + assertThat(flowValues.size).isEqualTo(3) + + assertThat(flowValues.map { it.isAnyPairedHearingDevice }) + .containsExactly(false, true, true) + .inOrder() + assertThat(flowValues.map { it.isAnyActiveHearingDevice }) + .containsExactly(false, false, true) + .inOrder() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..00ee1c36590c --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractorTest.kt @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2024 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.hearingdevices.domain.interactor + +import android.platform.test.annotations.EnabledOnRavenwood +import android.provider.Settings +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager +import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler +import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject +import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx +import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.junit.MockitoJUnit +import org.mockito.junit.MockitoRule +import org.mockito.kotlin.anyOrNull +import org.mockito.kotlin.verify + +@SmallTest +@EnabledOnRavenwood +@RunWith(AndroidJUnit4::class) +class HearingDevicesTileUserActionInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val inputHandler = FakeQSTileIntentUserInputHandler() + + private lateinit var underTest: HearingDevicesTileUserActionInteractor + + @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule() + @Mock private lateinit var dialogManager: HearingDevicesDialogManager + + @Before + fun setUp() { + underTest = + HearingDevicesTileUserActionInteractor( + testScope.coroutineContext, + inputHandler, + dialogManager, + ) + } + + @Test + fun handleClick_launchDialog() = + testScope.runTest { + val input = + HearingDevicesTileModel( + isAnyActiveHearingDevice = true, + isAnyPairedHearingDevice = true, + ) + + underTest.handleInput(QSTileInputTestKtx.click(input)) + + verify(dialogManager).showDialog(anyOrNull(), eq(LAUNCH_SOURCE_QS_TILE)) + } + + @Test + fun handleLongClick_launchSettings() = + testScope.runTest { + val input = + HearingDevicesTileModel( + isAnyActiveHearingDevice = true, + isAnyPairedHearingDevice = true, + ) + + underTest.handleInput(QSTileInputTestKtx.longClick(input)) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_HEARING_DEVICES_SETTINGS) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java index 0454317b5f04..06d19d7f9822 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java @@ -68,13 +68,13 @@ import com.android.internal.logging.UiEventLogger; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.internal.statusbar.IStatusBarService; import com.android.internal.util.LatencyTracker; +import com.android.keyguard.EmptyLockIconViewController; import com.android.keyguard.KeyguardClockSwitch; import com.android.keyguard.KeyguardClockSwitchController; import com.android.keyguard.KeyguardSliceViewController; import com.android.keyguard.KeyguardStatusView; import com.android.keyguard.KeyguardStatusViewController; import com.android.keyguard.KeyguardUpdateMonitor; -import com.android.keyguard.LegacyLockIconViewController; import com.android.keyguard.dagger.KeyguardQsUserSwitchComponent; import com.android.keyguard.dagger.KeyguardStatusBarViewComponent; import com.android.keyguard.dagger.KeyguardStatusViewComponent; @@ -271,6 +271,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected KeyguardUserSwitcherController mKeyguardUserSwitcherController; @Mock protected KeyguardStatusViewComponent mKeyguardStatusViewComponent; @Mock protected KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory; + @Mock protected EmptyLockIconViewController mLockIconViewController; @Mock protected KeyguardStatusBarViewComponent mKeyguardStatusBarViewComponent; @Mock protected KeyguardClockSwitchController mKeyguardClockSwitchController; @Mock protected KeyguardStatusBarViewController mKeyguardStatusBarViewController; @@ -285,7 +286,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { @Mock protected AmbientState mAmbientState; @Mock protected UserManager mUserManager; @Mock protected UiEventLogger mUiEventLogger; - @Mock protected LegacyLockIconViewController mLockIconViewController; @Mock protected KeyguardViewConfigurator mKeyguardViewConfigurator; @Mock protected KeyguardRootView mKeyguardRootView; @Mock protected View mKeyguardRootViewChild; @@ -397,7 +397,6 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mFeatureFlags.set(Flags.QS_USER_DETAIL_SHORTCUT, false); mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - mSetFlagsRule.disableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); mMainDispatcher = getMainDispatcher(); KeyguardInteractorFactory.WithDependencies keyguardInteractorDeps = @@ -687,6 +686,9 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(longPressHandlingView.getResources()).thenReturn(longPressHandlingViewRes); when(longPressHandlingViewRes.getString(anyInt())).thenReturn(""); + when(mKeyguardRootView.findViewById(anyInt())).thenReturn(mKeyguardRootViewChild); + when(mKeyguardViewConfigurator.getKeyguardRootView()).thenReturn(mKeyguardRootView); + mNotificationPanelViewController = new NotificationPanelViewController( mView, mMainHandler, @@ -852,7 +854,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { mNotificationPanelViewController.mBottomAreaShadeAlphaAnimator.cancel(); mNotificationPanelViewController.cancelHeightAnimator(); leakedAnimators = mNotificationPanelViewController.mTestSetOfAnimatorsUsed.stream() - .filter(Animator::isRunning).toList(); + .filter(Animator::isRunning).toList(); mNotificationPanelViewController.mTestSetOfAnimatorsUsed.forEach(Animator::cancel); } if (mMainHandler != null) { @@ -869,11 +871,7 @@ public class NotificationPanelViewControllerBaseTest extends SysuiTestCase { when(mNotificationStackScrollLayoutController.getTop()).thenReturn(0); when(mNotificationStackScrollLayoutController.getHeight()).thenReturn(stackBottom); when(mNotificationStackScrollLayoutController.getBottom()).thenReturn(stackBottom); - when(mLockIconViewController.getTop()).thenReturn((float) (stackBottom - lockIconPadding)); - when(mKeyguardRootViewChild.getTop()).thenReturn((int) (stackBottom - lockIconPadding)); - when(mKeyguardRootView.findViewById(anyInt())).thenReturn(mKeyguardRootViewChild); - when(mKeyguardViewConfigurator.getKeyguardRootView()).thenReturn(mKeyguardRootView); when(mResources.getDimensionPixelSize(R.dimen.keyguard_indication_bottom_padding)) .thenReturn(indicationPadding); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java index 43dbb40d7721..ec75972aecfe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java @@ -217,7 +217,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(5); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(5); } @@ -235,7 +234,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -253,7 +251,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -271,7 +268,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(2); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(2); } @@ -289,7 +285,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); assertThat(mNotificationPanelViewController.getVerticalSpaceForLockscreenShelf()) .isEqualTo(0); } @@ -389,7 +384,6 @@ public class NotificationPanelViewControllerTest extends NotificationPanelViewCo @Test @DisableFlags(com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) public void alternateBouncerVisible_onTouchEvent_notHandled() { - mSetFlagsRule.enableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR); // GIVEN alternate bouncer is visible when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt index 6f2302a22d7b..9fe52991c3a0 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewControllerTest.kt @@ -30,7 +30,6 @@ import android.view.ViewGroup import android.view.ViewTreeObserver import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityContainerController -import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.Flags import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT @@ -54,7 +53,6 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.TransitionStep import com.android.systemui.res.R -import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.shade.NotificationShadeWindowView.InteractionEventHandler import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor import com.android.systemui.statusbar.DragDownHelper @@ -71,7 +69,6 @@ import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.DozeScrimController import com.android.systemui.statusbar.phone.DozeServiceHost import com.android.systemui.statusbar.phone.PhoneStatusBarViewController -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.UnfoldTransitionProgressProvider @@ -80,6 +77,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.eq import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat +import java.util.Optional import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow @@ -98,11 +96,10 @@ import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.times import org.mockito.Mockito.verify +import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters -import java.util.Optional -import org.mockito.Mockito.`when` as whenever @OptIn(ExperimentalCoroutinesApi::class) @SmallTest @@ -125,12 +122,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : @Mock private lateinit var dumpManager: DumpManager @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var stackScrollLayoutController: NotificationStackScrollLayoutController - @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController @Mock private lateinit var quickSettingsController: QuickSettingsControllerImpl @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController - @Mock private lateinit var lockIconViewController: LegacyLockIconViewController @Mock private lateinit var phoneStatusBarViewController: PhoneStatusBarViewController @Mock private lateinit var pulsingGestureListener: PulsingGestureListener @Mock @@ -144,7 +139,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : @Mock lateinit var keyguardSecurityContainerController: KeyguardSecurityContainerController @Mock private lateinit var unfoldTransitionProgressProvider: - Optional<UnfoldTransitionProgressProvider> + Optional<UnfoldTransitionProgressProvider> @Mock lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor @Mock lateinit var dragDownHelper: DragDownHelper @Mock lateinit var mSelectedUserInteractor: SelectedUserInteractor @@ -176,20 +171,17 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : MockitoAnnotations.initMocks(this) whenever(view.bottom).thenReturn(VIEW_BOTTOM) whenever(view.findViewById<ViewGroup>(R.id.keyguard_bouncer_container)) - .thenReturn(mock(ViewGroup::class.java)) + .thenReturn(mock(ViewGroup::class.java)) whenever(keyguardBouncerComponentFactory.create(any(ViewGroup::class.java))) - .thenReturn(keyguardBouncerComponent) + .thenReturn(keyguardBouncerComponent) whenever(keyguardBouncerComponent.securityContainerController) - .thenReturn(keyguardSecurityContainerController) + .thenReturn(keyguardSecurityContainerController) whenever(keyguardTransitionInteractor.transition(Edge.create(LOCKSCREEN, DREAMING))) - .thenReturn(emptyFlow<TransitionStep>()) + .thenReturn(emptyFlow<TransitionStep>()) featureFlagsClassic = FakeFeatureFlagsClassic() featureFlagsClassic.set(SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlagsClassic.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - if (!SceneContainerFlag.isEnabled) { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - } mSetFlagsRule.enableFlags(Flags.FLAG_REVAMPED_BOUNCER_MESSAGES) testScope = TestScope() @@ -208,9 +200,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : panelExpansionInteractor, ShadeExpansionStateManager(), stackScrollLayoutController, - statusBarKeyguardViewManager, statusBarWindowStateController, - lockIconViewController, centralSurfaces, dozeServiceHost, dozeScrimController, @@ -233,7 +223,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : quickSettingsController, primaryBouncerInteractor, alternateBouncerInteractor, - mock(BouncerViewBinder::class.java) + mock(BouncerViewBinder::class.java), ) underTest.setupExpandedStatusBar() underTest.setDragDownHelper(dragDownHelper) @@ -294,7 +284,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + .thenReturn(true) whenever(phoneStatusBarViewController.sendTouchToView(DOWN_EVENT)).thenReturn(true) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -309,7 +299,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + .thenReturn(true) // Item we're testing whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(false) @@ -327,7 +317,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) // Item we're testing whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(false) + .thenReturn(false) val returnVal = interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -341,7 +331,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + .thenReturn(true) // Item we're testing whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(false) @@ -358,7 +348,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(statusBarWindowStateController.windowIsShowing()).thenReturn(true) whenever(panelExpansionInteractor.isFullyCollapsed).thenReturn(true) whenever(phoneStatusBarViewController.touchIsWithinView(anyFloat(), anyFloat())) - .thenReturn(true) + .thenReturn(true) // Down event first interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT) @@ -379,7 +369,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : // GIVEN touch dispatcher in a state that returns true underTest.setStatusBarViewController(phoneStatusBarViewController) whenever(keyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) - .thenReturn(true) + .thenReturn(true) assertThat(interactionEventHandler.handleDispatchTouchEvent(DOWN_EVENT)).isTrue() // WHEN launch animation is running for 2 seconds @@ -432,47 +422,13 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : } @Test - fun shouldInterceptTouchEvent_statusBarKeyguardViewManagerShouldIntercept() { - // down event should be intercepted by keyguardViewManager - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(true) - - // Then touch should not be intercepted - val shouldIntercept = interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT) - assertThat(shouldIntercept).isTrue() - } - - @Test - @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) - fun shouldInterceptTouchEvent_dozing_touchInLockIconArea_touchNotIntercepted() { - // GIVEN dozing - whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) - // AND alternate bouncer doesn't want the touch - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(false) - // AND quick settings controller doesn't want it - whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) - .thenReturn(false) - // AND the lock icon wants the touch - whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(true) - - // THEN touch should NOT be intercepted by NotificationShade - assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isFalse() - } - - @Test @EnableFlags(FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT) fun shouldInterceptTouchEvent_dozing_touchNotInLockIconArea_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) - // AND alternate bouncer doesn't want the touch - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(false) - // AND the lock icon does NOT want the touch - whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) // AND quick settings controller doesn't want it whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) - .thenReturn(false) + .thenReturn(false) // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -483,14 +439,9 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : fun shouldInterceptTouchEvent_dozing_touchInStatusBar_touchIntercepted() { // GIVEN dozing whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) - // AND alternate bouncer doesn't want the touch - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(false) - // AND the lock icon does NOT want the touch - whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) // AND quick settings controller DOES want it whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) - .thenReturn(true) + .thenReturn(true) // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -503,20 +454,13 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : whenever(sysuiStatusBarStateController.isDozing).thenReturn(true) // AND pulsing whenever(dozeServiceHost.isPulsing()).thenReturn(true) - // AND status bar doesn't want it - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(DOWN_EVENT)) - .thenReturn(false) - // AND shade is not fully expanded (mock is false by default) - // AND the lock icon does NOT want the touch - whenever(lockIconViewController.willHandleTouchWhileDozing(DOWN_EVENT)).thenReturn(false) // AND quick settings controller DOES want it whenever(quickSettingsController.shouldQuickSettingsIntercept(any(), any(), any())) - .thenReturn(true) + .thenReturn(true) // AND bouncer is not showing whenever(centralSurfaces.isBouncerShowing()).thenReturn(false) // AND panel view controller wants it - whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT)) - .thenReturn(true) + whenever(shadeViewController.handleExternalInterceptTouch(DOWN_EVENT)).thenReturn(true) // THEN touch should be intercepted by NotificationShade assertThat(interactionEventHandler.shouldInterceptTouchEvent(DOWN_EVENT)).isTrue() @@ -589,12 +533,10 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : underTest.setupCommunalHubLayout() // Simluate attaching the view so flow collection starts. - val onAttachStateChangeListenerArgumentCaptor = ArgumentCaptor.forClass( - View.OnAttachStateChangeListener::class.java - ) - verify(view, atLeast(1)).addOnAttachStateChangeListener( - onAttachStateChangeListenerArgumentCaptor.capture() - ) + val onAttachStateChangeListenerArgumentCaptor = + ArgumentCaptor.forClass(View.OnAttachStateChangeListener::class.java) + verify(view, atLeast(1)) + .addOnAttachStateChangeListener(onAttachStateChangeListenerArgumentCaptor.capture()) for (listener in onAttachStateChangeListenerArgumentCaptor.allValues) { listener.onViewAttachedToWindow(view) } @@ -608,7 +550,7 @@ class NotificationShadeWindowViewControllerTest(flags: FlagsParameterization) : @RequiresFlagsDisabled(Flags.FLAG_COMMUNAL_HUB) fun doesNotSetupCommunalHubLayout_whenFlagDisabled() { whenever(mGlanceableHubContainerController.communalAvailable()) - .thenReturn(MutableStateFlow(false)) + .thenReturn(MutableStateFlow(false)) val mockCommunalPlaceholder = mock(View::class.java) val fakeViewIndex = 20 diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt index ca29dd98a637..9093b2bcbbf8 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.kt @@ -23,7 +23,6 @@ import android.widget.FrameLayout import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.keyguard.KeyguardSecurityContainerController -import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.dagger.KeyguardBouncerComponent import com.android.systemui.Flags as AConfigFlags import com.android.systemui.SysuiTestCase @@ -57,7 +56,6 @@ import com.android.systemui.statusbar.notification.stack.NotificationStackScroll import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.DozeScrimController import com.android.systemui.statusbar.phone.DozeServiceHost -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.window.StatusBarWindowStateController import com.android.systemui.unfold.SysUIUnfoldComponent import com.android.systemui.unfold.UnfoldTransitionProgressProvider @@ -104,11 +102,9 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { @Mock private lateinit var notificationStackScrollLayoutController: NotificationStackScrollLayoutController - @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager @Mock private lateinit var statusBarWindowStateController: StatusBarWindowStateController @Mock private lateinit var lockscreenShadeTransitionController: LockscreenShadeTransitionController - @Mock private lateinit var lockIconViewController: LegacyLockIconViewController @Mock private lateinit var keyguardUnlockAnimationController: KeyguardUnlockAnimationController @Mock private lateinit var ambientState: AmbientState @Mock private lateinit var shadeLogger: ShadeLogger @@ -161,7 +157,6 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { val featureFlags = FakeFeatureFlags() featureFlags.set(Flags.SPLIT_SHADE_SUBPIXEL_OPTIMIZATION, true) featureFlags.set(Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false) - mSetFlagsRule.disableFlags(AConfigFlags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) mSetFlagsRule.enableFlags(AConfigFlags.FLAG_REVAMPED_BOUNCER_MESSAGES) testScope = TestScope() controller = @@ -176,9 +171,7 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { panelExpansionInteractor, ShadeExpansionStateManager(), notificationStackScrollLayoutController, - statusBarKeyguardViewManager, statusBarWindowStateController, - lockIconViewController, centralSurfaces, dozeServiceHost, dozeScrimController, @@ -221,48 +214,18 @@ class NotificationShadeWindowViewTest : SysuiTestCase() { } @Test - fun testInterceptTouchWhenShowingAltAuth() = - testScope.runTest { - captureInteractionEventHandler() - - // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept - whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())).thenReturn(true) - whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) - - // THEN we should intercept touch - assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isTrue() - } - - @Test fun testNoInterceptTouch() = testScope.runTest { captureInteractionEventHandler() - // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept + // WHEN not dozing, drag down helper doesn't want to intercept whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(statusBarKeyguardViewManager.shouldInterceptTouchEvent(any())) - .thenReturn(false) whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) // THEN we shouldn't intercept touch assertThat(interactionEventHandler.shouldInterceptTouchEvent(mock())).isFalse() } - @Test - fun testHandleTouchEventWhenShowingAltAuth() = - testScope.runTest { - captureInteractionEventHandler() - - // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept - whenever(statusBarStateController.isDozing).thenReturn(false) - whenever(statusBarKeyguardViewManager.onTouch(any())).thenReturn(true) - whenever(dragDownHelper.onInterceptTouchEvent(any())).thenReturn(false) - - // THEN we should handle the touch - assertThat(interactionEventHandler.handleTouchEvent(mock())).isTrue() - } - private fun captureInteractionEventHandler() { verify(underTest).setInteractionEventHandler(interactionEventHandlerCaptor.capture()) interactionEventHandler = interactionEventHandlerCaptor.value diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt index 118dea6376bb..69a76271f726 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelTest.kt @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel +package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel import android.content.packageManager import android.graphics.drawable.BitmapDrawable import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.commandline.CommandRegistry @@ -40,13 +40,13 @@ import org.mockito.kotlin.any import org.mockito.kotlin.whenever @SmallTest -class DemoRonChipViewModelTest : SysuiTestCase() { +class DemoNotifChipViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val commandRegistry = kosmos.commandRegistry private val pw = PrintWriter(StringWriter()) - private val underTest = kosmos.demoRonChipViewModel + private val underTest = kosmos.demoNotifChipViewModel @Before fun setUp() { @@ -56,61 +56,61 @@ class DemoRonChipViewModelTest : SysuiTestCase() { } @Test - @DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @DisableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_flagOff_hidden() = testScope.runTest { val latest by collectLastValue(underTest.chip) - addDemoRonChip() + addDemoNotifChip() assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_noPackage_hidden() = testScope.runTest { val latest by collectLastValue(underTest.chip) - commandRegistry.onShellCommand(pw, arrayOf("demo-ron")) + commandRegistry.onShellCommand(pw, arrayOf("demo-notif")) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_hasPackage_shown() = testScope.runTest { val latest by collectLastValue(underTest.chip) - commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui")) + commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "-p", "com.android.systemui")) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_hasText_shownWithText() = testScope.runTest { val latest by collectLastValue(underTest.chip) commandRegistry.onShellCommand( pw, - arrayOf("demo-ron", "-p", "com.android.systemui", "-t", "test") + arrayOf("demo-notif", "-p", "com.android.systemui", "-t", "test"), ) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown.Text::class.java) } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_supportsColor() = testScope.runTest { val latest by collectLastValue(underTest.chip) commandRegistry.onShellCommand( pw, - arrayOf("demo-ron", "-p", "com.android.systemui", "-c", "#434343") + arrayOf("demo-notif", "-p", "com.android.systemui", "-c", "#434343"), ) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) @@ -119,28 +119,28 @@ class DemoRonChipViewModelTest : SysuiTestCase() { } @Test - @EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) + @EnableFlags(StatusBarNotifChips.FLAG_NAME) fun chip_hasHideArg_hidden() = testScope.runTest { val latest by collectLastValue(underTest.chip) // First, show a chip - addDemoRonChip() + addDemoNotifChip() assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) // Then, hide the chip - commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "--hide")) + commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "--hide")) assertThat(latest).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } - private fun addDemoRonChip() { - Companion.addDemoRonChip(commandRegistry, pw) + private fun addDemoNotifChip() { + addDemoNotifChip(commandRegistry, pw) } companion object { - fun addDemoRonChip(commandRegistry: CommandRegistry, pw: PrintWriter) { - commandRegistry.onShellCommand(pw, arrayOf("demo-ron", "-p", "com.android.systemui")) + fun addDemoNotifChip(commandRegistry: CommandRegistry, pw: PrintWriter) { + commandRegistry.onShellCommand(pw, arrayOf("demo-notif", "-p", "com.android.systemui")) } } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt new file mode 100644 index 000000000000..eb5d9318c88f --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelTest.kt @@ -0,0 +1,121 @@ +/* + * Copyright (C) 2024 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.chips.notification.ui.viewmodel + +import android.platform.test.annotations.EnableFlags +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.kosmos.testScope +import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips +import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlin.test.Test +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.runner.RunWith +import org.mockito.kotlin.mock + +@SmallTest +@RunWith(AndroidJUnit4::class) +@OptIn(ExperimentalCoroutinesApi::class) +@EnableFlags(StatusBarNotifChips.FLAG_NAME) +class NotifChipsViewModelTest : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + private val activeNotificationListRepository = kosmos.activeNotificationListRepository + + private val underTest = kosmos.notifChipsViewModel + + @Test + fun chips_noNotifs_empty() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + setNotifs(emptyList()) + + assertThat(latest).isEmpty() + } + + @Test + fun chips_notifMissingStatusBarChipIconView_empty() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = null))) + + assertThat(latest).isEmpty() + } + + @Test + fun chips_oneNotif_statusBarIconViewMatches() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val icon = mock<StatusBarIconView>() + setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon))) + + assertThat(latest).hasSize(1) + val chip = latest!![0] + assertThat(chip).isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat(chip.icon).isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(icon)) + } + + @Test + fun chips_twoNotifs_twoChips() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val firstIcon = mock<StatusBarIconView>() + val secondIcon = mock<StatusBarIconView>() + setNotifs( + listOf( + activeNotificationModel(key = "notif1", statusBarChipIcon = firstIcon), + activeNotificationModel(key = "notif2", statusBarChipIcon = secondIcon), + ) + ) + + assertThat(latest).hasSize(2) + assertIsNotifChip(latest!![0], firstIcon) + assertIsNotifChip(latest!![1], secondIcon) + } + + private fun setNotifs(notifs: List<ActiveNotificationModel>) { + activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { notifs.forEach { addIndividualNotif(it) } } + .build() + testScope.runCurrent() + } + + companion object { + fun assertIsNotifChip(latest: OngoingActivityChipModel?, expectedIcon: StatusBarIconView) { + assertThat(latest) + .isInstanceOf(OngoingActivityChipModel.Shown.ShortTimeDelta::class.java) + assertThat((latest as OngoingActivityChipModel.Shown).icon) + .isEqualTo(OngoingActivityChipModel.ChipIcon.StatusBarView(expectedIcon)) + } + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt index 26ce7b956fde..e96def6d43a3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelTest.kt @@ -25,7 +25,6 @@ import android.platform.test.annotations.DisableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS import com.android.systemui.SysuiTestCase import com.android.systemui.common.shared.model.Icon import com.android.systemui.coroutines.collectLastValue @@ -40,7 +39,8 @@ import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.phone.SystemUIDialog @@ -66,13 +66,11 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.whenever -/** - * Tests for [OngoingActivityChipsViewModel] when the [FLAG_STATUS_BAR_RON_CHIPS] flag is disabled. - */ +/** Tests for [OngoingActivityChipsViewModel] when the [StatusBarNotifChips] flag is disabled. */ @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) -@DisableFlags(FLAG_STATUS_BAR_RON_CHIPS) +@DisableFlags(StatusBarNotifChips.FLAG_NAME) class OngoingActivityChipsViewModelTest : SysuiTestCase() { private val kosmos = Kosmos().also { it.testCase = this } private val testScope = kosmos.testScope @@ -99,11 +97,11 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { @Before fun setUp() { setUpPackageManagerForMediaProjection(kosmos) - kosmos.demoRonChipViewModel.start() + kosmos.demoNotifChipViewModel.start() val icon = BitmapDrawable( context.resources, - Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888) + Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888), ) whenever(kosmos.packageManager.getApplicationIcon(any<String>())).thenReturn(icon) } @@ -325,7 +323,7 @@ class OngoingActivityChipsViewModelTest : SysuiTestCase() { latest: OngoingActivityChipModel?, chipView: View, dialog: SystemUIDialog, - kosmos: Kosmos + kosmos: Kosmos, ): DialogInterface.OnClickListener { // Capture the action that would get invoked when the user clicks "Stop" on the dialog lateinit var dialogStopAction: DialogInterface.OnClickListener diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt index 631120b39805..b12d7c57e1fd 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithRonsViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsWithNotifsViewModelTest.kt @@ -24,7 +24,6 @@ import android.platform.test.annotations.EnableFlags import android.view.View import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest -import com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testScope @@ -34,10 +33,13 @@ import com.android.systemui.mediaprojection.taskswitcher.FakeActivityTaskManager import com.android.systemui.res.R import com.android.systemui.screenrecord.data.model.ScreenRecordModel import com.android.systemui.screenrecord.data.repository.screenRecordRepository +import com.android.systemui.statusbar.StatusBarIconView import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.NORMAL_PACKAGE import com.android.systemui.statusbar.chips.mediaprojection.domain.interactor.MediaProjectionChipInteractorTest.Companion.setUpPackageManagerForMediaProjection -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModelTest.Companion.addDemoRonChip -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModelTest.Companion.addDemoNotifChip +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips +import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModelTest.Companion.assertIsNotifChip import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer @@ -46,6 +48,10 @@ import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsVie import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.assertIsShareToAppChip import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipsViewModelTest.Companion.getStopActionFromDialog import com.android.systemui.statusbar.commandline.commandRegistry +import com.android.systemui.statusbar.notification.data.model.activeNotificationModel +import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore +import com.android.systemui.statusbar.notification.data.repository.activeNotificationListRepository +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.ongoingcall.data.repository.ongoingCallRepository import com.android.systemui.statusbar.phone.ongoingcall.shared.model.OngoingCallModel @@ -67,14 +73,12 @@ import org.mockito.kotlin.any import org.mockito.kotlin.mock import org.mockito.kotlin.whenever -/** - * Tests for [OngoingActivityChipsViewModel] when the [FLAG_STATUS_BAR_RON_CHIPS] flag is enabled. - */ +/** Tests for [OngoingActivityChipsViewModel] when the [StatusBarNotifChips] flag is enabled. */ @SmallTest @RunWith(AndroidJUnit4::class) @OptIn(ExperimentalCoroutinesApi::class) -@EnableFlags(FLAG_STATUS_BAR_RON_CHIPS) -class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { +@EnableFlags(StatusBarNotifChips.FLAG_NAME) +class OngoingActivityChipsWithNotifsViewModelTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope private val systemClock = kosmos.fakeSystemClock @@ -83,6 +87,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { private val screenRecordState = kosmos.screenRecordRepository.screenRecordState private val mediaProjectionState = kosmos.fakeMediaProjectionRepository.mediaProjectionState private val callRepo = kosmos.ongoingCallRepository + private val activeNotificationListRepository = kosmos.activeNotificationListRepository private val pw = PrintWriter(StringWriter()) @@ -103,16 +108,16 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { @Before fun setUp() { setUpPackageManagerForMediaProjection(kosmos) - kosmos.demoRonChipViewModel.start() + kosmos.demoNotifChipViewModel.start() val icon = BitmapDrawable( context.resources, - Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888) + Bitmap.createBitmap(/* width= */ 100, /* height= */ 100, Bitmap.Config.ARGB_8888), ) whenever(kosmos.packageManager.getApplicationIcon(any<String>())).thenReturn(icon) } - // Even though the `primaryChip` flow isn't used when the RONs flag is on, still test that the + // Even though the `primaryChip` flow isn't used when the notifs flag is on, still test that the // flow has the right behavior to verify that we don't break any existing functionality. @Test @@ -249,13 +254,13 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { testScope.runTest { screenRecordState.value = ScreenRecordModel.Recording callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) - addDemoRonChip(commandRegistry, pw) + addDemoNotifChip(commandRegistry, pw) val latest by collectLastValue(underTest.chips) assertIsScreenRecordChip(latest!!.primary) assertIsCallChip(latest!!.secondary) - // Demo RON chip is dropped + // Demo notif chip is dropped } @Test @@ -288,10 +293,101 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { } @Test + fun chips_singleNotifChip_primaryIsNotifSecondaryIsHidden() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val icon = mock<StatusBarIconView>() + setNotifs(listOf(activeNotificationModel(key = "notif", statusBarChipIcon = icon))) + + assertIsNotifChip(latest!!.primary, icon) + assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) + } + + @Test + fun chips_twoNotifChips_primaryAndSecondaryAreNotifsInOrder() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val firstIcon = mock<StatusBarIconView>() + val secondIcon = mock<StatusBarIconView>() + setNotifs( + listOf( + activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), + activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon), + ) + ) + + assertIsNotifChip(latest!!.primary, firstIcon) + assertIsNotifChip(latest!!.secondary, secondIcon) + } + + @Test + fun chips_threeNotifChips_topTwoShown() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + val firstIcon = mock<StatusBarIconView>() + val secondIcon = mock<StatusBarIconView>() + val thirdIcon = mock<StatusBarIconView>() + setNotifs( + listOf( + activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), + activeNotificationModel(key = "secondNotif", statusBarChipIcon = secondIcon), + activeNotificationModel(key = "thirdNotif", statusBarChipIcon = thirdIcon), + ) + ) + + assertIsNotifChip(latest!!.primary, firstIcon) + assertIsNotifChip(latest!!.secondary, secondIcon) + } + + @Test + fun chips_callAndNotifs_primaryIsCallSecondaryIsNotif() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + val firstIcon = mock<StatusBarIconView>() + setNotifs( + listOf( + activeNotificationModel(key = "firstNotif", statusBarChipIcon = firstIcon), + activeNotificationModel( + key = "secondNotif", + statusBarChipIcon = mock<StatusBarIconView>(), + ), + ) + ) + + assertIsCallChip(latest!!.primary) + assertIsNotifChip(latest!!.secondary, firstIcon) + } + + @Test + fun chips_screenRecordAndCallAndNotifs_notifsNotShown() = + testScope.runTest { + val latest by collectLastValue(underTest.chips) + + callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) + screenRecordState.value = ScreenRecordModel.Recording + setNotifs( + listOf( + activeNotificationModel( + key = "notif", + statusBarChipIcon = mock<StatusBarIconView>(), + ) + ) + ) + + assertIsScreenRecordChip(latest!!.primary) + assertIsCallChip(latest!!.secondary) + } + + @Test fun primaryChip_higherPriorityChipAdded_lowerPriorityChipReplaced() = testScope.runTest { // Start with just the lowest priority chip shown - addDemoRonChip(commandRegistry, pw) + addDemoNotifChip(commandRegistry, pw) // And everything else hidden callRepo.setOngoingCallState(OngoingCallModel.NoCall) mediaProjectionState.value = MediaProjectionState.NotProjecting @@ -299,7 +395,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.primaryChip) - assertIsDemoRonChip(latest) + assertIsDemoNotifChip(latest) // WHEN the higher priority call chip is added callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) @@ -333,7 +429,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { mediaProjectionState.value = MediaProjectionState.Projecting.EntireScreen(NORMAL_PACKAGE) callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) - addDemoRonChip(commandRegistry, pw) + addDemoNotifChip(commandRegistry, pw) val latest by collectLastValue(underTest.primaryChip) @@ -355,15 +451,15 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { // WHEN the higher priority call is removed callRepo.setOngoingCallState(OngoingCallModel.NoCall) - // THEN the lower priority demo RON is used - assertIsDemoRonChip(latest) + // THEN the lower priority demo notif is used + assertIsDemoNotifChip(latest) } @Test fun chips_movesChipsAroundAccordingToPriority() = testScope.runTest { // Start with just the lowest priority chip shown - addDemoRonChip(commandRegistry, pw) + addDemoNotifChip(commandRegistry, pw) // And everything else hidden callRepo.setOngoingCallState(OngoingCallModel.NoCall) mediaProjectionState.value = MediaProjectionState.NotProjecting @@ -371,16 +467,16 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { val latest by collectLastValue(underTest.chips) - assertIsDemoRonChip(latest!!.primary) + assertIsDemoNotifChip(latest!!.primary) assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) // WHEN the higher priority call chip is added callRepo.setOngoingCallState(inCallModel(startTimeMs = 34)) - // THEN the higher priority call chip is used as primary and demo ron is demoted to + // THEN the higher priority call chip is used as primary and demo notif is demoted to // secondary assertIsCallChip(latest!!.primary) - assertIsDemoRonChip(latest!!.secondary) + assertIsDemoNotifChip(latest!!.secondary) // WHEN the higher priority media projection chip is added mediaProjectionState.value = @@ -391,7 +487,7 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { ) // THEN the higher priority media projection chip is used as primary and call is demoted - // to secondary (and demo RON is dropped altogether) + // to secondary (and demo notif is dropped altogether) assertIsShareToAppChip(latest!!.primary) assertIsCallChip(latest!!.secondary) @@ -405,15 +501,15 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { screenRecordState.value = ScreenRecordModel.DoingNothing callRepo.setOngoingCallState(OngoingCallModel.NoCall) - // THEN media projection and demo RON remain + // THEN media projection and demo notif remain assertIsShareToAppChip(latest!!.primary) - assertIsDemoRonChip(latest!!.secondary) + assertIsDemoNotifChip(latest!!.secondary) // WHEN media projection is dropped mediaProjectionState.value = MediaProjectionState.NotProjecting - // THEN demo RON is promoted to primary - assertIsDemoRonChip(latest!!.primary) + // THEN demo notif is promoted to primary + assertIsDemoNotifChip(latest!!.primary) assertThat(latest!!.secondary).isInstanceOf(OngoingActivityChipModel.Hidden::class.java) } @@ -526,9 +622,17 @@ class OngoingActivityChipsWithRonsViewModelTest : SysuiTestCase() { assertThat(latest).isEqualTo(OngoingActivityChipModel.Hidden(shouldAnimate = false)) } - private fun assertIsDemoRonChip(latest: OngoingActivityChipModel?) { + private fun assertIsDemoNotifChip(latest: OngoingActivityChipModel?) { assertThat(latest).isInstanceOf(OngoingActivityChipModel.Shown::class.java) assertThat((latest as OngoingActivityChipModel.Shown).icon) .isInstanceOf(OngoingActivityChipModel.ChipIcon.FullColorAppIcon::class.java) } + + private fun setNotifs(notifs: List<ActiveNotificationModel>) { + activeNotificationListRepository.activeNotifications.value = + ActiveNotificationsStore.Builder() + .apply { notifs.forEach { addIndividualNotif(it) } } + .build() + testScope.runCurrent() + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt new file mode 100644 index 000000000000..8dcc44463213 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarterTest.kt @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2024 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.core + +import android.platform.test.annotations.EnableFlags +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.google.common.truth.Expect +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.kotlin.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) +class MultiDisplayStatusBarStarterTest : SysuiTestCase() { + @get:Rule val expect: Expect = Expect.create() + + private val kosmos = + testKosmos().also { + it.statusBarOrchestratorFactory = it.fakeStatusBarOrchestratorFactory + it.statusBarInitializerStore = it.fakeStatusBarInitializerStore + } + private val testScope = kosmos.testScope + private val fakeDisplayRepository = kosmos.displayRepository + private val fakeOrchestratorFactory = kosmos.fakeStatusBarOrchestratorFactory + private val fakeInitializerStore = kosmos.fakeStatusBarInitializerStore + + // Lazy, so that @EnableFlags is set before initializer is instantiated. + private val underTest by lazy { kosmos.multiDisplayStatusBarStarter } + + @Test + fun start_startsInitializersForCurrentDisplays() = + testScope.runTest { + fakeDisplayRepository.addDisplay(displayId = 1) + fakeDisplayRepository.addDisplay(displayId = 2) + + underTest.start() + runCurrent() + + expect + .that(fakeInitializerStore.forDisplay(displayId = 1).startedByCoreStartable) + .isTrue() + expect + .that(fakeInitializerStore.forDisplay(displayId = 2).startedByCoreStartable) + .isTrue() + } + + @Test + fun start_startsOrchestratorForCurrentDisplays() = + testScope.runTest { + fakeDisplayRepository.addDisplay(displayId = 1) + fakeDisplayRepository.addDisplay(displayId = 2) + + underTest.start() + runCurrent() + + verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 1)!!).start() + verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 2)!!).start() + } + + @Test + fun displayAdded_orchestratorForNewDisplayIsStarted() = + testScope.runTest { + underTest.start() + runCurrent() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + verify(fakeOrchestratorFactory.createdOrchestratorForDisplay(displayId = 3)!!).start() + } + + @Test + fun displayAdded_initializerForNewDisplayIsStarted() = + testScope.runTest { + underTest.start() + runCurrent() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + expect + .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable) + .isTrue() + } + + @Test + fun displayAddedDuringStart_initializerForNewDisplayIsStarted() = + testScope.runTest { + underTest.start() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + expect + .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable) + .isTrue() + } + + @Test + fun displayAddedDuringStart_orchestratorForNewDisplayIsStarted() = + testScope.runTest { + underTest.start() + + fakeDisplayRepository.addDisplay(displayId = 3) + runCurrent() + + expect + .that(fakeInitializerStore.forDisplay(displayId = 3).startedByCoreStartable) + .isTrue() + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt index f64387c95e3d..c737bf7a8bec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarInitializerTest.kt @@ -60,10 +60,9 @@ class StatusBarInitializerTest : SysuiTestCase() { val underTest = StatusBarInitializerImpl( - displayId = context.displayId, - statusBarWindowControllerStore = windowControllerStore, collapsedStatusBarFragmentProvider = { mock(CollapsedStatusBarFragment::class.java) }, creationListeners = setOf(), + statusBarWindowController = windowController, ) @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt index bb3fb1e71f78..ab8e878ab309 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/core/StatusBarOrchestratorTest.kt @@ -38,13 +38,10 @@ import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT import com.android.systemui.statusbar.data.model.StatusBarMode.LIGHTS_OUT_TRANSPARENT import com.android.systemui.statusbar.data.model.StatusBarMode.OPAQUE import com.android.systemui.statusbar.data.model.StatusBarMode.TRANSPARENT -import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository -import com.android.systemui.statusbar.phone.mockPhoneStatusBarTransitions -import com.android.systemui.statusbar.phone.mockPhoneStatusBarViewController +import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository import com.android.systemui.statusbar.window.data.model.StatusBarWindowState -import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStateRepositoryStore -import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore -import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore +import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStatePerDisplayRepository +import com.android.systemui.statusbar.window.fakeStatusBarWindowController import com.android.systemui.testKosmos import com.android.wm.shell.bubbles.bubbles import com.google.common.truth.Truth.assertThat @@ -60,25 +57,20 @@ import org.mockito.kotlin.verify @RunWith(AndroidJUnit4::class) class StatusBarOrchestratorTest : SysuiTestCase() { - private val kosmos = - testKosmos().also { - it.testDispatcher = it.unconfinedTestDispatcher - it.statusBarWindowStateRepositoryStore = it.fakeStatusBarWindowStateRepositoryStore - } + private val kosmos = testKosmos().also { it.testDispatcher = it.unconfinedTestDispatcher } private val testScope = kosmos.testScope - private val statusBarViewController = kosmos.mockPhoneStatusBarViewController - private val statusBarWindowControllerStore = kosmos.fakeStatusBarWindowControllerStore - private val statusBarModeRepository = kosmos.fakeStatusBarModeRepository - private val pluginDependencyProvider = kosmos.mockPluginDependencyProvider - private val notificationShadeWindowViewController = + private val fakeStatusBarModePerDisplayRepository = kosmos.fakeStatusBarModePerDisplayRepository + private val mockPluginDependencyProvider = kosmos.mockPluginDependencyProvider + private val mockNotificationShadeWindowViewController = kosmos.mockNotificationShadeWindowViewController - private val shadeSurface = kosmos.mockShadeSurface - private val bouncerRepository = kosmos.fakeKeyguardBouncerRepository - private val fakeStatusBarWindowStateRepositoryStore = - kosmos.fakeStatusBarWindowStateRepositoryStore + private val mockShadeSurface = kosmos.mockShadeSurface + private val fakeBouncerRepository = kosmos.fakeKeyguardBouncerRepository + private val fakeStatusBarWindowStatePerDisplayRepository = + kosmos.fakeStatusBarWindowStatePerDisplayRepository private val fakePowerRepository = kosmos.fakePowerRepository - private val mockPhoneStatusBarTransitions = kosmos.mockPhoneStatusBarTransitions private val mockBubbles = kosmos.bubbles + private val fakeStatusBarWindowController = kosmos.fakeStatusBarWindowController + private val fakeStatusBarInitializer = kosmos.fakeStatusBarInitializer private val orchestrator = kosmos.statusBarOrchestrator @@ -86,30 +78,31 @@ class StatusBarOrchestratorTest : SysuiTestCase() { fun start_setsUpPluginDependencies() { orchestrator.start() - verify(pluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java) - verify(pluginDependencyProvider).allowPluginDependency(StatusBarStateController::class.java) + verify(mockPluginDependencyProvider).allowPluginDependency(DarkIconDispatcher::class.java) + verify(mockPluginDependencyProvider) + .allowPluginDependency(StatusBarStateController::class.java) } @Test fun start_attachesWindow() { orchestrator.start() - assertThat(statusBarWindowControllerStore.defaultDisplay.isAttached).isTrue() + assertThat(fakeStatusBarWindowController.isAttached).isTrue() } @Test fun start_setsStatusBarControllerOnShade() { orchestrator.start() - verify(notificationShadeWindowViewController) - .setStatusBarViewController(statusBarViewController) + verify(mockNotificationShadeWindowViewController) + .setStatusBarViewController(fakeStatusBarInitializer.statusBarViewController) } @Test fun start_updatesShadeExpansion() { orchestrator.start() - verify(shadeSurface).updateExpansionAndVisibility() + verify(mockShadeSurface).updateExpansionAndVisibility() } @Test @@ -117,9 +110,9 @@ class StatusBarOrchestratorTest : SysuiTestCase() { testScope.runTest { orchestrator.start() - bouncerRepository.setPrimaryShow(isShowing = true) + fakeBouncerRepository.setPrimaryShow(isShowing = true) - verify(statusBarViewController) + verify(fakeStatusBarInitializer.statusBarViewController) .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) } @@ -128,9 +121,9 @@ class StatusBarOrchestratorTest : SysuiTestCase() { testScope.runTest { orchestrator.start() - bouncerRepository.setPrimaryShow(isShowing = false) + fakeBouncerRepository.setPrimaryShow(isShowing = false) - verify(statusBarViewController) + verify(fakeStatusBarInitializer.statusBarViewController) .setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) } @@ -141,7 +134,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { orchestrator.start() - verify(mockPhoneStatusBarTransitions).finishAnimations() + verify(fakeStatusBarInitializer.statusBarTransitions).finishAnimations() } @Test @@ -151,7 +144,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { orchestrator.start() - verify(mockPhoneStatusBarTransitions, never()).finishAnimations() + verify(fakeStatusBarInitializer.statusBarTransitions, never()).finishAnimations() } @Test @@ -208,7 +201,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { orchestrator.start() - verify(mockPhoneStatusBarTransitions) + verify(fakeStatusBarInitializer.statusBarTransitions) .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true) } @@ -222,19 +215,19 @@ class StatusBarOrchestratorTest : SysuiTestCase() { orchestrator.start() - verify(mockPhoneStatusBarTransitions) + verify(fakeStatusBarInitializer.statusBarTransitions) .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true) setStatusBarMode(OPAQUE) - verify(mockPhoneStatusBarTransitions) + verify(fakeStatusBarInitializer.statusBarTransitions) .transitionTo(OPAQUE.toTransitionModeInt(), /* animate= */ true) setStatusBarMode(LIGHTS_OUT) - verify(mockPhoneStatusBarTransitions) + verify(fakeStatusBarInitializer.statusBarTransitions) .transitionTo(LIGHTS_OUT.toTransitionModeInt(), /* animate= */ true) setStatusBarMode(LIGHTS_OUT_TRANSPARENT) - verify(mockPhoneStatusBarTransitions) + verify(fakeStatusBarInitializer.statusBarTransitions) .transitionTo(LIGHTS_OUT_TRANSPARENT.toTransitionModeInt(), /* animate= */ true) } @@ -248,7 +241,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { orchestrator.start() - verify(mockPhoneStatusBarTransitions) + verify(fakeStatusBarInitializer.statusBarTransitions) .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false) } @@ -262,7 +255,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { orchestrator.start() - verify(mockPhoneStatusBarTransitions) + verify(fakeStatusBarInitializer.statusBarTransitions) .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false) } @@ -276,7 +269,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { orchestrator.start() - verify(mockPhoneStatusBarTransitions) + verify(fakeStatusBarInitializer.statusBarTransitions) .transitionTo(/* mode= */ TRANSPARENT.toTransitionModeInt(), /* animate= */ false) } @@ -295,7 +288,7 @@ class StatusBarOrchestratorTest : SysuiTestCase() { setTransientStatusBar() clearTransientStatusBar() - verify(mockPhoneStatusBarTransitions, times(1)) + verify(fakeStatusBarInitializer.statusBarTransitions, times(1)) .transitionTo(TRANSPARENT.toTransitionModeInt(), /* animate= */ true) } @@ -318,18 +311,18 @@ class StatusBarOrchestratorTest : SysuiTestCase() { } private fun setTransientStatusBar() { - statusBarModeRepository.defaultDisplay.showTransient() + fakeStatusBarModePerDisplayRepository.showTransient() } private fun clearTransientStatusBar() { - statusBarModeRepository.defaultDisplay.clearTransient() + fakeStatusBarModePerDisplayRepository.clearTransient() } private fun setStatusBarWindowState(state: StatusBarWindowState) { - fakeStatusBarWindowStateRepositoryStore.defaultDisplay.setWindowState(state) + fakeStatusBarWindowStatePerDisplayRepository.setWindowState(state) } private fun setStatusBarMode(statusBarMode: StatusBarMode) { - statusBarModeRepository.defaultDisplay.statusBarMode.value = statusBarMode + fakeStatusBarModePerDisplayRepository.statusBarMode.value = statusBarMode } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt index 35e4047109d5..97fa6eb17b5b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/NotificationScrimNestedScrollConnectionTest.kt @@ -31,6 +31,7 @@ import org.junit.runner.RunWith @RunWith(AndroidJUnit4::class) class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { private var isStarted = false + private var wasStarted = false private var scrimOffset = 0f private var contentHeight = 0f private var isCurrentGestureOverscroll = false @@ -46,7 +47,10 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { minVisibleScrimHeight = { MIN_VISIBLE_SCRIM_HEIGHT }, isCurrentGestureOverscroll = { isCurrentGestureOverscroll }, onStart = { isStarted = true }, - onStop = { isStarted = false }, + onStop = { + wasStarted = true + isStarted = false + }, ) @Test @@ -180,6 +184,7 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { ) assertThat(offsetConsumed).isEqualTo(Offset.Zero) + assertThat(wasStarted).isEqualTo(false) assertThat(isStarted).isEqualTo(false) } @@ -196,7 +201,9 @@ class NotificationScrimNestedScrollConnectionTest : SysuiTestCase() { ) assertThat(offsetConsumed).isEqualTo(Offset.Zero) - assertThat(isStarted).isEqualTo(true) + // Returning 0 offset will immediately stop the connection + assertThat(wasStarted).isEqualTo(true) + assertThat(isStarted).isEqualTo(false) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java index d04d6fc5f593..0947cd5aaabb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/FeedbackInfoTest.java @@ -45,12 +45,12 @@ import android.content.res.Configuration; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.service.notification.StatusBarNotification; -import android.testing.UiThreadTest; import android.view.LayoutInflater; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java index 22f1e4604bbd..6435e8203d3d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java @@ -24,9 +24,9 @@ import static org.mockito.Mockito.mock; import android.provider.Settings; import android.testing.TestableResources; -import android.testing.UiThreadTest; import android.util.KeyValueListParser; +import androidx.test.annotation.UiThreadTest; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt index 8d678ef00b4a..bf144729dea3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationListViewModelTest.kt @@ -21,7 +21,6 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import android.platform.test.annotations.EnableFlags import android.platform.test.flag.junit.FlagsParameterization import androidx.test.filters.SmallTest -import com.android.app.tracing.coroutines.flow.map import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue import com.android.systemui.flags.DisableSceneContainer @@ -51,6 +50,7 @@ import com.android.systemui.util.ui.isAnimating import com.android.systemui.util.ui.value import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.map import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt index c804fc6990ae..ba5fb7f8df00 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProviderTest.kt @@ -98,7 +98,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) var chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) @@ -135,7 +135,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) chipBounds = getPrivacyChipBoundingRectForInsets(bounds, dotWidth, chipWidth, isRtl) @@ -164,8 +164,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { val chipWidth = 30 val dotWidth = 10 val isRtl = false - val contentRect = - Rect(/* left = */ 0, /* top = */ 10, /* right = */ 1000, /* bottom = */ 100) + val contentRect = Rect(/* left= */ 0, /* top= */ 10, /* right= */ 1000, /* bottom= */ 100) val chipBounds = getPrivacyChipBoundingRectForInsets(contentRect, dotWidth, chipWidth, isRtl) @@ -207,7 +206,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -228,7 +227,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -251,7 +250,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -263,7 +262,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, 0, screenBounds.height() - dcBounds.height() - dotWidth, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -278,7 +277,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -320,7 +319,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -331,7 +330,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { protectionBounds.bottom, 0, screenBounds.height() - minRightPadding, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -346,7 +345,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -369,7 +368,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -381,7 +380,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, 0, screenBounds.height() - protectionBounds.bottom - dotWidth, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -396,7 +395,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -415,7 +414,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = screenBounds.right - dcWidth, top = 0, right = screenBounds.right, - bottom = dcHeight + bottom = dcHeight, ) val dcBoundsLandscape = Rect(left = 0, top = 0, right = dcHeight, bottom = dcWidth) val dcBoundsSeascape = @@ -423,14 +422,14 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = screenBounds.right - dcHeight, top = screenBounds.bottom - dcWidth, right = screenBounds.right - dcHeight, - bottom = screenBounds.bottom - dcWidth + bottom = screenBounds.bottom - dcWidth, ) val dcBoundsUpsideDown = Rect( left = 0, top = screenBounds.bottom - dcHeight, right = dcWidth, - bottom = screenBounds.bottom - dcHeight + bottom = screenBounds.bottom - dcHeight, ) val minLeftPadding = 20 val minRightPadding = 20 @@ -448,7 +447,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = minLeftPadding, top = 0, right = dcBoundsPortrait.left - dotWidth, - bottom = sbHeightPortrait + bottom = sbHeightPortrait, ) var bounds = @@ -463,7 +462,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -475,7 +474,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = dcBoundsLandscape.height(), top = 0, right = screenBounds.height() - minRightPadding, - bottom = sbHeightLandscape + bottom = sbHeightLandscape, ) bounds = @@ -490,7 +489,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -502,7 +501,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = minLeftPadding, top = 0, right = screenBounds.width() - minRightPadding, - bottom = sbHeightPortrait + bottom = sbHeightPortrait, ) bounds = @@ -517,7 +516,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -529,7 +528,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left = minLeftPadding, top = 0, right = screenBounds.height() - minRightPadding, - bottom = sbHeightLandscape + bottom = sbHeightLandscape, ) bounds = @@ -544,7 +543,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -584,7 +583,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -595,7 +594,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { protectionBounds.bottom, 0, screenBounds.height() - minRightPadding, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -610,7 +609,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -633,7 +632,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -645,7 +644,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, 0, screenBounds.height() - protectionBounds.bottom - dotWidth, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -660,7 +659,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -682,7 +681,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl = false, dotWidth = 10, bottomAlignedMargin = BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight = 15 + statusBarContentHeight = 15, ) assertThat(bounds.top).isEqualTo(0) @@ -704,7 +703,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl = false, dotWidth = 10, bottomAlignedMargin = 5, - statusBarContentHeight = 15 + statusBarContentHeight = 15, ) // Content in the status bar is centered vertically. To achieve the bottom margin we want, @@ -756,7 +755,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -777,7 +776,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -798,7 +797,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -809,7 +808,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { minLeftPadding, 0, screenBounds.height() - dcBounds.height() - dotWidth, - sbHeightLandscape + sbHeightLandscape, ) bounds = @@ -824,7 +823,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -860,7 +859,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -880,7 +879,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -900,7 +899,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -920,7 +919,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) } @@ -958,7 +957,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { isRtl, dotWidth, BOTTOM_ALIGNED_MARGIN_NONE, - statusBarContentHeight + statusBarContentHeight, ) assertRects(expectedBounds, bounds, currentRotation, targetRotation) @@ -968,12 +967,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { fun testDisplayChanged_returnsUpdatedInsets() { // GIVEN: get insets on the first display and switch to the second display val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160)) @@ -993,12 +992,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { // GIVEN: get insets on the first display, switch to the second display, // get insets and switch back val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) configuration.windowConfiguration.setMaxBounds(Rect(0, 0, 1080, 2160)) @@ -1024,12 +1023,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { configuration.windowConfiguration.setMaxBounds(0, 0, 100, 100) configurationController.onConfigurationChanged(configuration) val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) val listener = object : StatusBarContentInsetsChangedListener { @@ -1053,12 +1052,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { fun onDensityOrFontScaleChanged_listenerNotified() { configuration.densityDpi = 12 val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) val listener = object : StatusBarContentInsetsChangedListener { @@ -1081,12 +1080,12 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { @Test fun onThemeChanged_listenerNotified() { val provider = - StatusBarContentInsetsProvider( + StatusBarContentInsetsProviderImpl( contextMock, configurationController, mock<DumpManager>(), mock<CommandRegistry>(), - mock<SysUICutoutProvider>() + mock<SysUICutoutProvider>(), ) val listener = object : StatusBarContentInsetsChangedListener { @@ -1108,13 +1107,13 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { expected: Rect, actual: Rect, @Rotation currentRotation: Int, - @Rotation targetRotation: Int + @Rotation targetRotation: Int, ) { assertTrue( "Rects must match. currentRotation=${RotationUtils.toString(currentRotation)}" + " targetRotation=${RotationUtils.toString(targetRotation)}" + " expected=$expected actual=$actual", - expected.equals(actual) + expected.equals(actual), ) } @@ -1126,7 +1125,7 @@ class StatusBarContentInsetsProviderTest : SysuiTestCase() { left: Rect = Rect(), top: Rect = Rect(), right: Rect = Rect(), - bottom: Rect = Rect() + bottom: Rect = Rect(), ) { whenever(dc.boundingRects) .thenReturn(listOf(left, top, right, bottom).filter { !it.isEmpty }) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java index 1d74331e429b..94753f7e5203 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java @@ -34,7 +34,6 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -43,9 +42,7 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; -import android.service.trust.TrustAgentService; import android.testing.TestableLooper; -import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewRootImpl; @@ -67,7 +64,6 @@ import com.android.keyguard.KeyguardMessageAreaController; import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; import com.android.keyguard.KeyguardUpdateMonitorCallback; -import com.android.keyguard.TrustGrantFlags; import com.android.keyguard.ViewMediatorCallback; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; @@ -580,22 +576,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - @DisableSceneContainer - @DisableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testShowAlternateBouncer_unlockingWithBiometricAllowed() { - // GIVEN will show alternate bouncer - when(mPrimaryBouncerInteractor.isFullyShowing()).thenReturn(false); - when(mAlternateBouncerInteractor.show()).thenReturn(true); - - // WHEN showGenericBouncer is called - mStatusBarKeyguardViewManager.showBouncer(true); - - // THEN alt auth bouncer is shown - verify(mAlternateBouncerInteractor).show(); - verify(mPrimaryBouncerInteractor, never()).show(anyBoolean()); - } - - @Test public void testUpdateResources_delegatesToBouncer() { mStatusBarKeyguardViewManager.updateResources(); @@ -841,145 +821,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { } @Test - @EnableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void handleDispatchTouchEvent_alternateBouncerViewFlagEnabled() { - mStatusBarKeyguardViewManager.addCallback(mCallback); - - // GIVEN alternate bouncer view flag enabled & the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // THEN the touch is not acted upon - verify(mCallback, never()).onTouch(any()); - } - - @Test - @EnableFlags(com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void onInterceptTouch_alternateBouncerViewFlagEnabled() { - // GIVEN alternate bouncer view flag enabled & the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // THEN the touch is not intercepted - assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - } - - @Test - public void handleDispatchTouchEvent_alternateBouncerNotVisible() { - mStatusBarKeyguardViewManager.addCallback(mCallback); - - // GIVEN the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - - // THEN handleDispatchTouchEvent doesn't use the touches - assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertFalse(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - - // THEN the touch is not acted upon - verify(mCallback, never()).onTouch(any()); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void handleDispatchTouchEvent_shouldInterceptTouchAndHandleTouch() { - mStatusBarKeyguardViewManager.addCallback(mCallback); - - // GIVEN the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN all touches are NOT the udfps overlay - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - - // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent - // to its child views (handleDispatchTouchEvent returns true) - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - - // THEN the touch is acted upon once for each dispatchTOuchEvent call - verify(mCallback, times(3)).onTouch(any()); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void handleDispatchTouchEvent_shouldInterceptTouchButNotHandleTouch() { - mStatusBarKeyguardViewManager.addCallback(mCallback); - - // GIVEN the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN all touches are within the udfps overlay - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(true); - - // THEN handleDispatchTouchEvent eats/intercepts the touches so motion events aren't sent - // to its child views (handleDispatchTouchEvent returns true) - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.dispatchTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - - // THEN the touch is NOT acted upon at the moment - verify(mCallback, never()).onTouch(any()); - } - - @Test - @DisableSceneContainer - public void shouldInterceptTouch_alternateBouncerNotVisible() { - // GIVEN the alternate bouncer is not visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - - // THEN no motion events are intercepted - assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertFalse(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void shouldInterceptTouch_alternateBouncerVisible() { - // GIVEN the alternate bouncer is visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // THEN all motion events are intercepted - assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0) - )); - assertTrue(mStatusBarKeyguardViewManager.shouldInterceptTouchEvent( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_MOVE, 0f, 0f, 0) - )); - } - - @Test public void alternateBouncerToShowPrimaryBouncer_updatesScrimControllerOnce() { // GIVEN the alternate bouncer has shown and calls to hide() will result in successfully // hiding it @@ -997,106 +838,6 @@ public class StatusBarKeyguardViewManagerTest extends SysuiTestCase { @Test @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void alternateBouncerOnTouch_actionDownThenUp_noMinTimeShown_noHideAltBouncer() { - reset(mAlternateBouncerInteractor); - - // GIVEN the alternate bouncer has shown for a minimum amount of time - when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(false); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - - // WHEN ACTION_DOWN and ACTION_UP touch event comes - boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); - when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true); - boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - - // THEN the touches are handled (doesn't let touches through to underlying views) - assertTrue(touchHandledDown); - assertTrue(touchHandledUp); - - // THEN alternate bouncer does NOT attempt to hide since min showing time wasn't met - verify(mAlternateBouncerInteractor, never()).hide(); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void alternateBouncerOnTouch_actionDownThenUp_handlesTouch_hidesAltBouncer() { - reset(mAlternateBouncerInteractor); - - // GIVEN the alternate bouncer has shown for a minimum amount of time - when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - - // WHEN ACTION_DOWN and ACTION_UP touch event comes - boolean touchHandledDown = mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_DOWN, 0f, 0f, 0)); - when(mAlternateBouncerInteractor.getReceivedDownTouch()).thenReturn(true); - boolean touchHandledUp = mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - - // THEN the touches are handled - assertTrue(touchHandledDown); - assertTrue(touchHandledUp); - - // THEN alternate bouncer attempts to hide - verify(mAlternateBouncerInteractor).hide(); - } - - @Test - @DisableSceneContainer - public void alternateBouncerOnTouch_actionUp_doesNotHideAlternateBouncer() { - reset(mAlternateBouncerInteractor); - - // GIVEN the alternate bouncer has shown for a minimum amount of time - when(mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()).thenReturn(true); - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(any())).thenReturn(false); - - // WHEN only ACTION_UP touch event comes - mStatusBarKeyguardViewManager.onTouch( - MotionEvent.obtain(0L, 0L, MotionEvent.ACTION_UP, 0f, 0f, 0)); - - // THEN the alternateBouncer doesn't hide - verify(mAlternateBouncerInteractor, never()).hide(); - } - - @Test - @DisableSceneContainer - @DisableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void onTrustChanged_hideAlternateBouncerAndClearMessageArea() { - // GIVEN keyguard update monitor callback is registered - verify(mKeyguardUpdateMonitor).registerCallback(mKeyguardUpdateMonitorCallback.capture()); - - reset(mKeyguardUpdateMonitor); - reset(mKeyguardMessageAreaController); - - // GIVEN alternate bouncer state = not visible - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(false); - - // WHEN the device is trusted by active unlock - mKeyguardUpdateMonitorCallback.getValue().onTrustGrantedForCurrentUser( - true, - true, - new TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD - | TrustAgentService.FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE), - null - ); - - // THEN the false visibility state is propagated to the keyguardUpdateMonitor - verify(mKeyguardUpdateMonitor).setAlternateBouncerShowing(eq(false)); - - // THEN message area visibility updated to FALSE with empty message - verify(mKeyguardMessageAreaController).setIsVisible(eq(false)); - verify(mKeyguardMessageAreaController).setMessage(eq("")); - } - - @Test - @DisableSceneContainer @DisableFlags(Flags.FLAG_SIM_PIN_RACE_CONDITION_ON_RESTART) public void testShowBouncerOrKeyguard_needsFullScreen() { when(mKeyguardSecurityModel.getSecurityMode(anyInt())).thenReturn( diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt index 40c3f221e2df..29e9ba752b36 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizerTest.kt @@ -61,6 +61,26 @@ class BackGestureRecognizerTest : SysuiTestCase() { } @Test + fun triggersProgressRelativeToDistance() { + assertProgressWhileMovingFingers(deltaX = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f) + assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE / 2, expectedProgress = 0.5f) + assertProgressWhileMovingFingers(deltaX = -SWIPE_DISTANCE, expectedProgress = 1f) + assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE, expectedProgress = 1f) + } + + private fun assertProgressWhileMovingFingers(deltaX: Float, expectedProgress: Float) { + assertStateAfterEvents( + events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaX = deltaX) }, + expectedState = InProgress(progress = expectedProgress), + ) + } + + @Test + fun triggeredProgressIsNoBiggerThanOne() { + assertProgressWhileMovingFingers(deltaX = SWIPE_DISTANCE * 2, expectedProgress = 1f) + } + + @Test fun doesntTriggerGestureFinished_onGestureDistanceTooShort() { assertStateAfterEvents( events = ThreeFingerGesture.swipeLeft(distancePx = SWIPE_DISTANCE / 2), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt index 8406d3b99bac..ff0cec5e06e9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/EasterEggGestureTest.kt @@ -104,7 +104,8 @@ class EasterEggGestureTest : SysuiTestCase() { } private fun assertStateAfterTwoFingerGesture(gesturePath: List<Point>, wasTriggered: Boolean) { - val events = TwoFingerGesture.createEvents { gesturePath.forEach { (x, y) -> move(x, y) } } + val events = + TwoFingerGesture.eventsForFullGesture { gesturePath.forEach { (x, y) -> move(x, y) } } assertStateAfterEvents(events = events, wasTriggered = wasTriggered) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt index 043b77577978..7d3ed92cecc6 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizerTest.kt @@ -56,6 +56,27 @@ class HomeGestureRecognizerTest : SysuiTestCase() { } @Test + fun triggersProgressRelativeToDistance() { + assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f) + assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE, expectedProgress = 1f) + } + + private fun assertProgressWhileMovingFingers(deltaY: Float, expectedProgress: Float) { + assertStateAfterEvents( + events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaY = deltaY) }, + expectedState = InProgress(progress = expectedProgress), + ) + } + + @Test + fun triggeredProgressIsBetweenZeroAndOne() { + // going in the wrong direction + assertProgressWhileMovingFingers(deltaY = SWIPE_DISTANCE / 2, expectedProgress = 0f) + // going further than required distance + assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE * 2, expectedProgress = 1f) + } + + @Test fun doesntTriggerGestureFinished_onGestureDistanceTooShort() { assertStateAfterEvents( events = ThreeFingerGesture.swipeUp(distancePx = SWIPE_DISTANCE / 2), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt index 7095a91a4e5d..c5c0d59ea48b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizerTest.kt @@ -77,11 +77,32 @@ class RecentAppsGestureRecognizerTest : SysuiTestCase() { fun triggersGestureProgressForThreeFingerGestureStarted() { assertStateAfterEvents( events = ThreeFingerGesture.startEvents(x = 0f, y = 0f), - expectedState = InProgress(), + expectedState = InProgress(progress = 0f), ) } @Test + fun triggersProgressRelativeToDistance() { + assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE / 2, expectedProgress = 0.5f) + assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE, expectedProgress = 1f) + } + + private fun assertProgressWhileMovingFingers(deltaY: Float, expectedProgress: Float) { + assertStateAfterEvents( + events = ThreeFingerGesture.eventsForGestureInProgress { move(deltaY = deltaY) }, + expectedState = InProgress(progress = expectedProgress), + ) + } + + @Test + fun triggeredProgressIsBetweenZeroAndOne() { + // going in the wrong direction + assertProgressWhileMovingFingers(deltaY = SWIPE_DISTANCE / 2, expectedProgress = 0f) + // going further than required distance + assertProgressWhileMovingFingers(deltaY = -SWIPE_DISTANCE * 2, expectedProgress = 1f) + } + + @Test fun doesntTriggerGestureFinished_onGestureDistanceTooShort() { assertStateAfterEvents( events = ThreeFingerGesture.swipeUp(distancePx = SWIPE_DISTANCE / 2), diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt index 296d4dce8ce4..42fe1e5d6bec 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilder.kt @@ -25,11 +25,23 @@ import android.view.MotionEvent.ACTION_UP import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_X import com.android.systemui.touchpad.tutorial.ui.gesture.MultiFingerGesture.Companion.DEFAULT_Y -/** - * Interface for gesture builders which support creating list of [MotionEvent] for common swipe - * gestures. For simple usage see swipe* methods or use [createEvents] for more specific scenarios. - */ -interface MultiFingerGesture { +/** Given gesture move events can build list of [MotionEvent]s included in that gesture */ +interface GestureEventsBuilder { + /** + * Creates full gesture including provided move events. This means returned events include DOWN, + * MOVE and UP. Note that move event's x and y is always relative to the starting one. + */ + fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> + + /** + * Creates partial gesture including provided move events. This means returned events include + * DOWN and MOVE. Note that move event's x and y is always relative to the starting one. + */ + fun eventsForGestureInProgress(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> +} + +/** Support creating list of [MotionEvent] for common swipe gestures. */ +interface MultiFingerGesture : GestureEventsBuilder { companion object { const val SWIPE_DISTANCE = 100f @@ -37,27 +49,41 @@ interface MultiFingerGesture { const val DEFAULT_Y = 500f } - fun swipeUp(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaY = -distancePx) } - - fun swipeDown(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaY = distancePx) } + fun swipeUp(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture { + move(deltaY = -distancePx) + } - fun swipeRight(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaX = distancePx) } + fun swipeDown(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture { + move(deltaY = distancePx) + } - fun swipeLeft(distancePx: Float = SWIPE_DISTANCE) = createEvents { move(deltaX = -distancePx) } + fun swipeRight(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture { + move(deltaX = distancePx) + } - /** - * Creates gesture with provided move events. Note that move event's x and y is always relative - * to the starting one - */ - fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> + fun swipeLeft(distancePx: Float = SWIPE_DISTANCE) = eventsForFullGesture { + move(deltaX = -distancePx) + } } object ThreeFingerGesture : MultiFingerGesture { - override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> { - return touchpadGesture( + + private val moveEventsBuilder = MoveEventsBuilder(::threeFingerEvent) + + override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> { + return buildGesture( + startEvents = { x, y -> startEvents(x, y) }, + moveEvents = moveEventsBuilder.getEvents(moveEvents), + endEvents = { x, y -> endEvents(x, y) }, + ) + } + + override fun eventsForGestureInProgress( + moveEvents: MoveEventsBuilder.() -> Unit + ): List<MotionEvent> { + return buildGesture( startEvents = { x, y -> startEvents(x, y) }, - moveEvents = GestureBuilder(::threeFingerEvent).apply { moveEvents() }.events, - endEvents = { x, y -> endEvents(x, y) } + moveEvents = moveEventsBuilder.getEvents(moveEvents), ) } @@ -65,7 +91,7 @@ object ThreeFingerGesture : MultiFingerGesture { return listOf( threeFingerEvent(ACTION_DOWN, x, y), threeFingerEvent(ACTION_POINTER_DOWN, x, y), - threeFingerEvent(ACTION_POINTER_DOWN, x, y) + threeFingerEvent(ACTION_POINTER_DOWN, x, y), ) } @@ -73,32 +99,43 @@ object ThreeFingerGesture : MultiFingerGesture { return listOf( threeFingerEvent(ACTION_POINTER_UP, x, y), threeFingerEvent(ACTION_POINTER_UP, x, y), - threeFingerEvent(ACTION_UP, x, y) + threeFingerEvent(ACTION_UP, x, y), ) } private fun threeFingerEvent( action: Int, x: Float = DEFAULT_X, - y: Float = DEFAULT_Y + y: Float = DEFAULT_Y, ): MotionEvent { return touchpadEvent( action = action, x = x, y = y, classification = MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE, - axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f) + axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 3f), ) } } object FourFingerGesture : MultiFingerGesture { - override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> { - return touchpadGesture( + private val moveEventsBuilder = MoveEventsBuilder(::fourFingerEvent) + + override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> { + return buildGesture( + startEvents = { x, y -> startEvents(x, y) }, + moveEvents = moveEventsBuilder.getEvents(moveEvents), + endEvents = { x, y -> endEvents(x, y) }, + ) + } + + override fun eventsForGestureInProgress( + moveEvents: MoveEventsBuilder.() -> Unit + ): List<MotionEvent> { + return buildGesture( startEvents = { x, y -> startEvents(x, y) }, - moveEvents = GestureBuilder(::fourFingerEvent).apply { moveEvents() }.events, - endEvents = { x, y -> endEvents(x, y) } + moveEvents = moveEventsBuilder.getEvents(moveEvents), ) } @@ -107,7 +144,7 @@ object FourFingerGesture : MultiFingerGesture { fourFingerEvent(ACTION_DOWN, x, y), fourFingerEvent(ACTION_POINTER_DOWN, x, y), fourFingerEvent(ACTION_POINTER_DOWN, x, y), - fourFingerEvent(ACTION_POINTER_DOWN, x, y) + fourFingerEvent(ACTION_POINTER_DOWN, x, y), ) } @@ -116,61 +153,74 @@ object FourFingerGesture : MultiFingerGesture { fourFingerEvent(ACTION_POINTER_UP, x, y), fourFingerEvent(ACTION_POINTER_UP, x, y), fourFingerEvent(ACTION_POINTER_UP, x, y), - fourFingerEvent(ACTION_UP, x, y) + fourFingerEvent(ACTION_UP, x, y), ) } private fun fourFingerEvent( action: Int, x: Float = DEFAULT_X, - y: Float = DEFAULT_Y + y: Float = DEFAULT_Y, ): MotionEvent { return touchpadEvent( action = action, x = x, y = y, classification = MotionEvent.CLASSIFICATION_MULTI_FINGER_SWIPE, - axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 4f) + axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 4f), ) } } object TwoFingerGesture : MultiFingerGesture { - override fun createEvents(moveEvents: GestureBuilder.() -> Unit): List<MotionEvent> { - return touchpadGesture( - startEvents = { x, y -> listOf(twoFingerEvent(ACTION_DOWN, x, y)) }, - moveEvents = GestureBuilder(::twoFingerEvent).apply { moveEvents() }.events, - endEvents = { x, y -> listOf(twoFingerEvent(ACTION_UP, x, y)) } + private val moveEventsBuilder = MoveEventsBuilder(::twoFingerEvent) + + override fun eventsForFullGesture(moveEvents: MoveEventsBuilder.() -> Unit): List<MotionEvent> { + return buildGesture( + startEvents = { x, y -> startEvents(x, y) }, + moveEvents = moveEventsBuilder.getEvents(moveEvents), + endEvents = { x, y -> listOf(twoFingerEvent(ACTION_UP, x, y)) }, + ) + } + + override fun eventsForGestureInProgress( + moveEvents: MoveEventsBuilder.() -> Unit + ): List<MotionEvent> { + return buildGesture( + startEvents = { x, y -> startEvents(x, y) }, + moveEvents = moveEventsBuilder.getEvents(moveEvents), ) } + private fun startEvents(x: Float, y: Float) = listOf(twoFingerEvent(ACTION_DOWN, x, y)) + private fun twoFingerEvent( action: Int, x: Float = DEFAULT_X, - y: Float = DEFAULT_Y + y: Float = DEFAULT_Y, ): MotionEvent { return touchpadEvent( action = action, x = x, y = y, classification = MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE, - axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 2f) + axisValues = mapOf(MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT to 2f), ) } } -private fun touchpadGesture( +private fun buildGesture( startEvents: (Float, Float) -> List<MotionEvent>, moveEvents: List<MotionEvent>, - endEvents: (Float, Float) -> List<MotionEvent> + endEvents: (Float, Float) -> List<MotionEvent> = { _, _ -> emptyList() }, ): List<MotionEvent> { val lastX = moveEvents.last().x val lastY = moveEvents.last().y return startEvents(DEFAULT_X, DEFAULT_Y) + moveEvents + endEvents(lastX, lastY) } -class GestureBuilder internal constructor(val eventBuilder: (Int, Float, Float) -> MotionEvent) { +class MoveEventsBuilder internal constructor(val eventBuilder: (Int, Float, Float) -> MotionEvent) { val events = mutableListOf<MotionEvent>() @@ -178,3 +228,11 @@ class GestureBuilder internal constructor(val eventBuilder: (Int, Float, Float) events.add(eventBuilder(ACTION_MOVE, DEFAULT_X + deltaX, DEFAULT_Y + deltaY)) } } + +private fun MoveEventsBuilder.getEvents( + moveEvents: MoveEventsBuilder.() -> Unit +): List<MotionEvent> { + events.clear() + this.moveEvents() + return events +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt index 13ebb42531b8..64136775b4eb 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureBuilderTest.kt @@ -54,7 +54,28 @@ class TouchpadGestureBuilderTest : SysuiTestCase() { ACTION_MOVE, ACTION_POINTER_UP, ACTION_POINTER_UP, - ACTION_UP + ACTION_UP, + ) + .inOrder() + } + + @Test + fun threeFingerGestureInProgressProducesCorrectEvents() { + val events = + ThreeFingerGesture.eventsForGestureInProgress { + move(deltaX = 10f) + move(deltaX = 20f) + } + + val actions = events.map { it.actionMasked } + assertWithMessage("Events have expected action type") + .that(actions) + .containsExactly( + ACTION_DOWN, + ACTION_POINTER_DOWN, + ACTION_POINTER_DOWN, + ACTION_MOVE, + ACTION_MOVE, ) .inOrder() } @@ -80,7 +101,7 @@ class TouchpadGestureBuilderTest : SysuiTestCase() { ACTION_POINTER_UP, ACTION_POINTER_UP, ACTION_POINTER_UP, - ACTION_UP + ACTION_UP, ) .inOrder() } @@ -109,7 +130,7 @@ class TouchpadGestureBuilderTest : SysuiTestCase() { @Test fun gestureBuilderProducesCorrectEventCoordinates() { val events = - ThreeFingerGesture.createEvents { + ThreeFingerGesture.eventsForFullGesture { move(deltaX = 50f) move(deltaX = 100f) } @@ -127,7 +148,7 @@ class TouchpadGestureBuilderTest : SysuiTestCase() { // up events DEFAULT_X + 100f to DEFAULT_Y, DEFAULT_X + 100f to DEFAULT_Y, - DEFAULT_X + 100f to DEFAULT_Y + DEFAULT_X + 100f to DEFAULT_Y, ) .inOrder() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt index a867eb38b44c..c302b40fc4d7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/touchpad/tutorial/ui/gesture/TouchpadGestureHandlerTest.kt @@ -85,7 +85,7 @@ class TouchpadGestureHandlerTest : SysuiTestCase() { } private fun backGestureEvents(): List<MotionEvent> { - return ThreeFingerGesture.createEvents { + return ThreeFingerGesture.eventsForFullGesture { move(deltaX = SWIPE_DISTANCE / 4) move(deltaX = SWIPE_DISTANCE / 2) move(deltaX = SWIPE_DISTANCE) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt index b8f581574848..a4b3916b7a55 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/util/drawable/DrawableSizeTest.kt @@ -35,7 +35,7 @@ class DrawableSizeTest : SysuiTestCase() { val drawable = BitmapDrawable( resources, - Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888) + Bitmap.createBitmap(resources.displayMetrics, 150, 150, Bitmap.Config.ARGB_8888), ) val result = DrawableSize.downscaleToSize(resources, drawable, 300, 300) assertThat(result).isSameInstanceAs(drawable) @@ -48,7 +48,7 @@ class DrawableSizeTest : SysuiTestCase() { val drawable = BitmapDrawable( resources, - Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888) + Bitmap.createBitmap(resources.displayMetrics, 150, 75, Bitmap.Config.ARGB_8888), ) val result = DrawableSize.downscaleToSize(resources, drawable, 75, 75) @@ -64,4 +64,31 @@ class DrawableSizeTest : SysuiTestCase() { val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) assertThat(result).isSameInstanceAs(drawable) } + + @Test + fun testDownscaleToSize_layerDrawable_allLayersSameType_resized() { + val drawable = + resources.getDrawable( + com.android.systemui.tests.R.drawable.layer_drawable_all_same_type, + resources.newTheme(), + ) + + val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) + + assertThat(result).isNotSameInstanceAs(drawable) + } + + /** Regression test for b/244282477. */ + @Test + fun testDownscaleToSize_layerDrawable_layersAreDifferentTypes_unchanged() { + val drawable = + resources.getDrawable( + com.android.systemui.tests.R.drawable.layer_drawable_different_types, + resources.newTheme(), + ) + + val result = DrawableSize.downscaleToSize(resources, drawable, 1, 1) + + assertThat(result).isSameInstanceAs(drawable) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorTest.kt new file mode 100644 index 000000000000..7361de7d21b0 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorTest.kt @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2024 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.volume.panel.component.volume.domain.interactor + +import android.media.AudioManager +import android.testing.TestableLooper +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.settingslib.volume.shared.model.AudioStream +import com.android.systemui.SysuiTestCase +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.testKosmos +import com.android.systemui.volume.data.repository.audioRepository +import com.android.systemui.volume.panel.component.volume.domain.model.SliderType +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 + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +class AudioSlidersInteractorTest : SysuiTestCase() { + + private val kosmos = testKosmos() + + private lateinit var underTest: AudioSlidersInteractor + + @Before + fun setUp() = + with(kosmos) { + audioRepository.setMode(AudioManager.MODE_NORMAL) + underTest = audioSlidersInteractor + } + + @Test + fun shouldAddAllStreams_notInCall() = + with(kosmos) { + testScope.runTest { + val sliders by collectLastValue(underTest.volumePanelSliders) + runCurrent() + + assertThat(sliders).isEqualTo( + mutableListOf( + AudioManager.STREAM_MUSIC, + AudioManager.STREAM_VOICE_CALL, + AudioManager.STREAM_RING, + AudioManager.STREAM_NOTIFICATION, + AudioManager.STREAM_ALARM + ).map { SliderType.Stream(AudioStream(it)) }) + } + } + + @Test + fun shouldAddAllStreams_inCall() = + with(kosmos) { + testScope.runTest { + audioRepository.setMode(AudioManager.MODE_IN_CALL) + + val sliders by collectLastValue(underTest.volumePanelSliders) + runCurrent() + + // Call stream is before music stream while in call. + assertThat(sliders).isEqualTo( + mutableListOf( + AudioManager.STREAM_VOICE_CALL, + AudioManager.STREAM_MUSIC, + AudioManager.STREAM_RING, + AudioManager.STREAM_NOTIFICATION, + AudioManager.STREAM_ALARM + ).map { SliderType.Stream(AudioStream(it)) }) + } + } +} diff --git a/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml index 627b92b8a779..3c1668405909 100644 --- a/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml +++ b/packages/SystemUI/res/drawable/qs_hearing_devices_related_tools_background.xml @@ -21,11 +21,8 @@ android:color="?android:attr/colorControlHighlight"> <item> <shape android:shape="rectangle"> - <solid android:color="@android:color/transparent"/> + <solid android:color="?androidprv:attr/materialColorPrimaryContainer"/> <corners android:radius="@dimen/hearing_devices_preset_spinner_background_radius"/> - <stroke - android:width="1dp" - android:color="?androidprv:attr/textColorTertiary" /> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml index 4a7bef9f48b9..80692f9481b7 100644 --- a/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml +++ b/packages/SystemUI/res/layout/hearing_devices_tile_dialog.xml @@ -72,7 +72,6 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toBottomOf="@id/device_function_barrier" - app:layout_constraintBottom_toTopOf="@id/related_tools_scroll" android:drawableStart="@drawable/ic_add" android:drawablePadding="20dp" android:drawableTint="?android:attr/textColorPrimary" @@ -92,24 +91,16 @@ app:barrierDirection="bottom" app:constraint_referenced_ids="device_function_barrier, pair_new_device_button" /> - <HorizontalScrollView - android:id="@+id/related_tools_scroll" + <LinearLayout + android:id="@+id/related_tools_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="@dimen/bluetooth_dialog_layout_margin" android:layout_marginEnd="@dimen/bluetooth_dialog_layout_margin" android:layout_marginTop="@dimen/hearing_devices_layout_margin" - android:scrollbars="none" - android:fillViewport="true" + android:orientation="horizontal" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/preset_spinner"> - <LinearLayout - android:id="@+id/related_tools_container" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal"> - </LinearLayout> - </HorizontalScrollView> + app:layout_constraintTop_toBottomOf="@id/device_barrier" /> </androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/hearing_tool_item.xml b/packages/SystemUI/res/layout/hearing_tool_item.xml index ff2fbe070e0f..f5baf2aaf6dc 100644 --- a/packages/SystemUI/res/layout/hearing_tool_item.xml +++ b/packages/SystemUI/res/layout/hearing_tool_item.xml @@ -17,8 +17,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/tool_view" - android:layout_width="match_parent" - android:layout_height="match_parent" + android:layout_width="0dp" + android:layout_height="wrap_content" android:orientation="vertical" android:gravity="top|center_horizontal" android:focusable="true" @@ -26,8 +26,10 @@ android:layout_weight="1"> <FrameLayout android:id="@+id/icon_frame" - android:layout_width="@dimen/hearing_devices_tool_icon_frame_width" - android:layout_height="@dimen/hearing_devices_tool_icon_frame_height" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:paddingTop="20dp" + android:paddingBottom="20dp" android:background="@drawable/qs_hearing_devices_related_tools_background" android:focusable="false" > <ImageView diff --git a/packages/SystemUI/res/layout/ongoing_activity_chip.xml b/packages/SystemUI/res/layout/ongoing_activity_chip.xml index 690a89a044b7..d0a1ce8ae629 100644 --- a/packages/SystemUI/res/layout/ongoing_activity_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_activity_chip.xml @@ -45,31 +45,26 @@ android:tint="?android:attr/colorPrimary" /> - <!-- Only one of [ongoing_activity_chip_time, ongoing_activity_chip_text] will ever - be shown at one time. --> + <!-- Only one of [ongoing_activity_chip_time, ongoing_activity_chip_text, + ongoing_activity_chip_short_time_delta] will ever be shown at one time. --> + + <!-- Shows a timer, like 00:01. --> <com.android.systemui.statusbar.chips.ui.view.ChipChronometer android:id="@+id/ongoing_activity_chip_time" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:gravity="center|start" - android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding" - android:textAppearance="@android:style/TextAppearance.Material.Small" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:textColor="?android:attr/colorPrimary" + style="@style/StatusBar.Chip.Text" /> - <!-- Used to show generic text in the chip instead of a timer. --> + <!-- Shows generic text. --> <TextView android:id="@+id/ongoing_activity_chip_text" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:singleLine="true" - android:gravity="center|start" - android:paddingStart="@dimen/ongoing_activity_chip_icon_text_padding" - android:textAppearance="@android:style/TextAppearance.Material.Small" - android:fontFamily="@*android:string/config_headlineFontFamily" - android:textColor="?android:attr/colorPrimary" + style="@style/StatusBar.Chip.Text" + android:visibility="gone" + /> + + <!-- Shows a time delta in short form, like "15min" or "1hr". --> + <android.widget.DateTimeView + android:id="@+id/ongoing_activity_chip_short_time_delta" + style="@style/StatusBar.Chip.Text" android:visibility="gone" /> diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml index e21466671363..77fbb642f664 100644 --- a/packages/SystemUI/res/layout/status_bar_expanded.xml +++ b/packages/SystemUI/res/layout/status_bar_expanded.xml @@ -128,12 +128,6 @@ <include layout="@layout/dock_info_bottom_area_overlay" /> - <com.android.keyguard.LockIconView - android:id="@+id/lock_icon_view" - android:layout_width="wrap_content" - android:layout_height="wrap_content"> - </com.android.keyguard.LockIconView> - <include layout="@layout/keyguard_bottom_area" android:visibility="gone" /> diff --git a/packages/SystemUI/res/layout/super_notification_shade.xml b/packages/SystemUI/res/layout/super_notification_shade.xml index 22d34eb7b115..fbb07bed4b50 100644 --- a/packages/SystemUI/res/layout/super_notification_shade.xml +++ b/packages/SystemUI/res/layout/super_notification_shade.xml @@ -58,22 +58,22 @@ android:layout_height="match_parent" android:visibility="invisible" /> - <!-- Shared container for the notification stack. Can be positioned by either - the keyguard_root_view or notification_panel --> - <com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer - android:id="@+id/shared_notification_container" + <!-- Root for all keyguard content. It was previously located within the shade. --> + <com.android.systemui.keyguard.ui.view.KeyguardRootView + android:id="@id/keyguard_root_view" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" - android:clipToPadding="false" /> - <!-- Root for all keyguard content. It was previously located within the shade. --> - <com.android.systemui.keyguard.ui.view.KeyguardRootView - android:id="@id/keyguard_root_view" + <!-- Shared container for the notification stack. Can be positioned by either + the keyguard_root_view or notification_panel --> + <com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer + android:id="@+id/shared_notification_container" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" + android:clipToPadding="false" /> <include layout="@layout/brightness_mirror_container" /> diff --git a/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml b/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml deleted file mode 100644 index 4799f8c5b668..000000000000 --- a/packages/SystemUI/res/layout/udfps_fpm_empty_view.xml +++ /dev/null @@ -1,30 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ Copyright (C) 2021 The Android Open Source Project - ~ - ~ Licensed under the Apache License, Version 2.0 (the "License"); - ~ you may not use this file except in compliance with the License. - ~ You may obtain a copy of the License at - ~ - ~ http://www.apache.org/licenses/LICENSE-2.0 - ~ - ~ Unless required by applicable law or agreed to in writing, software - ~ distributed under the License is distributed on an "AS IS" BASIS, - ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - ~ See the License for the specific language governing permissions and - ~ limitations under the License. - --> -<com.android.systemui.biometrics.UdfpsFpmEmptyView - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/udfps_animation_view" - android:layout_width="match_parent" - android:layout_height="match_parent"> - <!-- The layout height/width are placeholders, which will be overwritten by - FingerprintSensorPropertiesInternal. --> - <View - android:id="@+id/udfps_enroll_accessibility_view" - android:layout_gravity="center" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:contentDescription="@string/accessibility_fingerprint_label"/> -</com.android.systemui.biometrics.UdfpsFpmEmptyView> diff --git a/packages/SystemUI/res/layout/udfps_view.xml b/packages/SystemUI/res/layout/udfps_view.xml deleted file mode 100644 index 257d238f5c54..000000000000 --- a/packages/SystemUI/res/layout/udfps_view.xml +++ /dev/null @@ -1,31 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<!-- - ~ 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. - --> -<com.android.systemui.biometrics.UdfpsView - xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:systemui="http://schemas.android.com/apk/res-auto" - android:id="@+id/udfps_view" - android:layout_width="match_parent" - android:layout_height="match_parent" - systemui:sensorTouchAreaCoefficient="1.0" - android:contentDescription="@string/accessibility_fingerprint_label"> - - <ViewStub - android:id="@+id/animation_view" - android:layout_width="match_parent" - android:layout_height="match_parent"/> - -</com.android.systemui.biometrics.UdfpsView> diff --git a/packages/SystemUI/res/values-land/config.xml b/packages/SystemUI/res/values-land/config.xml index b5efeb5f6b3b..5d5b95546d0d 100644 --- a/packages/SystemUI/res/values-land/config.xml +++ b/packages/SystemUI/res/values-land/config.xml @@ -25,6 +25,12 @@ <integer name="quick_settings_num_columns">4</integer> + <!-- The number of rows in the paginated grid QuickSettings --> + <integer name="quick_settings_paginated_grid_num_rows">2</integer> + + <!-- The number of rows in the paginated grid QuickQuickSettings --> + <integer name="quick_qs_paginated_grid_num_rows">1</integer> + <!-- The number of columns in the infinite grid QuickSettings --> <integer name="quick_settings_infinite_grid_num_columns">8</integer> diff --git a/packages/SystemUI/res/values-sw600dp-land/config.xml b/packages/SystemUI/res/values-sw600dp-land/config.xml index fc6d20e11d3b..c661846d025f 100644 --- a/packages/SystemUI/res/values-sw600dp-land/config.xml +++ b/packages/SystemUI/res/values-sw600dp-land/config.xml @@ -27,6 +27,12 @@ <!-- Whether to use the split 2-column notification shade --> <bool name="config_use_split_notification_shade">true</bool> + <!-- The number of rows in the paginated grid QuickSettings --> + <integer name="quick_settings_paginated_grid_num_rows">3</integer> + + <!-- The number of rows in the paginated grid QuickQuickSettings --> + <integer name="quick_qs_paginated_grid_num_rows">2</integer> + <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">2</integer> diff --git a/packages/SystemUI/res/values-sw600dp-port/config.xml b/packages/SystemUI/res/values-sw600dp-port/config.xml index 7daad1a43f73..f556b97eefc2 100644 --- a/packages/SystemUI/res/values-sw600dp-port/config.xml +++ b/packages/SystemUI/res/values-sw600dp-port/config.xml @@ -21,6 +21,12 @@ <!-- The maximum number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">3</integer> + <!-- The number of rows in the paginated grid QuickSettings --> + <integer name="quick_settings_paginated_grid_num_rows">3</integer> + + <!-- The number of rows in the paginated grid QuickQuickSettings --> + <integer name="quick_qs_paginated_grid_num_rows">2</integer> + <!-- The number of columns in the QuickSettings --> <integer name="quick_settings_num_columns">3</integer> diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml index 6f94f9e2a216..7d840cfe949a 100644 --- a/packages/SystemUI/res/values/config.xml +++ b/packages/SystemUI/res/values/config.xml @@ -70,6 +70,12 @@ <!-- The number of rows in the QuickSettings --> <integer name="quick_settings_max_rows">4</integer> + <!-- The number of rows in the paginated grid QuickSettings --> + <integer name="quick_settings_paginated_grid_num_rows">4</integer> + + <!-- The number of rows in the paginated grid QuickQuickSettings --> + <integer name="quick_qs_paginated_grid_num_rows">2</integer> + <!-- The number of columns in the infinite grid QuickSettings --> <integer name="quick_settings_infinite_grid_num_columns">4</integer> @@ -128,17 +134,19 @@ <!-- Use collapsed layout for media player in landscape QQS --> <bool name="config_quickSettingsMediaLandscapeCollapsed">true</bool> - <!-- For hearing devices related tool list. Need to be in ComponentName format (package/class). - Should be activity to be launched. - Already contains tool that holds intent: "com.android.settings.action.live_caption". - Maximum number is 3. --> + <!-- Hearing devices related tool list. Each entry must be in ComponentName format + (package/class), indicating the specific application component to launch. + Already contains tool that handles intent: "com.android.settings.action.live_caption" by + default. You can add up to 2 additional related tools. --> <string-array name="config_quickSettingsHearingDevicesRelatedToolName" translatable="false"> </string-array> - <!-- The drawable resource names. If provided, it will replace the corresponding icons in - config_quickSettingsHearingDevicesRelatedToolName. Can be empty to use original icons. - Already contains tool that holds intent: "com.android.settings.action.live_caption". - Maximum number is 3. --> + <!-- Hearing devices related tool icon list. Provide drawable resource names in the same order + as the component names in config_quickSettingsHearingDevicesRelatedToolName. The icons + should be monochrome and will be tinted according to the system's material color. Ensure + the number of icon resources matches the number of components specified in + config_quickSettingsHearingDevicesRelatedToolName. If this array is empty or the sizes + don't match, the original application icons will be used. --> <string-array name="config_quickSettingsHearingDevicesRelatedToolIcon" translatable="false"> </string-array> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 6c8a7403953e..209a971814f4 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1798,8 +1798,6 @@ <dimen name="hearing_devices_preset_spinner_text_padding_vertical">15dp</dimen> <dimen name="hearing_devices_preset_spinner_arrow_size">24dp</dimen> <dimen name="hearing_devices_preset_spinner_background_radius">28dp</dimen> - <dimen name="hearing_devices_tool_icon_frame_width">94dp</dimen> - <dimen name="hearing_devices_tool_icon_frame_height">64dp</dimen> <dimen name="hearing_devices_tool_icon_size">28dp</dimen> <!-- Height percentage of the parent container occupied by the communal view --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index e1f25f99cc98..7cebac2feaba 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -70,6 +70,20 @@ <item name="android:fontWeight">700</item> </style> + <style name="StatusBar" /> + <style name="StatusBar.Chip" /> + + <style name="StatusBar.Chip.Text"> + <item name="android:layout_width">wrap_content</item> + <item name="android:layout_height">wrap_content</item> + <item name="android:singleLine">true</item> + <item name="android:gravity">center|start</item> + <item name="android:paddingStart">@dimen/ongoing_activity_chip_icon_text_padding</item> + <item name="android:textAppearance">@android:style/TextAppearance.Material.Small</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + <item name="android:textColor">?android:attr/colorPrimary</item> + </style> + <style name="Chipbar" /> <style name="Chipbar.Text" parent="@*android:style/TextAppearance.DeviceDefault.Notification.Title"> @@ -677,10 +691,12 @@ <style name="TunerSettings" parent="@android:style/Theme.DeviceDefault.Settings"> <item name="android:windowActionBar">false</item> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> <item name="preferenceTheme">@style/TunerPreferenceTheme</item> </style> <style name="TunerPreferenceTheme" parent="@style/PreferenceThemeOverlay.SettingsBase"> + <item name="android:windowOptOutEdgeToEdgeEnforcement">true</item> </style> <style name="TextAppearance.NotificationInfo.Confirmation"> diff --git a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt index b792db354b36..306d68217e50 100644 --- a/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt +++ b/packages/SystemUI/src/com/android/keyguard/EmptyLockIconViewController.kt @@ -17,6 +17,7 @@ package com.android.keyguard import android.view.MotionEvent +import android.view.View import com.android.systemui.dagger.SysUISingleton import com.android.systemui.keyguard.ui.view.KeyguardRootView import com.android.systemui.res.R @@ -34,11 +35,10 @@ import javax.inject.Inject @SysUISingleton class EmptyLockIconViewController @Inject -constructor( - private val keyguardRootView: Lazy<KeyguardRootView>, -) : LockIconViewController { +constructor(private val keyguardRootView: Lazy<KeyguardRootView>) : LockIconViewController { private val deviceEntryIconViewId = R.id.device_entry_icon_view - override fun setLockIconView(lockIconView: LockIconView) { + + override fun setLockIconView(lockIconView: View) { // no-op } diff --git a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java deleted file mode 100644 index 03b13fe47c10..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/LegacyLockIconViewController.java +++ /dev/null @@ -1,843 +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.keyguard; - -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; -import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT; - -import static com.android.keyguard.LockIconView.ICON_FINGERPRINT; -import static com.android.keyguard.LockIconView.ICON_LOCK; -import static com.android.keyguard.LockIconView.ICON_UNLOCK; -import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; -import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; -import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; -import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.hardware.biometrics.BiometricAuthenticator; -import android.hardware.biometrics.BiometricSourceType; -import android.os.VibrationAttributes; -import android.util.DisplayMetrics; -import android.util.Log; -import android.util.MathUtils; -import android.view.HapticFeedbackConstants; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.WindowInsets; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.AccessibilityNodeInfo; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.VisibleForTesting; -import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; - -import com.android.systemui.Dumpable; -import com.android.systemui.biometrics.AuthController; -import com.android.systemui.biometrics.AuthRippleController; -import com.android.systemui.biometrics.UdfpsController; -import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; -import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.dagger.qualifiers.Main; -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FeatureFlags; -import com.android.systemui.flags.Flags; -import com.android.systemui.keyguard.KeyguardBottomAreaRefactor; -import com.android.systemui.keyguard.MigrateClocksToBlueprint; -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor; -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; -import com.android.systemui.keyguard.shared.model.KeyguardState; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.concurrency.DelayableExecutor; - -import dagger.Lazy; - -import kotlinx.coroutines.ExperimentalCoroutinesApi; - -import java.io.PrintWriter; -import java.util.Objects; -import java.util.function.Consumer; - -import javax.inject.Inject; - -/** - * Controls when to show the LockIcon affordance (lock/unlocked icon or circle) on lock screen. - * - * For devices with UDFPS, the lock icon will show at the sensor location. Else, the lock - * icon will show a set distance from the bottom of the device. - */ -@SysUISingleton -public class LegacyLockIconViewController implements Dumpable, LockIconViewController { - private static final String TAG = "LockIconViewController"; - private static final float sDefaultDensity = - (float) DisplayMetrics.DENSITY_DEVICE_STABLE / (float) DisplayMetrics.DENSITY_DEFAULT; - private static final int sLockIconRadiusPx = (int) (sDefaultDensity * 36); - private static final VibrationAttributes TOUCH_VIBRATION_ATTRIBUTES = - VibrationAttributes.createForUsage(VibrationAttributes.USAGE_TOUCH); - - private static final long FADE_OUT_DURATION_MS = 250L; - - private final long mLongPressTimeout; - @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; - @NonNull private final KeyguardViewController mKeyguardViewController; - @NonNull private final StatusBarStateController mStatusBarStateController; - @NonNull private final KeyguardStateController mKeyguardStateController; - @NonNull private final FalsingManager mFalsingManager; - @NonNull private final AuthController mAuthController; - @NonNull private final AccessibilityManager mAccessibilityManager; - @NonNull private final ConfigurationController mConfigurationController; - @NonNull private final DelayableExecutor mExecutor; - private boolean mUdfpsEnrolled; - private Resources mResources; - private Context mContext; - @NonNull private CharSequence mUnlockedLabel; - @NonNull private CharSequence mLockedLabel; - @NonNull private final VibratorHelper mVibrator; - @Nullable private final AuthRippleController mAuthRippleController; - @NonNull private final FeatureFlags mFeatureFlags; - @NonNull private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; - @NonNull private final KeyguardTransitionInteractor mTransitionInteractor; - @NonNull private final KeyguardInteractor mKeyguardInteractor; - @NonNull private final View.AccessibilityDelegate mAccessibilityDelegate; - @NonNull private final Lazy<DeviceEntryInteractor> mDeviceEntryInteractor; - - // Tracks the velocity of a touch to help filter out the touches that move too fast. - private VelocityTracker mVelocityTracker; - // The ID of the pointer for which ACTION_DOWN has occurred. -1 means no pointer is active. - private int mActivePointerId = -1; - - private boolean mIsDozing; - private boolean mIsActiveDreamLockscreenHosted; - private boolean mIsBouncerShowing; - private boolean mRunningFPS; - private boolean mCanDismissLockScreen; - private int mStatusBarState; - private boolean mIsKeyguardShowing; - private Runnable mLongPressCancelRunnable; - - private boolean mUdfpsSupported; - private float mHeightPixels; - private float mWidthPixels; - private int mBottomPaddingPx; - private int mDefaultPaddingPx; - - private boolean mShowUnlockIcon; - private boolean mShowLockIcon; - - // for udfps when strong auth is required or unlocked on AOD - private boolean mShowAodLockIcon; - private boolean mShowAodUnlockedIcon; - private final int mMaxBurnInOffsetX; - private final int mMaxBurnInOffsetY; - private float mInterpolatedDarkAmount; - - private boolean mDownDetected; - private final Rect mSensorTouchLocation = new Rect(); - private LockIconView mView; - - @VisibleForTesting - final Consumer<Float> mDozeTransitionCallback = (Float value) -> { - mInterpolatedDarkAmount = value; - mView.setDozeAmount(value); - updateBurnInOffsets(); - }; - - @VisibleForTesting - final Consumer<Boolean> mIsDozingCallback = (Boolean isDozing) -> { - mIsDozing = isDozing; - updateBurnInOffsets(); - updateVisibility(); - }; - - @VisibleForTesting - final Consumer<Boolean> mIsActiveDreamLockscreenHostedCallback = - (Boolean isLockscreenHosted) -> { - mIsActiveDreamLockscreenHosted = isLockscreenHosted; - updateVisibility(); - }; - - @Inject - public LegacyLockIconViewController( - @NonNull StatusBarStateController statusBarStateController, - @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor, - @NonNull KeyguardViewController keyguardViewController, - @NonNull KeyguardStateController keyguardStateController, - @NonNull FalsingManager falsingManager, - @NonNull AuthController authController, - @NonNull DumpManager dumpManager, - @NonNull AccessibilityManager accessibilityManager, - @NonNull ConfigurationController configurationController, - @NonNull @Main DelayableExecutor executor, - @NonNull VibratorHelper vibrator, - @Nullable AuthRippleController authRippleController, - @NonNull @Main Resources resources, - @NonNull KeyguardTransitionInteractor transitionInteractor, - @NonNull KeyguardInteractor keyguardInteractor, - @NonNull FeatureFlags featureFlags, - PrimaryBouncerInteractor primaryBouncerInteractor, - Context context, - Lazy<DeviceEntryInteractor> deviceEntryInteractor - ) { - mStatusBarStateController = statusBarStateController; - mKeyguardUpdateMonitor = keyguardUpdateMonitor; - mAuthController = authController; - mKeyguardViewController = keyguardViewController; - mKeyguardStateController = keyguardStateController; - mFalsingManager = falsingManager; - mAccessibilityManager = accessibilityManager; - mConfigurationController = configurationController; - mExecutor = executor; - mVibrator = vibrator; - mAuthRippleController = authRippleController; - mTransitionInteractor = transitionInteractor; - mKeyguardInteractor = keyguardInteractor; - mFeatureFlags = featureFlags; - mPrimaryBouncerInteractor = primaryBouncerInteractor; - - mMaxBurnInOffsetX = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); - mMaxBurnInOffsetY = resources.getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); - mUnlockedLabel = resources.getString(R.string.accessibility_unlock_button); - mLockedLabel = resources.getString(R.string.accessibility_lock_icon); - mLongPressTimeout = resources.getInteger(R.integer.config_lockIconLongPress); - dumpManager.registerDumpable(TAG, this); - mResources = resources; - mContext = context; - mDeviceEntryInteractor = deviceEntryInteractor; - - mAccessibilityDelegate = new View.AccessibilityDelegate() { - private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityAuthenticateHint = - new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_CLICK, - mResources.getString(R.string.accessibility_authenticate_hint)); - private final AccessibilityNodeInfo.AccessibilityAction mAccessibilityEnterHint = - new AccessibilityNodeInfo.AccessibilityAction( - AccessibilityNodeInfoCompat.ACTION_CLICK, - mResources.getString(R.string.accessibility_enter_hint)); - public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfo info) { - super.onInitializeAccessibilityNodeInfo(v, info); - if (isActionable()) { - if (mShowLockIcon) { - info.addAction(mAccessibilityAuthenticateHint); - } else if (mShowUnlockIcon) { - info.addAction(mAccessibilityEnterHint); - } - } - } - }; - } - - /** Sets the LockIconView to the controller and rebinds any that depend on it. */ - @SuppressLint("ClickableViewAccessibility") - @Override - public void setLockIconView(LockIconView lockIconView) { - mView = lockIconView; - mView.setAccessibilityDelegate(mAccessibilityDelegate); - - if (mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { - collectFlow(mView, mTransitionInteractor.transitionValue(KeyguardState.AOD), - mDozeTransitionCallback); - collectFlow(mView, mKeyguardInteractor.isDozing(), mIsDozingCallback); - } - - if (mFeatureFlags.isEnabled(LOCKSCREEN_WALLPAPER_DREAM_ENABLED)) { - collectFlow(mView, mKeyguardInteractor.isActiveDreamLockscreenHosted(), - mIsActiveDreamLockscreenHostedCallback); - } - - updateIsUdfpsEnrolled(); - updateConfiguration(); - updateKeyguardShowing(); - - mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); - mIsDozing = mStatusBarStateController.isDozing(); - mInterpolatedDarkAmount = mStatusBarStateController.getDozeAmount(); - mRunningFPS = mKeyguardUpdateMonitor.isFingerprintDetectionRunning(); - mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); - mStatusBarState = mStatusBarStateController.getState(); - - updateColors(); - mDownDetected = false; - updateBurnInOffsets(); - updateVisibility(); - - updateAccessibility(); - - lockIconView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View view) { - registerCallbacks(); - } - - @Override - public void onViewDetachedFromWindow(View view) { - unregisterCallbacks(); - } - }); - - if (lockIconView.isAttachedToWindow()) { - registerCallbacks(); - } - - lockIconView.setOnTouchListener((view, motionEvent) -> onTouchEvent(motionEvent)); - } - - private void registerCallbacks() { - mConfigurationController.addCallback(mConfigurationListener); - mAuthController.addCallback(mAuthControllerCallback); - mKeyguardUpdateMonitor.registerCallback(mKeyguardUpdateMonitorCallback); - mStatusBarStateController.addCallback(mStatusBarStateListener); - mKeyguardStateController.addCallback(mKeyguardStateCallback); - mAccessibilityManager.addAccessibilityStateChangeListener( - mAccessibilityStateChangeListener); - - } - - private void unregisterCallbacks() { - mAuthController.removeCallback(mAuthControllerCallback); - mConfigurationController.removeCallback(mConfigurationListener); - mKeyguardUpdateMonitor.removeCallback(mKeyguardUpdateMonitorCallback); - mStatusBarStateController.removeCallback(mStatusBarStateListener); - mKeyguardStateController.removeCallback(mKeyguardStateCallback); - mAccessibilityManager.removeAccessibilityStateChangeListener( - mAccessibilityStateChangeListener); - - } - - private void updateAccessibility() { - if (mAccessibilityManager.isEnabled()) { - mView.setOnClickListener(mA11yClickListener); - } else { - mView.setOnClickListener(null); - } - } - - @Override - public float getTop() { - return mView.getLocationTop(); - } - - @Override - public float getBottom() { - return mView.getLocationBottom(); - } - - private void updateVisibility() { - if (!mIsKeyguardShowing && !mIsDozing) { - mView.setVisibility(View.INVISIBLE); - return; - } - - if (mIsKeyguardShowing && mIsActiveDreamLockscreenHosted) { - mView.setVisibility(View.INVISIBLE); - return; - } - - boolean wasShowingFpIcon = mUdfpsEnrolled && !mShowUnlockIcon && !mShowLockIcon - && !mShowAodUnlockedIcon && !mShowAodLockIcon; - mShowLockIcon = !mCanDismissLockScreen && isLockScreen() - && (!mUdfpsEnrolled || !mRunningFPS); - mShowUnlockIcon = mCanDismissLockScreen && isLockScreen(); - mShowAodUnlockedIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && mCanDismissLockScreen; - mShowAodLockIcon = mIsDozing && mUdfpsEnrolled && !mRunningFPS && !mCanDismissLockScreen; - - final CharSequence prevContentDescription = mView.getContentDescription(); - if (mShowLockIcon) { - if (wasShowingFpIcon) { - // fp icon was shown by UdfpsView, and now we still want to animate the transition - // in this drawable - mView.updateIcon(ICON_FINGERPRINT, false); - } - mView.updateIcon(ICON_LOCK, false); - mView.setContentDescription(mLockedLabel); - mView.setVisibility(View.VISIBLE); - } else if (mShowUnlockIcon) { - if (wasShowingFpIcon) { - // fp icon was shown by UdfpsView, and now we still want to animate the transition - // in this drawable - mView.updateIcon(ICON_FINGERPRINT, false); - } - mView.updateIcon(ICON_UNLOCK, false); - mView.setContentDescription(mUnlockedLabel); - mView.setVisibility(View.VISIBLE); - } else if (mShowAodUnlockedIcon) { - mView.updateIcon(ICON_UNLOCK, true); - mView.setContentDescription(mUnlockedLabel); - mView.setVisibility(View.VISIBLE); - } else if (mShowAodLockIcon) { - mView.updateIcon(ICON_LOCK, true); - mView.setContentDescription(mLockedLabel); - mView.setVisibility(View.VISIBLE); - } else { - mView.clearIcon(); - mView.setVisibility(View.INVISIBLE); - mView.setContentDescription(null); - } - - boolean accessibilityEnabled = - !mPrimaryBouncerInteractor.isAnimatingAway() && mView.isVisibleToUser(); - mView.setImportantForAccessibility( - accessibilityEnabled ? View.IMPORTANT_FOR_ACCESSIBILITY_YES - : View.IMPORTANT_FOR_ACCESSIBILITY_NO); - - if (!Objects.equals(prevContentDescription, mView.getContentDescription()) - && mView.getContentDescription() != null && accessibilityEnabled) { - mView.announceForAccessibility(mView.getContentDescription()); - } - } - - private boolean isLockScreen() { - return !mIsDozing - && !mIsBouncerShowing - && mStatusBarState == StatusBarState.KEYGUARD; - } - - private void updateKeyguardShowing() { - mIsKeyguardShowing = mKeyguardStateController.isShowing() - && !mKeyguardStateController.isKeyguardGoingAway(); - } - - private void updateColors() { - mView.updateColorAndBackgroundVisibility(); - } - - private void updateConfiguration() { - WindowManager windowManager = mContext.getSystemService(WindowManager.class); - Rect bounds = windowManager.getCurrentWindowMetrics().getBounds(); - mWidthPixels = bounds.right; - if (mFeatureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)) { - // Assumed to be initially neglected as there are no left or right insets in portrait - // However, on landscape, these insets need to included when calculating the midpoint - WindowInsets insets = windowManager.getCurrentWindowMetrics().getWindowInsets(); - mWidthPixels -= insets.getSystemWindowInsetLeft() + insets.getSystemWindowInsetRight(); - } - mHeightPixels = bounds.bottom; - mBottomPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom); - mDefaultPaddingPx = mResources.getDimensionPixelSize(R.dimen.lock_icon_padding); - mUnlockedLabel = mResources.getString( - R.string.accessibility_unlock_button); - mLockedLabel = mResources.getString(R.string.accessibility_lock_icon); - updateLockIconLocation(); - } - - private void updateLockIconLocation() { - final float scaleFactor = mAuthController.getScaleFactor(); - final int scaledPadding = (int) (mDefaultPaddingPx * scaleFactor); - if (KeyguardBottomAreaRefactor.isEnabled() || MigrateClocksToBlueprint.isEnabled()) { - // positioning in this case is handled by [DefaultDeviceEntrySection] - mView.getLockIcon().setPadding(scaledPadding, scaledPadding, scaledPadding, - scaledPadding); - } else { - if (mUdfpsSupported) { - mView.setCenterLocation(mAuthController.getUdfpsLocation(), - mAuthController.getUdfpsRadius(), scaledPadding); - } else { - mView.setCenterLocation( - new Point((int) mWidthPixels / 2, - (int) (mHeightPixels - - ((mBottomPaddingPx + sLockIconRadiusPx) * scaleFactor))), - sLockIconRadiusPx * scaleFactor, scaledPadding); - } - } - } - - @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("mUdfpsSupported: " + mUdfpsSupported); - pw.println("mUdfpsEnrolled: " + mUdfpsEnrolled); - pw.println("mIsKeyguardShowing: " + mIsKeyguardShowing); - pw.println(); - pw.println(" mShowUnlockIcon: " + mShowUnlockIcon); - pw.println(" mShowLockIcon: " + mShowLockIcon); - pw.println(" mShowAodUnlockedIcon: " + mShowAodUnlockedIcon); - pw.println(); - pw.println(" mIsDozing: " + mIsDozing); - pw.println(" isFlagEnabled(DOZING_MIGRATION_1): " - + mFeatureFlags.isEnabled(DOZING_MIGRATION_1)); - pw.println(" mIsBouncerShowing: " + mIsBouncerShowing); - pw.println(" mRunningFPS: " + mRunningFPS); - pw.println(" mCanDismissLockScreen: " + mCanDismissLockScreen); - pw.println(" mStatusBarState: " + StatusBarState.toString(mStatusBarState)); - pw.println(" mInterpolatedDarkAmount: " + mInterpolatedDarkAmount); - pw.println(" mSensorTouchLocation: " + mSensorTouchLocation); - pw.println(" mDefaultPaddingPx: " + mDefaultPaddingPx); - pw.println(" mIsActiveDreamLockscreenHosted: " + mIsActiveDreamLockscreenHosted); - - if (mView != null) { - mView.dump(pw, args); - } - } - - /** Every minute, update the aod icon's burn in offset */ - @Override - public void dozeTimeTick() { - updateBurnInOffsets(); - } - - private void updateBurnInOffsets() { - float offsetX = MathUtils.lerp(0f, - getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) - - mMaxBurnInOffsetX, mInterpolatedDarkAmount); - float offsetY = MathUtils.lerp(0f, - getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) - - mMaxBurnInOffsetY, mInterpolatedDarkAmount); - - mView.setTranslationX(offsetX); - mView.setTranslationY(offsetY); - } - - private void updateIsUdfpsEnrolled() { - boolean wasUdfpsSupported = mUdfpsSupported; - boolean wasUdfpsEnrolled = mUdfpsEnrolled; - - mUdfpsSupported = mKeyguardUpdateMonitor.isUdfpsSupported(); - mView.setUseBackground(mUdfpsSupported); - - mUdfpsEnrolled = mKeyguardUpdateMonitor.isUdfpsEnrolled(); - if (wasUdfpsSupported != mUdfpsSupported || wasUdfpsEnrolled != mUdfpsEnrolled) { - updateVisibility(); - } - } - - private StatusBarStateController.StateListener mStatusBarStateListener = - new StatusBarStateController.StateListener() { - @Override - public void onDozeAmountChanged(float linear, float eased) { - if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { - mInterpolatedDarkAmount = eased; - mView.setDozeAmount(eased); - updateBurnInOffsets(); - } - } - - @Override - public void onDozingChanged(boolean isDozing) { - if (!mFeatureFlags.isEnabled(DOZING_MIGRATION_1)) { - mIsDozing = isDozing; - updateBurnInOffsets(); - updateVisibility(); - } - } - - @Override - public void onStateChanged(int statusBarState) { - mStatusBarState = statusBarState; - updateVisibility(); - } - }; - - private final KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback = - new KeyguardUpdateMonitorCallback() { - @Override - public void onKeyguardBouncerStateChanged(boolean bouncer) { - mIsBouncerShowing = bouncer; - updateVisibility(); - } - - @Override - public void onBiometricRunningStateChanged(boolean running, - BiometricSourceType biometricSourceType) { - final boolean wasRunningFps = mRunningFPS; - - if (biometricSourceType == FINGERPRINT) { - mRunningFPS = running; - } - - if (wasRunningFps != mRunningFPS) { - updateVisibility(); - } - } - }; - - private final KeyguardStateController.Callback mKeyguardStateCallback = - new KeyguardStateController.Callback() { - @Override - public void onUnlockedChanged() { - mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); - updateKeyguardShowing(); - updateVisibility(); - } - - @Override - public void onKeyguardShowingChanged() { - // Reset values in case biometrics were removed (ie: pin/pattern/password => swipe). - // If biometrics were removed, local vars mCanDismissLockScreen and - // mUserUnlockedWithBiometric may not be updated. - mCanDismissLockScreen = mKeyguardStateController.canDismissLockScreen(); - - // reset mIsBouncerShowing state in case it was preemptively set - // onLongPress - mIsBouncerShowing = mKeyguardViewController.isBouncerShowing(); - - updateKeyguardShowing(); - updateVisibility(); - } - - @Override - public void onKeyguardFadingAwayChanged() { - updateKeyguardShowing(); - updateVisibility(); - } - }; - - private final ConfigurationController.ConfigurationListener mConfigurationListener = - new ConfigurationController.ConfigurationListener() { - @Override - public void onUiModeChanged() { - updateColors(); - } - - @Override - public void onThemeChanged() { - updateColors(); - } - - @Override - public void onConfigChanged(Configuration newConfig) { - updateConfiguration(); - updateColors(); - } - }; - - /** - * Handles the touch if {@link #isActionable()} is true. - * Subsequently, will trigger {@link #onLongPress()} if a touch is continuously in the lock icon - * area for {@link #mLongPressTimeout} ms. - * - * Touch speed debouncing mimics logic from the velocity tracker in {@link UdfpsController}. - */ - private boolean onTouchEvent(MotionEvent event) { - if (!actionableDownEventStartedOnView(event)) { - cancelTouches(); - return false; - } - - switch(event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_HOVER_ENTER: - if (!mDownDetected && mAccessibilityManager.isTouchExplorationEnabled()) { - vibrateOnTouchExploration(); - } - - // The pointer that causes ACTION_DOWN is always at index 0. - // We need to persist its ID to track it during ACTION_MOVE that could include - // data for many other pointers because of multi-touch support. - mActivePointerId = event.getPointerId(0); - if (mVelocityTracker == null) { - // To simplify the lifecycle of the velocity tracker, make sure it's never null - // after ACTION_DOWN, and always null after ACTION_CANCEL or ACTION_UP. - mVelocityTracker = VelocityTracker.obtain(); - } else { - // ACTION_UP or ACTION_CANCEL is not guaranteed to be called before a new - // ACTION_DOWN, in that case we should just reuse the old instance. - mVelocityTracker.clear(); - } - mVelocityTracker.addMovement(event); - - mDownDetected = true; - mLongPressCancelRunnable = mExecutor.executeDelayed( - this::onLongPress, mLongPressTimeout); - break; - case MotionEvent.ACTION_MOVE: - case MotionEvent.ACTION_HOVER_MOVE: - mVelocityTracker.addMovement(event); - // Compute pointer velocity in pixels per second. - mVelocityTracker.computeCurrentVelocity(1000); - float velocity = computePointerSpeed(mVelocityTracker, - mActivePointerId); - if (event.getClassification() != MotionEvent.CLASSIFICATION_DEEP_PRESS - && exceedsVelocityThreshold(velocity)) { - Log.v(TAG, "lock icon long-press rescheduled due to " - + "high pointer velocity=" + velocity); - mLongPressCancelRunnable.run(); - mLongPressCancelRunnable = mExecutor.executeDelayed( - this::onLongPress, mLongPressTimeout); - } - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_HOVER_EXIT: - cancelTouches(); - break; - } - - return true; - } - - /** - * Calculate the pointer speed given a velocity tracker and the pointer id. - * This assumes that the velocity tracker has already been passed all relevant motion events. - */ - private static float computePointerSpeed(@NonNull VelocityTracker tracker, int pointerId) { - final float vx = tracker.getXVelocity(pointerId); - final float vy = tracker.getYVelocity(pointerId); - return (float) Math.sqrt(Math.pow(vx, 2.0) + Math.pow(vy, 2.0)); - } - - /** - * Whether the velocity exceeds the acceptable UDFPS debouncing threshold. - */ - private static boolean exceedsVelocityThreshold(float velocity) { - return velocity > 750f; - } - - private boolean actionableDownEventStartedOnView(MotionEvent event) { - if (!isActionable()) { - return false; - } - - if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { - return true; - } - - return mDownDetected; - } - - @ExperimentalCoroutinesApi - @VisibleForTesting - protected void onLongPress() { - cancelTouches(); - if (mFalsingManager.isFalseLongTap(FalsingManager.LOW_PENALTY)) { - Log.v(TAG, "lock icon long-press rejected by the falsing manager."); - return; - } - - // pre-emptively set to true to hide view - mIsBouncerShowing = true; - if (!DeviceEntryUdfpsRefactor.isEnabled() - && mUdfpsSupported && mShowUnlockIcon && mAuthRippleController != null) { - mAuthRippleController.showUnlockRipple(FINGERPRINT); - } - updateVisibility(); - - // play device entry haptic (consistent with UDFPS controller longpress) - vibrateOnLongPress(); - - if (SceneContainerFlag.isEnabled()) { - mDeviceEntryInteractor.get().attemptDeviceEntry(); - } else { - mKeyguardViewController.showPrimaryBouncer(/* scrim */ true); - } - } - - - private void cancelTouches() { - mDownDetected = false; - if (mLongPressCancelRunnable != null) { - mLongPressCancelRunnable.run(); - } - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - private boolean isActionable() { - if (mIsBouncerShowing) { - Log.v(TAG, "lock icon long-press ignored, bouncer already showing."); - // a long press gestures from AOD may have already triggered the bouncer to show, - // so this touch is no longer actionable - return false; - } - return mUdfpsSupported || mShowUnlockIcon; - } - - /** - * Set the alpha of this view. - */ - @Override - public void setAlpha(float alpha) { - mView.setAlpha(alpha); - } - - private void updateUdfpsConfig() { - // must be called from the main thread since it may update the views - mExecutor.execute(() -> { - updateIsUdfpsEnrolled(); - updateConfiguration(); - }); - } - - @VisibleForTesting - void vibrateOnTouchExploration() { - mVibrator.performHapticFeedback( - mView, - HapticFeedbackConstants.CONTEXT_CLICK - ); - } - - @VisibleForTesting - void vibrateOnLongPress() { - mVibrator.performHapticFeedback(mView, UdfpsController.LONG_PRESS); - } - - private final AuthController.Callback mAuthControllerCallback = new AuthController.Callback() { - @Override - public void onAllAuthenticatorsRegistered(@BiometricAuthenticator.Modality int modality) { - if (modality == TYPE_FINGERPRINT) { - updateUdfpsConfig(); - } - } - - @Override - public void onEnrollmentsChanged(@BiometricAuthenticator.Modality int modality) { - if (modality == TYPE_FINGERPRINT) { - updateUdfpsConfig(); - } - } - - @Override - public void onUdfpsLocationChanged(UdfpsOverlayParams udfpsOverlayParams) { - updateUdfpsConfig(); - } - }; - - /** - * Whether the lock icon will handle a touch while dozing. - */ - @Override - public boolean willHandleTouchWhileDozing(MotionEvent event) { - // is in lock icon area - mView.getHitRect(mSensorTouchLocation); - final boolean inLockIconArea = - mSensorTouchLocation.contains((int) event.getX(), (int) event.getY()) - && mView.getVisibility() == View.VISIBLE; - - return inLockIconArea && actionableDownEventStartedOnView(event); - } - - private final View.OnClickListener mA11yClickListener = v -> onLongPress(); - - private final AccessibilityManager.AccessibilityStateChangeListener - mAccessibilityStateChangeListener = enabled -> updateAccessibility(); -} diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconView.java b/packages/SystemUI/src/com/android/keyguard/LockIconView.java deleted file mode 100644 index ff6a3d0cc6f0..000000000000 --- a/packages/SystemUI/src/com/android/keyguard/LockIconView.java +++ /dev/null @@ -1,263 +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.keyguard; - -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.Color; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.View; -import android.widget.FrameLayout; -import android.widget.ImageView; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; - -import com.android.internal.graphics.ColorUtils; -import com.android.settingslib.Utils; -import com.android.systemui.Dumpable; -import com.android.systemui.res.R; - -import java.io.PrintWriter; - -/** - * A view positioned under the notification shade. - */ -public class LockIconView extends FrameLayout implements Dumpable { - @IntDef({ICON_NONE, ICON_LOCK, ICON_FINGERPRINT, ICON_UNLOCK}) - public @interface IconType {} - - public static final int ICON_NONE = -1; - public static final int ICON_LOCK = 0; - public static final int ICON_FINGERPRINT = 1; - public static final int ICON_UNLOCK = 2; - - private @IconType int mIconType; - private boolean mAod; - - @NonNull private final RectF mSensorRect; - @NonNull private Point mLockIconCenter = new Point(0, 0); - private float mRadius; - private int mLockIconPadding; - - private ImageView mLockIcon; - private ImageView mBgView; - - private int mLockIconColor; - private boolean mUseBackground = false; - private float mDozeAmount = 0f; - - @SuppressLint("ClickableViewAccessibility") - public LockIconView(Context context, AttributeSet attrs) { - super(context, attrs); - mSensorRect = new RectF(); - - addBgImageView(context, attrs); - addLockIconImageView(context, attrs); - } - - void setDozeAmount(float dozeAmount) { - mDozeAmount = dozeAmount; - updateColorAndBackgroundVisibility(); - } - - void updateColorAndBackgroundVisibility() { - if (mUseBackground && mLockIcon.getDrawable() != null) { - mLockIconColor = ColorUtils.blendARGB( - Utils.getColorAttrDefaultColor(getContext(), android.R.attr.textColorPrimary), - Color.WHITE, - mDozeAmount); - int backgroundColor = Utils.getColorAttrDefaultColor(getContext(), - com.android.internal.R.attr.colorSurface); - mBgView.setImageTintList(ColorStateList.valueOf(backgroundColor)); - mBgView.setAlpha(1f - mDozeAmount); - mBgView.setVisibility(View.VISIBLE); - } else { - mLockIconColor = ColorUtils.blendARGB( - Utils.getColorAttrDefaultColor(getContext(), R.attr.wallpaperTextColorAccent), - Color.WHITE, - mDozeAmount); - mBgView.setVisibility(View.GONE); - } - - mLockIcon.setImageTintList(ColorStateList.valueOf(mLockIconColor)); - } - - /** - * Whether or not to render the lock icon background. Mainly used for UDPFS. - */ - public void setUseBackground(boolean useBackground) { - mUseBackground = useBackground; - updateColorAndBackgroundVisibility(); - } - - /** - * Set the location of the lock icon. - */ - public void setCenterLocation(@NonNull Point center, float radius, int drawablePadding) { - mLockIconCenter = center; - mRadius = radius; - mLockIconPadding = drawablePadding; - - mLockIcon.setPadding(mLockIconPadding, mLockIconPadding, mLockIconPadding, - mLockIconPadding); - - mSensorRect.set(mLockIconCenter.x - mRadius, - mLockIconCenter.y - mRadius, - mLockIconCenter.x + mRadius, - mLockIconCenter.y + mRadius); - - final FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) getLayoutParams(); - if (lp != null) { - lp.width = (int) (mSensorRect.right - mSensorRect.left); - lp.height = (int) (mSensorRect.bottom - mSensorRect.top); - lp.topMargin = (int) mSensorRect.top; - lp.setMarginStart((int) mSensorRect.left); - setLayoutParams(lp); - } - } - - @Override - public boolean hasOverlappingRendering() { - return false; - } - - float getLocationTop() { - Rect r = new Rect(); - mLockIcon.getGlobalVisibleRect(r); - return r.top; - } - - float getLocationBottom() { - Rect r = new Rect(); - mLockIcon.getGlobalVisibleRect(r); - return r.bottom; - - } - - /** - * Updates the icon its default state where no visual is shown. - */ - public void clearIcon() { - updateIcon(ICON_NONE, false); - } - - /** - * Transition the current icon to a new state - * @param icon type (ie: lock icon, unlock icon, fingerprint icon) - * @param aod whether to use the aod icon variant (some icons don't have aod variants and will - * therefore show no icon) - */ - public void updateIcon(@IconType int icon, boolean aod) { - mIconType = icon; - mAod = aod; - - mLockIcon.setImageState(getLockIconState(mIconType, mAod), true); - } - - public ImageView getLockIcon() { - return mLockIcon; - } - - private void addLockIconImageView(Context context, AttributeSet attrs) { - mLockIcon = new ImageView(context, attrs); - mLockIcon.setId(R.id.lock_icon); - mLockIcon.setScaleType(ImageView.ScaleType.CENTER_CROP); - mLockIcon.setImageDrawable(context.getDrawable(R.drawable.super_lock_icon)); - addView(mLockIcon); - LayoutParams lp = (LayoutParams) mLockIcon.getLayoutParams(); - lp.height = MATCH_PARENT; - lp.width = MATCH_PARENT; - lp.gravity = Gravity.CENTER; - mLockIcon.setLayoutParams(lp); - } - - private void addBgImageView(Context context, AttributeSet attrs) { - mBgView = new ImageView(context, attrs); - mBgView.setId(R.id.lock_icon_bg); - mBgView.setImageDrawable(context.getDrawable(R.drawable.fingerprint_bg)); - mBgView.setVisibility(View.INVISIBLE); - addView(mBgView); - LayoutParams lp = (LayoutParams) mBgView.getLayoutParams(); - lp.height = MATCH_PARENT; - lp.width = MATCH_PARENT; - mBgView.setLayoutParams(lp); - } - - private static int[] getLockIconState(@IconType int icon, boolean aod) { - if (icon == ICON_NONE) { - return new int[0]; - } - - int[] lockIconState = new int[2]; - switch (icon) { - case ICON_LOCK: - lockIconState[0] = android.R.attr.state_first; - break; - case ICON_FINGERPRINT: - lockIconState[0] = android.R.attr.state_middle; - break; - case ICON_UNLOCK: - lockIconState[0] = android.R.attr.state_last; - break; - } - - if (aod) { - lockIconState[1] = android.R.attr.state_single; - } else { - lockIconState[1] = -android.R.attr.state_single; - } - - return lockIconState; - } - - private String typeToString(@IconType int type) { - switch (type) { - case ICON_NONE: - return "none"; - case ICON_LOCK: - return "lock"; - case ICON_FINGERPRINT: - return "fingerprint"; - case ICON_UNLOCK: - return "unlock"; - } - - return "invalid"; - } - - @Override - public void dump(@NonNull PrintWriter pw, @NonNull String[] args) { - pw.println("Lock Icon View Parameters:"); - pw.println(" Center in px (x, y)= (" - + mLockIconCenter.x + ", " + mLockIconCenter.y + ")"); - pw.println(" Radius in pixels: " + mRadius); - pw.println(" Drawable padding: " + mLockIconPadding); - pw.println(" mIconType=" + typeToString(mIconType)); - pw.println(" mAod=" + mAod); - pw.println("Lock Icon View actual measurements:"); - pw.println(" topLeft= (" + getX() + ", " + getY() + ")"); - pw.println(" width=" + getWidth() + " height=" + getHeight()); - } -} diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt index 10d5a0cc3dd5..c5012b01dd3e 100644 --- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt +++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.kt @@ -17,13 +17,19 @@ package com.android.keyguard import android.view.MotionEvent +import android.view.View /** Controls the [LockIconView]. */ interface LockIconViewController { - fun setLockIconView(lockIconView: LockIconView) + fun setLockIconView(lockIconView: View) + fun getTop(): Float + fun getBottom(): Float + fun dozeTimeTick() + fun setAlpha(alpha: Float) + fun willHandleTouchWhileDozing(event: MotionEvent): Boolean } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java index 158623fa80af..1978bb89b5b2 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesDialogDelegate.java @@ -42,6 +42,7 @@ import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.Space; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; @@ -52,6 +53,7 @@ import androidx.annotation.VisibleForTesting; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; +import com.android.settingslib.Utils; import com.android.settingslib.bluetooth.BluetoothCallback; import com.android.settingslib.bluetooth.CachedBluetoothDevice; import com.android.settingslib.bluetooth.HapClientProfile; @@ -428,10 +430,16 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, } catch (Resources.NotFoundException e) { Log.i(TAG, "No hearing devices related tool config resource"); } - final int listSize = toolItemList.size(); - for (int i = 0; i < listSize; i++) { + for (int i = 0; i < toolItemList.size(); i++) { View view = createHearingToolView(context, toolItemList.get(i)); mRelatedToolsContainer.addView(view); + if (i != toolItemList.size() - 1) { + final int spaceSize = context.getResources().getDimensionPixelSize( + R.dimen.hearing_devices_layout_margin); + Space space = new Space(context); + space.setLayoutParams(new LinearLayout.LayoutParams(spaceSize, 0)); + mRelatedToolsContainer.addView(space); + } } } @@ -492,6 +500,10 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, TextView text = view.requireViewById(R.id.tool_name); view.setContentDescription(item.getToolName()); icon.setImageDrawable(item.getToolIcon()); + if (item.isCustomIcon()) { + icon.getDrawable().mutate().setTint(Utils.getColorAttr(context, + com.android.internal.R.attr.materialColorOnPrimaryContainer).getDefaultColor()); + } text.setText(item.getToolName()); Intent intent = item.getToolIntent(); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); @@ -517,7 +529,8 @@ public class HearingDevicesDialogDelegate implements SystemUIDialog.Delegate, return new ToolItem( context.getString(R.string.quick_settings_hearing_devices_live_caption_title), context.getDrawable(R.drawable.ic_volume_odi_captions), - LIVE_CAPTION_INTENT); + LIVE_CAPTION_INTENT, + /* isCustomIcon= */ true); } return null; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java index 2006726e6847..7e4c1e5e7b6e 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/HearingDevicesToolItemParser.java @@ -41,7 +41,7 @@ public class HearingDevicesToolItemParser { private static final String SPLIT_DELIMITER = "/"; private static final String RES_TYPE = "drawable"; @VisibleForTesting - static final int MAX_NUM = 3; + static final int MAX_NUM = 2; /** * Parses the string arrays to create a list of {@link ToolItem}. @@ -82,7 +82,8 @@ public class HearingDevicesToolItemParser { useCustomIcons ? iconList.get(i) : activityInfoList.get(i).loadIcon(packageManager), new Intent(Intent.ACTION_MAIN).setComponent( - activityInfoList.get(i).getComponentName()) + activityInfoList.get(i).getComponentName()), + useCustomIcons )); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt index 66bb2b5e2328..ef03f0cdef79 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/hearingaid/ToolItem.kt @@ -23,4 +23,5 @@ data class ToolItem( var toolName: String = "", var toolIcon: Drawable, var toolIntent: Intent, + var isCustomIcon: Boolean, ) diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt index cd9efaf6e6bb..610e3f8a8c84 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt @@ -39,6 +39,10 @@ import com.android.systemui.qs.tiles.impl.fontscaling.domain.FontScalingTileMapp import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileDataInteractor import com.android.systemui.qs.tiles.impl.fontscaling.domain.interactor.FontScalingTileUserActionInteractor import com.android.systemui.qs.tiles.impl.fontscaling.domain.model.FontScalingTileModel +import com.android.systemui.qs.tiles.impl.hearingdevices.domain.HearingDevicesTileMapper +import com.android.systemui.qs.tiles.impl.hearingdevices.domain.interactor.HearingDevicesTileDataInteractor +import com.android.systemui.qs.tiles.impl.hearingdevices.domain.interactor.HearingDevicesTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.hearingdevices.domain.model.HearingDevicesTileModel import com.android.systemui.qs.tiles.impl.inversion.domain.ColorInversionTileMapper import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionTileDataInteractor import com.android.systemui.qs.tiles.impl.inversion.domain.interactor.ColorInversionUserActionInteractor @@ -159,6 +163,13 @@ interface QSAccessibilityModule { impl: NightDisplayTileDataInteractor ): QSTileAvailabilityInteractor + @Binds + @IntoMap + @StringKey(HEARING_DEVICES_TILE_SPEC) + fun provideHearingDevicesAvailabilityInteractor( + impl: HearingDevicesTileDataInteractor + ): QSTileAvailabilityInteractor + companion object { const val COLOR_CORRECTION_TILE_SPEC = "color_correction" const val COLOR_INVERSION_TILE_SPEC = "inversion" @@ -191,7 +202,7 @@ interface QSAccessibilityModule { factory: QSTileViewModelFactory.Static<ColorCorrectionTileModel>, mapper: ColorCorrectionTileMapper, stateInteractor: ColorCorrectionTileDataInteractor, - userActionInteractor: ColorCorrectionUserActionInteractor + userActionInteractor: ColorCorrectionUserActionInteractor, ): QSTileViewModel = factory.create( TileSpec.create(COLOR_CORRECTION_TILE_SPEC), @@ -223,7 +234,7 @@ interface QSAccessibilityModule { factory: QSTileViewModelFactory.Static<ColorInversionTileModel>, mapper: ColorInversionTileMapper, stateInteractor: ColorInversionTileDataInteractor, - userActionInteractor: ColorInversionUserActionInteractor + userActionInteractor: ColorInversionUserActionInteractor, ): QSTileViewModel = factory.create( TileSpec.create(COLOR_INVERSION_TILE_SPEC), @@ -255,7 +266,7 @@ interface QSAccessibilityModule { factory: QSTileViewModelFactory.Static<FontScalingTileModel>, mapper: FontScalingTileMapper, stateInteractor: FontScalingTileDataInteractor, - userActionInteractor: FontScalingTileUserActionInteractor + userActionInteractor: FontScalingTileUserActionInteractor, ): QSTileViewModel = factory.create( TileSpec.create(FONT_SCALING_TILE_SPEC), @@ -279,21 +290,6 @@ interface QSAccessibilityModule { category = TileCategory.DISPLAY, ) - @Provides - @IntoMap - @StringKey(HEARING_DEVICES_TILE_SPEC) - fun provideHearingDevicesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = - QSTileConfig( - tileSpec = TileSpec.create(HEARING_DEVICES_TILE_SPEC), - uiConfig = - QSTileUIConfig.Resource( - iconRes = R.drawable.qs_hearing_devices_icon, - labelRes = R.string.quick_settings_hearing_devices_label, - ), - instanceId = uiEventLogger.getNewInstanceId(), - category = TileCategory.ACCESSIBILITY, - ) - /** * Inject Reduce Bright Colors Tile into tileViewModelMap in QSModule. The tile is hidden * behind a flag. @@ -305,7 +301,7 @@ interface QSAccessibilityModule { factory: QSTileViewModelFactory.Static<ReduceBrightColorsTileModel>, mapper: ReduceBrightColorsTileMapper, stateInteractor: ReduceBrightColorsTileDataInteractor, - userActionInteractor: ReduceBrightColorsTileUserActionInteractor + userActionInteractor: ReduceBrightColorsTileUserActionInteractor, ): QSTileViewModel = if (Flags.qsNewTilesFuture()) factory.create( @@ -339,7 +335,7 @@ interface QSAccessibilityModule { factory: QSTileViewModelFactory.Static<OneHandedModeTileModel>, mapper: OneHandedModeTileMapper, stateInteractor: OneHandedModeTileDataInteractor, - userActionInteractor: OneHandedModeTileUserActionInteractor + userActionInteractor: OneHandedModeTileUserActionInteractor, ): QSTileViewModel = if (Flags.qsNewTilesFuture()) factory.create( @@ -376,7 +372,7 @@ interface QSAccessibilityModule { factory: QSTileViewModelFactory.Static<NightDisplayTileModel>, mapper: NightDisplayTileMapper, stateInteractor: NightDisplayTileDataInteractor, - userActionInteractor: NightDisplayTileUserActionInteractor + userActionInteractor: NightDisplayTileUserActionInteractor, ): QSTileViewModel = if (Flags.qsNewTilesFuture()) factory.create( @@ -386,5 +382,43 @@ interface QSAccessibilityModule { mapper, ) else StubQSTileViewModel + + @Provides + @IntoMap + @StringKey(HEARING_DEVICES_TILE_SPEC) + fun provideHearingDevicesTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(HEARING_DEVICES_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_hearing_devices_icon, + labelRes = R.string.quick_settings_hearing_devices_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + category = TileCategory.ACCESSIBILITY, + ) + + /** + * Inject HearingDevices Tile into tileViewModelMap in QSModule. The tile is hidden behind a + * flag. + */ + @Provides + @IntoMap + @StringKey(HEARING_DEVICES_TILE_SPEC) + fun provideHearingDevicesTileViewModel( + factory: QSTileViewModelFactory.Static<HearingDevicesTileModel>, + mapper: HearingDevicesTileMapper, + stateInteractor: HearingDevicesTileDataInteractor, + userActionInteractor: HearingDevicesTileUserActionInteractor, + ): QSTileViewModel { + return if (Flags.hearingAidsQsTileDialog() && Flags.qsNewTilesFuture()) { + factory.create( + TileSpec.create(HEARING_DEVICES_TILE_SPEC), + userActionInteractor, + stateInteractor, + mapper, + ) + } else StubQSTileViewModel + } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt index c95a94e5e388..f6cc72431db0 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt @@ -37,11 +37,9 @@ import com.android.systemui.biometrics.data.repository.FacePropertyRepository import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.WakefulnessLifecycle import com.android.systemui.keyguard.shared.model.BiometricUnlockSource import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.log.core.LogLevel import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.res.R import com.android.systemui.statusbar.CircleReveal @@ -87,7 +85,7 @@ constructor( private val lightRevealScrim: LightRevealScrim, private val authRippleInteractor: AuthRippleInteractor, private val facePropertyRepository: FacePropertyRepository, - rippleView: AuthRippleView? + rippleView: AuthRippleView?, ) : ViewController<AuthRippleView>(rippleView), CoreStartable, @@ -108,15 +106,13 @@ constructor( } init { - if (DeviceEntryUdfpsRefactor.isEnabled) { - rippleView?.repeatWhenAttached { - repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) { - authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource -> - if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) { - showUnlockRippleInternal(BiometricSourceType.FINGERPRINT) - } else { - showUnlockRippleInternal(BiometricSourceType.FACE) - } + rippleView?.repeatWhenAttached { + repeatOnLifecycle(androidx.lifecycle.Lifecycle.State.CREATED) { + authRippleInteractor.showUnlockRipple.collect { biometricUnlockSource -> + if (biometricUnlockSource == BiometricUnlockSource.FINGERPRINT_SENSOR) { + showUnlockRippleInternal(BiometricSourceType.FINGERPRINT) + } else { + showUnlockRippleInternal(BiometricSourceType.FACE) } } } @@ -134,29 +130,8 @@ constructor( keyguardStateController.addCallback(this) wakefulnessLifecycle.addObserver(this) commandRegistry.registerCommand("auth-ripple") { AuthRippleCommand() } - if (!DeviceEntryUdfpsRefactor.isEnabled) { - biometricUnlockController.addListener(biometricModeListener) - } } - private val biometricModeListener = - object : BiometricUnlockController.BiometricUnlockEventsListener { - override fun onBiometricUnlockedWithKeyguardDismissal( - biometricSourceType: BiometricSourceType? - ) { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - if (biometricSourceType != null) { - showUnlockRippleInternal(biometricSourceType) - } else { - logger.log( - TAG, - LogLevel.ERROR, - "Unexpected scenario where biometricSourceType is null" - ) - } - } - } - @VisibleForTesting public override fun onViewDetached() { udfpsController?.removeCallback(udfpsControllerCallback) @@ -166,17 +141,10 @@ constructor( keyguardStateController.removeCallback(this) wakefulnessLifecycle.removeObserver(this) commandRegistry.unregisterCommand("auth-ripple") - biometricUnlockController.removeListener(biometricModeListener) notificationShadeWindowController.setForcePluginOpen(false, this) } - @Deprecated("Update authRippleInteractor.showUnlockRipple instead of calling this.") - fun showUnlockRipple(biometricSourceType: BiometricSourceType) { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - showUnlockRippleInternal(biometricSourceType) - } - private fun showUnlockRippleInternal(biometricSourceType: BiometricSourceType) { val keyguardNotShowing = !keyguardStateController.isShowing val unlockNotAllowed = @@ -197,8 +165,8 @@ constructor( 0, Math.max( Math.max(it.x, displayMetrics.widthPixels - it.x), - Math.max(it.y, displayMetrics.heightPixels - it.y) - ) + Math.max(it.y, displayMetrics.heightPixels - it.y), + ), ) logger.showingUnlockRippleAt(it.x, it.y, "FP sensor radius: $udfpsRadius") showUnlockedRipple() @@ -213,8 +181,8 @@ constructor( 0, Math.max( Math.max(it.x, displayMetrics.widthPixels - it.x), - Math.max(it.y, displayMetrics.heightPixels - it.y) - ) + Math.max(it.y, displayMetrics.heightPixels - it.y), + ), ) logger.showingUnlockRippleAt(it.x, it.y, "Face unlock ripple") showUnlockedRipple() @@ -322,7 +290,7 @@ constructor( override fun onBiometricAuthenticated( userId: Int, biometricSourceType: BiometricSourceType, - isStrongBiometric: Boolean + isStrongBiometric: Boolean, ) { if (biometricSourceType == BiometricSourceType.FINGERPRINT) { mView.fadeDwellRipple() @@ -337,7 +305,7 @@ constructor( override fun onBiometricAcquired( biometricSourceType: BiometricSourceType, - acquireInfo: Int + acquireInfo: Int, ) { if ( biometricSourceType == BiometricSourceType.FINGERPRINT && diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt deleted file mode 100644 index 242601d46fa4..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpView.kt +++ /dev/null @@ -1,35 +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.systemui.biometrics - -import android.content.Context -import android.util.AttributeSet - -/** - * Class that coordinates non-HBM animations during BiometricPrompt. - * - * Currently doesn't draw anything. - * - * Note that [AuthBiometricFingerprintViewController] also shows UDFPS animations. At some point we should - * de-dupe this if necessary. - */ -class UdfpsBpView(context: Context, attrs: AttributeSet?) : UdfpsAnimationView(context, attrs) { - - // Drawable isn't ever added to the view, so we don't currently show anything - private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context) - - override fun getDrawable(): UdfpsDrawable = fingerprintDrawable -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt deleted file mode 100644 index e0455b58b919..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt +++ /dev/null @@ -1,47 +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.systemui.biometrics - -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogManager - -/** - * Class that coordinates non-HBM animations for biometric prompt. - */ -class UdfpsBpViewController( - view: UdfpsBpView, - statusBarStateController: StatusBarStateController, - shadeInteractor: ShadeInteractor, - systemUIDialogManager: SystemUIDialogManager, - dumpManager: DumpManager, - udfpsOverlayInteractor: UdfpsOverlayInteractor, -) : UdfpsAnimationViewController<UdfpsBpView>( - view, - statusBarStateController, - shadeInteractor, - systemUIDialogManager, - dumpManager, - udfpsOverlayInteractor, -) { - override val tag = "UdfpsBpViewController" - - override fun shouldPauseAuth(): Boolean { - return false - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java index a3904caa9dcc..2863e29c9a34 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java @@ -87,7 +87,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Application; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.doze.DozeReceiver; import com.android.systemui.dump.DumpManager; import com.android.systemui.keyguard.ScreenLifecycle; @@ -98,7 +97,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.power.domain.interactor.PowerInteractor; import com.android.systemui.shade.domain.interactor.ShadeInteractor; import com.android.systemui.shared.system.SysUiStatsLog; -import com.android.systemui.statusbar.LockscreenShadeTransitionController; import com.android.systemui.statusbar.VibratorHelper; import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.phone.SystemUIDialogManager; @@ -162,7 +160,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull private final FalsingManager mFalsingManager; @NonNull private final PowerManager mPowerManager; @NonNull private final AccessibilityManager mAccessibilityManager; - @NonNull private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; @NonNull private final ConfigurationController mConfigurationController; @NonNull private final SystemClock mSystemClock; @NonNull private final UnlockedScreenOffAnimationController @@ -283,7 +280,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mKeyguardUpdateMonitor, mDialogManager, mDumpManager, - mLockscreenShadeTransitionController, mConfigurationController, mKeyguardStateController, mUnlockedScreenOffAnimationController, @@ -291,10 +287,9 @@ public class UdfpsController implements DozeReceiver, Dumpable { requestId, reason, callback, - (view, event, fromUdfpsView) -> onTouch( + (view, event) -> onTouch( requestId, - event, - fromUdfpsView + event ), mActivityTransitionAnimator, mPrimaryBouncerInteractor, @@ -374,9 +369,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mOverlay == null || mOverlay.isHiding()) { return; } - if (!DeviceEntryUdfpsRefactor.isEnabled()) { - ((UdfpsView) mOverlay.getTouchOverlay()).setDebugMessage(message); - } }); } @@ -391,7 +383,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { */ public void debugOnTouch(MotionEvent event) { final long requestId = (mOverlay != null) ? mOverlay.getRequestId() : 0L; - UdfpsController.this.onTouch(requestId, event, true); + UdfpsController.this.onTouch(requestId, event); } /** @@ -449,22 +441,10 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!mOverlayParams.equals(overlayParams)) { mOverlayParams = overlayParams; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if (mOverlay != null && mOverlay.getRequestReason() == REASON_AUTH_KEYGUARD) { - mOverlay.updateOverlayParams(mOverlayParams); - } else { - redrawOverlay(); - } + if (mOverlay != null && mOverlay.getRequestReason() == REASON_AUTH_KEYGUARD) { + mOverlay.updateOverlayParams(mOverlayParams); } else { - final boolean wasShowingAlternateBouncer = - mAlternateBouncerInteractor.isVisibleState(); - // When the bounds change it's always to re-create the overlay's window with new - // LayoutParams. If the overlay needs to be shown, this will re-create and show the - // overlay with the updated LayoutParams. Otherwise, the overlay will remain hidden. redrawOverlay(); - if (wasShowingAlternateBouncer) { - mKeyguardViewManager.showBouncer(true); - } } } } @@ -563,11 +543,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } } - private boolean onTouch(long requestId, @NonNull MotionEvent event, boolean fromUdfpsView) { - if (!fromUdfpsView) { - Log.e(TAG, "ignoring the touch injected from outside of UdfpsView"); - return false; - } + private boolean onTouch(long requestId, @NonNull MotionEvent event) { if (mOverlay == null) { Log.w(TAG, "ignoring onTouch with null overlay"); return false; @@ -591,13 +567,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!mIsAodInterruptActive) { mOnFingerDown = false; } - } else if (!DeviceEntryUdfpsRefactor.isEnabled()) { - if ((mLockscreenShadeTransitionController.getQSDragProgress() != 0f - && !mAlternateBouncerInteractor.isVisibleState()) - || mPrimaryBouncerInteractor.isInTransit()) { - Log.w(TAG, "ignoring touch due to qsDragProcess or primaryBouncerInteractor"); - return false; - } } final TouchProcessorResult result = mTouchProcessor.processTouch(event, mActivePointerId, @@ -661,22 +630,13 @@ public class UdfpsController implements DozeReceiver, Dumpable { mStatusBarStateController.isDozing()); break; - case UNCHANGED: - if (mActivePointerId == MotionEvent.INVALID_POINTER_ID - && mAlternateBouncerInteractor.isVisibleState()) { - // No pointer on sensor, forward to keyguard if alternateBouncer is visible - mKeyguardViewManager.onTouch(event); - } - default: break; } logBiometricTouch(processedTouch.getEvent(), data); // Always pilfer pointers that are within sensor area or when alternate bouncer is showing - if (mActivePointerId != MotionEvent.INVALID_POINTER_ID - || (mAlternateBouncerInteractor.isVisibleState() - && !DeviceEntryUdfpsRefactor.isEnabled())) { + if (mActivePointerId != MotionEvent.INVALID_POINTER_ID) { shouldPilfer = true; } @@ -692,14 +652,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { } private boolean shouldTryToDismissKeyguard() { - boolean onKeyguard = false; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - onKeyguard = mKeyguardStateController.isShowing(); - } else { - onKeyguard = mOverlay != null - && mOverlay.getAnimationViewController() - instanceof UdfpsKeyguardViewControllerLegacy; - } + boolean onKeyguard = mKeyguardStateController.isShowing(); return onKeyguard && mKeyguardStateController.canDismissLockScreen() && !mAttemptedToDismissKeyguard; @@ -719,7 +672,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { @NonNull FalsingManager falsingManager, @NonNull PowerManager powerManager, @NonNull AccessibilityManager accessibilityManager, - @NonNull LockscreenShadeTransitionController lockscreenShadeTransitionController, @NonNull ScreenLifecycle screenLifecycle, @NonNull VibratorHelper vibrator, @NonNull UdfpsHapticsSimulator udfpsHapticsSimulator, @@ -769,7 +721,6 @@ public class UdfpsController implements DozeReceiver, Dumpable { mFalsingManager = falsingManager; mPowerManager = powerManager; mAccessibilityManager = accessibilityManager; - mLockscreenShadeTransitionController = lockscreenShadeTransitionController; screenLifecycle.addObserver(mScreenObserver); mScreenOn = screenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_ON; mConfigurationController = configurationController; @@ -849,13 +800,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { @Override public void dozeTimeTick() { - if (mOverlay != null && mOverlay.getTouchOverlay() instanceof UdfpsView) { - DeviceEntryUdfpsRefactor.assertInLegacyMode(); - final View view = mOverlay.getTouchOverlay(); - if (view != null) { - ((UdfpsView) view).dozeTimeTick(); - } - } + } private void redrawOverlay() { @@ -915,17 +860,8 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (!isOptical()) { return; } - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if (mUdfpsDisplayMode != null) { - mUdfpsDisplayMode.disable(null); - } - } else { - if (view != null) { - UdfpsView udfpsView = (UdfpsView) view; - if (udfpsView.isDisplayConfigured()) { - udfpsView.unconfigureDisplay(); - } - } + if (mUdfpsDisplayMode != null) { + mUdfpsDisplayMode.disable(null); } } @@ -1118,11 +1054,7 @@ public class UdfpsController implements DozeReceiver, Dumpable { if (mIgnoreRefreshRate) { dispatchOnUiReady(requestId); } else { - if (DeviceEntryUdfpsRefactor.isEnabled()) { mUdfpsDisplayMode.enable(() -> dispatchOnUiReady(requestId)); - } else { - ((UdfpsView) view).configureDisplay(() -> dispatchOnUiReady(requestId)); - } } } diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt index 1bac0bc26a94..a1efc196dbee 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt @@ -23,8 +23,6 @@ import android.graphics.PixelFormat import android.graphics.Rect import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR import android.hardware.biometrics.BiometricRequestConstants.RequestReason @@ -42,7 +40,6 @@ import android.view.View import android.view.WindowManager import android.view.accessibility.AccessibilityManager import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener -import androidx.annotation.LayoutRes import androidx.annotation.VisibleForTesting import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.keyguard.KeyguardUpdateMonitor @@ -56,7 +53,6 @@ import com.android.systemui.biometrics.ui.viewmodel.DeviceEntryUdfpsTouchOverlay import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.dump.DumpManager import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState @@ -64,7 +60,6 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController @@ -102,7 +97,6 @@ constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val dialogManager: SystemUIDialogManager, private val dumpManager: DumpManager, - private val transitionController: LockscreenShadeTransitionController, private val configurationController: ConfigurationController, private val keyguardStateController: KeyguardStateController, private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, @@ -110,7 +104,7 @@ constructor( val requestId: Long, @RequestReason val requestReason: Int, private val controllerCallback: IUdfpsOverlayControllerCallback, - private val onTouch: (View, MotionEvent, Boolean) -> Boolean, + private val onTouch: (View, MotionEvent) -> Boolean, private val activityTransitionAnimator: ActivityTransitionAnimator, private val primaryBouncerInteractor: PrimaryBouncerInteractor, private val alternateBouncerInteractor: AlternateBouncerInteractor, @@ -133,23 +127,15 @@ constructor( .map {} // map to Unit private var listenForCurrentKeyguardState: Job? = null private var addViewRunnable: Runnable? = null - private var overlayViewLegacy: UdfpsView? = null - private set - private var overlayTouchView: UdfpsTouchOverlay? = null /** - * Get the current UDFPS overlay touch view which is a different View depending on whether the - * DeviceEntryUdfpsRefactor flag is enabled or not. + * Get the current UDFPS overlay touch view * * @return The view, when [isShowing], else null */ fun getTouchOverlay(): View? { - return if (DeviceEntryUdfpsRefactor.isEnabled) { - overlayTouchView - } else { - overlayViewLegacy - } + return overlayTouchView } private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams() @@ -161,7 +147,7 @@ constructor( WindowManager.LayoutParams( WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, 0 /* flags set in computeLayoutParams() */, - PixelFormat.TRANSLUCENT + PixelFormat.TRANSLUCENT, ) .apply { title = TAG @@ -188,10 +174,6 @@ constructor( val isHiding: Boolean get() = getTouchOverlay() == null - /** The animation controller if the overlay [isShowing]. */ - val animationViewController: UdfpsAnimationViewController<*>? - get() = overlayViewLegacy?.animationViewController - private var touchExplorationEnabled = false private fun shouldRemoveEnrollmentUi(): Boolean { @@ -199,7 +181,7 @@ constructor( return Settings.Global.getInt( context.contentResolver, SETTING_REMOVE_ENROLLMENT_UI, - 0 /* def */ + 0, /* def */ ) != 0 } return false @@ -212,63 +194,43 @@ constructor( overlayParams = params sensorBounds = Rect(params.sensorBounds) try { - if (DeviceEntryUdfpsRefactor.isEnabled) { - overlayTouchView = - (inflater.inflate(R.layout.udfps_touch_overlay, null, false) - as UdfpsTouchOverlay) - .apply { - // This view overlaps the sensor area - // prevent it from being selectable during a11y - if (requestReason.isImportantForAccessibility()) { - importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO - } - - addViewNowOrLater(this, null) - when (requestReason) { - REASON_AUTH_KEYGUARD -> - UdfpsTouchOverlayBinder.bind( - view = this, - viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(), - udfpsOverlayInteractor = udfpsOverlayInteractor, - ) - else -> - UdfpsTouchOverlayBinder.bind( - view = this, - viewModel = defaultUdfpsTouchOverlayViewModel.get(), - udfpsOverlayInteractor = udfpsOverlayInteractor, - ) - } - } - } else { - overlayViewLegacy = - (inflater.inflate(R.layout.udfps_view, null, false) as UdfpsView).apply { - overlayParams = params - setUdfpsDisplayModeProvider(udfpsDisplayModeProvider) - val animation = inflateUdfpsAnimation(this, controller) - if (animation != null) { - animation.init() - animationViewController = animation - } + overlayTouchView = + (inflater.inflate(R.layout.udfps_touch_overlay, null, false) + as UdfpsTouchOverlay) + .apply { // This view overlaps the sensor area // prevent it from being selectable during a11y if (requestReason.isImportantForAccessibility()) { importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO } - addViewNowOrLater(this, animation) - sensorRect = sensorBounds + addViewNowOrLater(this, null) + when (requestReason) { + REASON_AUTH_KEYGUARD -> + UdfpsTouchOverlayBinder.bind( + view = this, + viewModel = deviceEntryUdfpsTouchOverlayViewModel.get(), + udfpsOverlayInteractor = udfpsOverlayInteractor, + ) + else -> + UdfpsTouchOverlayBinder.bind( + view = this, + viewModel = defaultUdfpsTouchOverlayViewModel.get(), + udfpsOverlayInteractor = udfpsOverlayInteractor, + ) + } } - } + getTouchOverlay()?.apply { touchExplorationEnabled = accessibilityManager.isTouchExplorationEnabled overlayTouchListener = TouchExplorationStateChangeListener { if (accessibilityManager.isTouchExplorationEnabled) { - setOnHoverListener { v, event -> onTouch(v, event, true) } + setOnHoverListener { v, event -> onTouch(v, event) } setOnTouchListener(null) touchExplorationEnabled = true } else { setOnHoverListener(null) - setOnTouchListener { v, event -> onTouch(v, event, true) } + setOnTouchListener { v, event -> onTouch(v, event) } touchExplorationEnabled = false } } @@ -312,7 +274,6 @@ constructor( } fun updateOverlayParams(updatedOverlayParams: UdfpsOverlayParams) { - DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode() overlayParams = updatedOverlayParams sensorBounds = updatedOverlayParams.sensorBounds getTouchOverlay()?.let { @@ -326,108 +287,11 @@ constructor( } } - fun inflateUdfpsAnimation( - view: UdfpsView, - controller: UdfpsController - ): UdfpsAnimationViewController<*>? { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - - val isEnrollment = - when (requestReason) { - REASON_ENROLL_FIND_SENSOR, - REASON_ENROLL_ENROLLING -> true - else -> false - } - - val filteredRequestReason = - if (isEnrollment && shouldRemoveEnrollmentUi()) { - REASON_AUTH_OTHER - } else { - requestReason - } - - return when (filteredRequestReason) { - REASON_ENROLL_FIND_SENSOR, - REASON_ENROLL_ENROLLING -> { - // Enroll udfps UI is handled by settings, so use empty view here - UdfpsFpmEmptyViewController( - view.addUdfpsView(R.layout.udfps_fpm_empty_view) { - updateAccessibilityViewLocation(sensorBounds) - }, - statusBarStateController, - shadeInteractor, - dialogManager, - dumpManager, - udfpsOverlayInteractor, - ) - } - REASON_AUTH_KEYGUARD -> { - UdfpsKeyguardViewControllerLegacy( - view.addUdfpsView(R.layout.udfps_keyguard_view_legacy) { - updateSensorLocation(sensorBounds) - }, - statusBarStateController, - statusBarKeyguardViewManager, - keyguardUpdateMonitor, - dumpManager, - transitionController, - configurationController, - keyguardStateController, - unlockedScreenOffAnimationController, - dialogManager, - controller, - activityTransitionAnimator, - primaryBouncerInteractor, - alternateBouncerInteractor, - udfpsKeyguardAccessibilityDelegate, - selectedUserInteractor, - transitionInteractor, - shadeInteractor, - udfpsOverlayInteractor, - ) - } - REASON_AUTH_BP -> { - // note: empty controller, currently shows no visual affordance - UdfpsBpViewController( - view.addUdfpsView(R.layout.udfps_bp_view), - statusBarStateController, - shadeInteractor, - dialogManager, - dumpManager, - udfpsOverlayInteractor, - ) - } - REASON_AUTH_OTHER, - REASON_AUTH_SETTINGS -> { - UdfpsFpmEmptyViewController( - view.addUdfpsView(R.layout.udfps_fpm_empty_view), - statusBarStateController, - shadeInteractor, - dialogManager, - dumpManager, - udfpsOverlayInteractor, - ) - } - else -> { - Log.e(TAG, "Animation for reason $requestReason not supported yet") - null - } - } - } - /** Hide the overlay or return false and do nothing if it is already hidden. */ fun hide(): Boolean { val wasShowing = isShowing - overlayViewLegacy?.apply { - if (isDisplayConfigured) { - unconfigureDisplay() - } - animationViewController = null - } - if (DeviceEntryUdfpsRefactor.isEnabled) { - udfpsDisplayModeProvider.disable(null) - } + udfpsDisplayModeProvider.disable(null) getTouchOverlay()?.apply { if (this.parent != null) { windowManager.removeView(this) @@ -440,7 +304,6 @@ constructor( } } - overlayViewLegacy = null overlayTouchView = null overlayTouchListener = null listenForCurrentKeyguardState?.cancel() @@ -490,7 +353,7 @@ constructor( Surface.rotationToString(rot) + " animation=$animation" + " isGoingToSleep=${keyguardUpdateMonitor.isGoingToSleep}" + - " isOccluded=${keyguardStateController.isOccluded}" + " isOccluded=${keyguardStateController.isOccluded}", ) } else { Log.v(TAG, "Rotate UDFPS bounds " + Surface.rotationToString(rot)) @@ -498,14 +361,14 @@ constructor( rotatedBounds, overlayParams.naturalDisplayWidth, overlayParams.naturalDisplayHeight, - rot + rot, ) RotationUtils.rotateBounds( sensorBounds, overlayParams.naturalDisplayWidth, overlayParams.naturalDisplayHeight, - rot + rot, ) } } @@ -519,14 +382,7 @@ constructor( } private fun shouldRotate(animation: UdfpsAnimationViewController<*>?): Boolean { - val keyguardNotShowing = - if (DeviceEntryUdfpsRefactor.isEnabled) { - !keyguardStateController.isShowing - } else { - animation !is UdfpsKeyguardViewControllerLegacy - } - - if (keyguardNotShowing) { + if (!keyguardStateController.isShowing) { // always rotate view if we're not on the keyguard return true } @@ -534,16 +390,6 @@ constructor( // on the keyguard, make sure we don't rotate if we're going to sleep or not occluded return !(keyguardUpdateMonitor.isGoingToSleep || !keyguardStateController.isOccluded) } - - private inline fun <reified T : View> UdfpsView.addUdfpsView( - @LayoutRes id: Int, - init: T.() -> Unit = {} - ): T { - val subView = inflater.inflate(id, null) as T - addView(subView) - subView.init() - return subView - } } @RequestReason diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt deleted file mode 100644 index 0838855f4756..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyView.kt +++ /dev/null @@ -1,49 +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.systemui.biometrics - -import android.content.Context -import android.graphics.Rect -import android.util.AttributeSet -import android.view.View -import android.view.ViewGroup -import com.android.systemui.res.R - -/** - * View corresponding with udfps_fpm_empty_view.xml - * - * Currently doesn't draw anything. - */ -class UdfpsFpmEmptyView( - context: Context, - attrs: AttributeSet? -) : UdfpsAnimationView(context, attrs) { - - // Drawable isn't ever added to the view, so we don't currently show anything - private val fingerprintDrawable: UdfpsFpDrawable = UdfpsFpDrawable(context) - - override fun getDrawable(): UdfpsDrawable = fingerprintDrawable - - fun updateAccessibilityViewLocation(sensorBounds: Rect) { - val fingerprintAccessibilityView: View = - requireViewById(R.id.udfps_enroll_accessibility_view) - val params: ViewGroup.LayoutParams = fingerprintAccessibilityView.layoutParams - params.width = sensorBounds.width() - params.height = sensorBounds.height() - fingerprintAccessibilityView.layoutParams = params - fingerprintAccessibilityView.requestLayout() - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt deleted file mode 100644 index cfbbc26800d2..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt +++ /dev/null @@ -1,45 +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.systemui.biometrics - -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.phone.SystemUIDialogManager - -/** - * Class that coordinates non-HBM animations for non keyguard, or biometric prompt states. - * - * Currently doesn't draw anything. - */ -class UdfpsFpmEmptyViewController( - view: UdfpsFpmEmptyView, - statusBarStateController: StatusBarStateController, - shadeInteractor: ShadeInteractor, - systemUIDialogManager: SystemUIDialogManager, - dumpManager: DumpManager, - udfpsOverlayInteractor: UdfpsOverlayInteractor, -) : UdfpsAnimationViewController<UdfpsFpmEmptyView>( - view, - statusBarStateController, - shadeInteractor, - systemUIDialogManager, - dumpManager, - udfpsOverlayInteractor, -) { - override val tag = "UdfpsFpmOtherViewController" -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt deleted file mode 100644 index c3d9240c40a1..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt +++ /dev/null @@ -1,555 +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.systemui.biometrics - -import android.content.res.Configuration -import android.util.MathUtils -import android.view.View -import androidx.annotation.VisibleForTesting -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import com.android.app.animation.Interpolators -import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.animation.ActivityTransitionAnimator -import com.android.systemui.biometrics.UdfpsKeyguardViewLegacy.ANIMATE_APPEAR_ON_SCREEN_OFF -import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor -import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.dump.DumpManager -import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor -import com.android.systemui.keyguard.shared.model.Edge -import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER -import com.android.systemui.keyguard.shared.model.KeyguardState.AOD -import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING -import com.android.systemui.keyguard.shared.model.KeyguardState.GONE -import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED -import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER -import com.android.systemui.lifecycle.repeatWhenAttached -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.res.R -import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.LockscreenShadeTransitionController -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.OccludingAppBiometricUI -import com.android.systemui.statusbar.phone.SystemUIDialogManager -import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.user.domain.interactor.SelectedUserInteractor -import java.io.PrintWriter -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch - -/** Class that coordinates non-HBM animations during keyguard authentication. */ -@ExperimentalCoroutinesApi -open class UdfpsKeyguardViewControllerLegacy( - private val view: UdfpsKeyguardViewLegacy, - statusBarStateController: StatusBarStateController, - private val keyguardViewManager: StatusBarKeyguardViewManager, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, - dumpManager: DumpManager, - private val lockScreenShadeTransitionController: LockscreenShadeTransitionController, - private val configurationController: ConfigurationController, - private val keyguardStateController: KeyguardStateController, - private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController, - systemUIDialogManager: SystemUIDialogManager, - private val udfpsController: UdfpsController, - private val activityTransitionAnimator: ActivityTransitionAnimator, - private val primaryBouncerInteractor: PrimaryBouncerInteractor, - private val alternateBouncerInteractor: AlternateBouncerInteractor, - private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate, - private val selectedUserInteractor: SelectedUserInteractor, - private val transitionInteractor: KeyguardTransitionInteractor, - shadeInteractor: ShadeInteractor, - udfpsOverlayInteractor: UdfpsOverlayInteractor, -) : - UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>( - view, - statusBarStateController, - shadeInteractor, - systemUIDialogManager, - dumpManager, - udfpsOverlayInteractor, - ) { - private val uniqueIdentifier = this.toString() - private var showingUdfpsBouncer = false - private var udfpsRequested = false - private var qsExpansion = 0f - private var faceDetectRunning = false - private var statusBarState = 0 - private var transitionToFullShadeProgress = 0f - private var lastDozeAmount = 0f - private var panelExpansionFraction = 0f - private var launchTransitionFadingAway = false - private var isLaunchingActivity = false - private var activityLaunchProgress = 0f - private var inputBouncerExpansion = 0f - - private val stateListener: StatusBarStateController.StateListener = - object : StatusBarStateController.StateListener { - override fun onStateChanged(statusBarState: Int) { - this@UdfpsKeyguardViewControllerLegacy.statusBarState = statusBarState - updateAlpha() - updatePauseAuth() - } - } - - private val configurationListener: ConfigurationController.ConfigurationListener = - object : ConfigurationController.ConfigurationListener { - override fun onUiModeChanged() { - view.updateColor() - } - - override fun onThemeChanged() { - view.updateColor() - } - - override fun onConfigChanged(newConfig: Configuration) { - updateScaleFactor() - view.updatePadding() - view.updateColor() - } - } - - private val keyguardStateControllerCallback: KeyguardStateController.Callback = - object : KeyguardStateController.Callback { - override fun onUnlockedChanged() { - updatePauseAuth() - } - - override fun onLaunchTransitionFadingAwayChanged() { - launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway - updatePauseAuth() - } - } - - private val mActivityTransitionAnimatorListener: ActivityTransitionAnimator.Listener = - object : ActivityTransitionAnimator.Listener { - override fun onTransitionAnimationStart() { - isLaunchingActivity = true - activityLaunchProgress = 0f - updateAlpha() - } - - override fun onTransitionAnimationEnd() { - isLaunchingActivity = false - updateAlpha() - } - - override fun onTransitionAnimationProgress(linearProgress: Float) { - activityLaunchProgress = linearProgress - updateAlpha() - } - } - - private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback = - object : KeyguardViewManagerCallback { - override fun onQSExpansionChanged(qsExpansion: Float) { - this@UdfpsKeyguardViewControllerLegacy.qsExpansion = qsExpansion - updateAlpha() - updatePauseAuth() - } - } - - private val occludingAppBiometricUI: OccludingAppBiometricUI = - object : OccludingAppBiometricUI { - override fun requestUdfps(request: Boolean, color: Int) { - udfpsRequested = request - view.requestUdfps(request, color) - updateAlpha() - updatePauseAuth() - } - - override fun dump(pw: PrintWriter) { - pw.println(tag) - } - } - - override val tag: String - get() = TAG - - override fun onInit() { - super.onInit() - keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) - } - - init { - com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor.assertInLegacyMode() - view.repeatWhenAttached { - // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion - // can make the view not visible; and we still want to listen for events - // that may make the view visible again. - repeatOnLifecycle(Lifecycle.State.CREATED) { - listenForBouncerExpansion(this) - listenForAlternateBouncerVisibility(this) - listenForOccludedToAodTransition(this) - listenForGoneToAodTransition(this) - listenForLockscreenAodTransitions(this) - listenForAodToOccludedTransitions(this) - listenForAlternateBouncerToAodTransitions(this) - listenForDreamingToAodTransitions(this) - listenForPrimaryBouncerToAodTransitions(this) - } - } - } - - @VisibleForTesting - suspend fun listenForPrimaryBouncerToAodTransitions(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor - .transition( - edge = Edge.create(Scenes.Bouncer, AOD), - edgeWithoutSceneContainer = Edge.create(PRIMARY_BOUNCER, AOD) - ) - .collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForDreamingToAodTransitions(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor.transition(Edge.create(DREAMING, 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(Edge.create(ALTERNATE_BOUNCER, 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(Edge.create(AOD, OCCLUDED)).collect { transitionStep -> - view.onDozeAmountChanged( - 1f - transitionStep.value, - 1f - transitionStep.value, - UdfpsKeyguardViewLegacy.ANIMATION_NONE, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForOccludedToAodTransition(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor.transition(Edge.create(OCCLUDED, AOD)).collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForGoneToAodTransition(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor - .transition( - edge = Edge.create(Scenes.Gone, AOD), - edgeWithoutSceneContainer = Edge.create(GONE, AOD) - ) - .collect { transitionStep -> - view.onDozeAmountChanged( - transitionStep.value, - transitionStep.value, - ANIMATE_APPEAR_ON_SCREEN_OFF, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForLockscreenAodTransitions(scope: CoroutineScope): Job { - return scope.launch { - transitionInteractor.transitionValue(AOD).collect { - view.onDozeAmountChanged( - it, - it, - UdfpsKeyguardViewLegacy.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, - ) - } - } - } - - @VisibleForTesting - suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job { - return scope.launch { - primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float -> - inputBouncerExpansion = bouncerExpansion - - panelExpansionFraction = - if (keyguardViewManager.isPrimaryBouncerInTransit) { - aboutToShowBouncerProgress(1f - bouncerExpansion) - } else { - 1f - bouncerExpansion - } - updateAlpha() - updatePauseAuth() - } - } - } - - @VisibleForTesting - suspend fun listenForAlternateBouncerVisibility(scope: CoroutineScope): Job { - return scope.launch { - alternateBouncerInteractor.isVisible.collect { isVisible: Boolean -> - showUdfpsBouncer(isVisible) - } - } - } - - public override fun onViewAttached() { - super.onViewAttached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, uniqueIdentifier) - val dozeAmount = statusBarStateController.dozeAmount - lastDozeAmount = dozeAmount - stateListener.onDozeAmountChanged(dozeAmount, dozeAmount) - statusBarStateController.addCallback(stateListener) - udfpsRequested = false - launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway - keyguardStateController.addCallback(keyguardStateControllerCallback) - statusBarState = statusBarStateController.state - qsExpansion = keyguardViewManager.qsExpansion - keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback) - configurationController.addCallback(configurationListener) - updateScaleFactor() - view.updatePadding() - updateAlpha() - updatePauseAuth() - keyguardViewManager.setOccludingAppBiometricUI(occludingAppBiometricUI) - lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = this - activityTransitionAnimator.addListener(mActivityTransitionAnimatorListener) - view.startIconAsyncInflate { - val animationViewInternal: View = - view.requireViewById(R.id.udfps_animation_view_internal) - animationViewInternal.accessibilityDelegate = udfpsKeyguardAccessibilityDelegate - } - } - - public override fun onViewDetached() { - super.onViewDetached() - alternateBouncerInteractor.setAlternateBouncerUIAvailable(false, uniqueIdentifier) - faceDetectRunning = false - keyguardStateController.removeCallback(keyguardStateControllerCallback) - statusBarStateController.removeCallback(stateListener) - keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI) - configurationController.removeCallback(configurationListener) - if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) { - lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null - } - activityTransitionAnimator.removeListener(mActivityTransitionAnimatorListener) - keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback) - } - - override fun dump(pw: PrintWriter, args: Array<String>) { - super.dump(pw, args) - pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer") - pw.println( - "altBouncerInteractor#isAlternateBouncerVisible=" + - "${alternateBouncerInteractor.isVisibleState()}" - ) - pw.println( - "altBouncerInteractor#canShowAlternateBouncerForFingerprint=" + - "${alternateBouncerInteractor.canShowAlternateBouncerForFingerprint()}" - ) - pw.println("faceDetectRunning=$faceDetectRunning") - pw.println("statusBarState=" + StatusBarState.toString(statusBarState)) - pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress") - pw.println("qsExpansion=$qsExpansion") - pw.println("panelExpansionFraction=$panelExpansionFraction") - pw.println("unpausedAlpha=" + view.unpausedAlpha) - pw.println("udfpsRequestedByApp=$udfpsRequested") - pw.println("launchTransitionFadingAway=$launchTransitionFadingAway") - pw.println("lastDozeAmount=$lastDozeAmount") - pw.println("inputBouncerExpansion=$inputBouncerExpansion") - view.dump(pw) - } - - /** - * Overrides non-bouncer show logic in shouldPauseAuth to still show icon. - * - * @return whether the udfpsBouncer has been newly shown or hidden - */ - private fun showUdfpsBouncer(show: Boolean): Boolean { - if (showingUdfpsBouncer == show) { - return false - } - val udfpsAffordanceWasNotShowing = shouldPauseAuth() - showingUdfpsBouncer = show - if (showingUdfpsBouncer) { - if (udfpsAffordanceWasNotShowing) { - view.animateInUdfpsBouncer(null) - } - view.announceForAccessibility( - view.context.getString(R.string.accessibility_fingerprint_bouncer) - ) - } - updateAlpha() - updatePauseAuth() - return true - } - - /** - * Returns true if the fingerprint manager is running but we want to temporarily pause - * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so - * this can be overridden with the showBouncer method. - */ - override fun shouldPauseAuth(): Boolean { - if (showingUdfpsBouncer) { - return false - } - if ( - udfpsRequested && - !notificationShadeVisible && - !isInputBouncerFullyVisible() && - keyguardStateController.isShowing - ) { - return false - } - if (launchTransitionFadingAway) { - return true - } - - // Only pause auth if we're not on the keyguard AND we're not transitioning to doze. - // For the UnlockedScreenOffAnimation, the statusBarState is - // delayed. However, we still animate in the UDFPS affordance with the - // unlockedScreenOffDozeAnimator. - if ( - statusBarState != StatusBarState.KEYGUARD && - !unlockedScreenOffAnimationController.isAnimationPlaying() - ) { - return true - } - if (isBouncerExpansionGreaterThan(.5f)) { - return true - } - if ( - keyguardUpdateMonitor.getUserUnlockedWithBiometric( - selectedUserInteractor.getSelectedUserId() - ) - ) { - // If the device was unlocked by a biometric, immediately hide the UDFPS icon to avoid - // overlap with the LockIconView. Shortly afterwards, UDFPS will stop running. - return true - } - return view.unpausedAlpha < 255 * .1 - } - - fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean { - return inputBouncerExpansion >= bouncerExpansionThreshold - } - - fun isInputBouncerFullyVisible(): Boolean { - return inputBouncerExpansion == 1f - } - - override fun listenForTouchesOutsideView(): Boolean { - return true - } - - /** - * Set the progress we're currently transitioning to the full shade. 0.0f means we're not - * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down - * to expand the notification shade from the empty space in the middle of the lock screen. - */ - fun setTransitionToFullShadeProgress(progress: Float) { - transitionToFullShadeProgress = progress - updateAlpha() - } - - /** - * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is - * based on the doze amount. - */ - override fun updateAlpha() { - // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested, - // then the keyguard is occluded by some application - so instead use the input bouncer - // hidden amount to determine the fade. - val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction - var alpha: Int = - if (showingUdfpsBouncer) 255 - else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt() - if (!showingUdfpsBouncer) { - // swipe from top of the lockscreen to expand full QS: - alpha = - (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion))) - .toInt() - - // swipe from the middle (empty space) of lockscreen to expand the notification shade: - alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt() - - // Fade out the icon if we are animating an activity launch over the lockscreen and the - // activity didn't request the UDFPS. - if (isLaunchingActivity && !udfpsRequested) { - val udfpsActivityLaunchAlphaMultiplier = - 1f - - (activityLaunchProgress * - (ActivityTransitionAnimator.TIMINGS.totalDuration / 83)) - .coerceIn(0f, 1f) - alpha = (alpha * udfpsActivityLaunchAlphaMultiplier).toInt() - } - - // Fade out alpha when a dialog is shown - // Fade in alpha when a dialog is hidden - alpha = (alpha * view.dialogSuggestedAlpha).toInt() - } - view.unpausedAlpha = alpha - } - - private fun getInputBouncerHiddenAmt(): Float { - return 1f - inputBouncerExpansion - } - - /** Update the scale factor based on the device's resolution. */ - private fun updateScaleFactor() { - udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) } - } - - companion object { - const val TAG = "UdfpsKeyguardViewController" - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java deleted file mode 100644 index 6d4eea852d26..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacy.java +++ /dev/null @@ -1,330 +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.systemui.biometrics; - -import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset; -import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInProgressOffset; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; -import android.content.Context; -import android.content.res.ColorStateList; -import android.graphics.PorterDuff; -import android.graphics.PorterDuffColorFilter; -import android.graphics.Rect; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.util.MathUtils; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; - -import androidx.annotation.IntDef; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.asynclayoutinflater.view.AsyncLayoutInflater; - -import com.android.app.animation.Interpolators; -import com.android.settingslib.Utils; -import com.android.systemui.res.R; - -import com.airbnb.lottie.LottieAnimationView; -import com.airbnb.lottie.LottieProperty; -import com.airbnb.lottie.model.KeyPath; - -import java.io.PrintWriter; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -/** - * View corresponding with udfps_keyguard_view_legacy.xml - */ -public class UdfpsKeyguardViewLegacy extends UdfpsAnimationView { - private UdfpsDrawable mFingerprintDrawable; // placeholder - private LottieAnimationView mAodFp; - private LottieAnimationView mLockScreenFp; - - // used when highlighting fp icon: - private int mTextColorPrimary; - private ImageView mBgProtection; - boolean mUdfpsRequested; - - private AnimatorSet mBackgroundInAnimator = new AnimatorSet(); - private int mAlpha; // 0-255 - private float mScaleFactor = 1; - private Rect mSensorBounds = new Rect(); - - // AOD anti-burn-in offsets - private final int mMaxBurnInOffsetX; - private final int mMaxBurnInOffsetY; - private float mInterpolatedDarkAmount; - private int mAnimationType = ANIMATION_NONE; - private boolean mFullyInflated; - private Runnable mOnFinishInflateRunnable; - - public UdfpsKeyguardViewLegacy(Context context, @Nullable AttributeSet attrs) { - super(context, attrs); - mFingerprintDrawable = new UdfpsFpDrawable(context); - - mMaxBurnInOffsetX = context.getResources() - .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_x); - mMaxBurnInOffsetY = context.getResources() - .getDimensionPixelSize(R.dimen.udfps_burn_in_offset_y); - } - - /** - * Inflate internal udfps view on a background thread and call the onFinishRunnable - * when inflation is finished. - */ - public void startIconAsyncInflate(Runnable onFinishInflate) { - mOnFinishInflateRunnable = onFinishInflate; - // inflate Lottie views on a background thread in case it takes a while to inflate - AsyncLayoutInflater inflater = new AsyncLayoutInflater(mContext); - inflater.inflate(R.layout.udfps_keyguard_view_internal, this, - mLayoutInflaterFinishListener); - } - - @Override - public UdfpsDrawable getDrawable() { - return mFingerprintDrawable; - } - - @Override - void onSensorRectUpdated(RectF bounds) { - super.onSensorRectUpdated(bounds); - bounds.round(this.mSensorBounds); - postInvalidate(); - } - - @Override - void onDisplayConfiguring() { - } - - @Override - void onDisplayUnconfigured() { - } - - @Override - public boolean dozeTimeTick() { - updateBurnInOffsets(); - return true; - } - - private void updateBurnInOffsets() { - if (!mFullyInflated) { - return; - } - - // if we're animating from screen off, we can immediately place the icon in the - // AoD-burn in location, else we need to translate the icon from LS => AoD. - final float darkAmountForAnimation = mAnimationType == ANIMATE_APPEAR_ON_SCREEN_OFF - ? 1f : mInterpolatedDarkAmount; - final float burnInOffsetX = MathUtils.lerp(0f, - getBurnInOffset(mMaxBurnInOffsetX * 2, true /* xAxis */) - - mMaxBurnInOffsetX, darkAmountForAnimation); - final float burnInOffsetY = MathUtils.lerp(0f, - getBurnInOffset(mMaxBurnInOffsetY * 2, false /* xAxis */) - - mMaxBurnInOffsetY, darkAmountForAnimation); - final float burnInProgress = MathUtils.lerp(0f, getBurnInProgressOffset(), - darkAmountForAnimation); - - if (mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN && !mPauseAuth) { - mLockScreenFp.setTranslationX(burnInOffsetX); - mLockScreenFp.setTranslationY(burnInOffsetY); - mBgProtection.setAlpha(1f - mInterpolatedDarkAmount); - mLockScreenFp.setAlpha(1f - mInterpolatedDarkAmount); - } else if (darkAmountForAnimation == 0f) { - // we're on the lockscreen and should use mAlpha (changes based on shade expansion) - mLockScreenFp.setTranslationX(0); - mLockScreenFp.setTranslationY(0); - mBgProtection.setAlpha(mAlpha / 255f); - mLockScreenFp.setAlpha(mAlpha / 255f); - } else { - mBgProtection.setAlpha(0f); - mLockScreenFp.setAlpha(0f); - } - mLockScreenFp.setProgress(1f - mInterpolatedDarkAmount); - - mAodFp.setTranslationX(burnInOffsetX); - mAodFp.setTranslationY(burnInOffsetY); - mAodFp.setProgress(burnInProgress); - mAodFp.setAlpha(mInterpolatedDarkAmount); - - // done animating - final boolean doneAnimatingBetweenAodAndLS = - mAnimationType == ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN - && (mInterpolatedDarkAmount == 0f || mInterpolatedDarkAmount == 1f); - final boolean doneAnimatingUnlockedScreenOff = - mAnimationType == ANIMATE_APPEAR_ON_SCREEN_OFF - && (mInterpolatedDarkAmount == 1f); - if (doneAnimatingBetweenAodAndLS || doneAnimatingUnlockedScreenOff) { - mAnimationType = ANIMATION_NONE; - } - } - - void requestUdfps(boolean request, int color) { - mUdfpsRequested = request; - } - - void updateColor() { - if (!mFullyInflated) { - return; - } - - mTextColorPrimary = Utils.getColorAttrDefaultColor(mContext, - com.android.internal.R.attr.materialColorOnSurface); - final int backgroundColor = Utils.getColorAttrDefaultColor(getContext(), - com.android.internal.R.attr.materialColorSurfaceContainerHigh); - mBgProtection.setImageTintList(ColorStateList.valueOf(backgroundColor)); - mLockScreenFp.invalidate(); // updated with a valueCallback - } - - void setScaleFactor(float scale) { - mScaleFactor = scale; - } - - void updatePadding() { - if (mLockScreenFp == null || mAodFp == null) { - return; - } - - final int defaultPaddingPx = - getResources().getDimensionPixelSize(R.dimen.lock_icon_padding); - final int padding = (int) (defaultPaddingPx * mScaleFactor); - mLockScreenFp.setPadding(padding, padding, padding, padding); - mAodFp.setPadding(padding, padding, padding, padding); - } - - /** - * @param alpha between 0 and 255 - */ - void setUnpausedAlpha(int alpha) { - mAlpha = alpha; - updateAlpha(); - } - - /** - * @return alpha between 0 and 255 - */ - int getUnpausedAlpha() { - return mAlpha; - } - - @Override - protected int updateAlpha() { - int alpha = super.updateAlpha(); - updateBurnInOffsets(); - return alpha; - } - - @Override - int calculateAlpha() { - if (mPauseAuth) { - return 0; - } - return mAlpha; - } - - static final int ANIMATION_NONE = 0; - static final int ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN = 1; - static final int ANIMATE_APPEAR_ON_SCREEN_OFF = 2; - - @Retention(RetentionPolicy.SOURCE) - @IntDef({ANIMATION_NONE, ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN, ANIMATE_APPEAR_ON_SCREEN_OFF}) - private @interface AnimationType {} - - void onDozeAmountChanged(float linear, float eased, @AnimationType int animationType) { - mAnimationType = animationType; - mInterpolatedDarkAmount = eased; - updateAlpha(); - } - - void updateSensorLocation(@NonNull Rect sensorBounds) { - mSensorBounds.set(sensorBounds); - } - - /** - * Animates in the bg protection circle behind the fp icon to highlight the icon. - */ - void animateInUdfpsBouncer(Runnable onEndAnimation) { - if (mBackgroundInAnimator.isRunning() || !mFullyInflated) { - // already animating in or not yet inflated - return; - } - - // fade in and scale up - mBackgroundInAnimator = new AnimatorSet(); - mBackgroundInAnimator.playTogether( - ObjectAnimator.ofFloat(mBgProtection, View.ALPHA, 0f, 1f), - ObjectAnimator.ofFloat(mBgProtection, View.SCALE_X, 0f, 1f), - ObjectAnimator.ofFloat(mBgProtection, View.SCALE_Y, 0f, 1f)); - mBackgroundInAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN); - mBackgroundInAnimator.setDuration(500); - mBackgroundInAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - if (onEndAnimation != null) { - onEndAnimation.run(); - } - } - }); - mBackgroundInAnimator.start(); - } - - /** - * Print debugging information for this class. - */ - public void dump(PrintWriter pw) { - pw.println("UdfpsKeyguardView (" + this + ")"); - pw.println(" mPauseAuth=" + mPauseAuth); - pw.println(" mUnpausedAlpha=" + getUnpausedAlpha()); - pw.println(" mUdfpsRequested=" + mUdfpsRequested); - pw.println(" mInterpolatedDarkAmount=" + mInterpolatedDarkAmount); - pw.println(" mAnimationType=" + mAnimationType); - } - - private final AsyncLayoutInflater.OnInflateFinishedListener mLayoutInflaterFinishListener = - new AsyncLayoutInflater.OnInflateFinishedListener() { - @Override - public void onInflateFinished(View view, int resid, ViewGroup parent) { - mFullyInflated = true; - mAodFp = view.findViewById(R.id.udfps_aod_fp); - mLockScreenFp = view.findViewById(R.id.udfps_lockscreen_fp); - mBgProtection = view.findViewById(R.id.udfps_keyguard_fp_bg); - - updatePadding(); - updateColor(); - updateAlpha(); - - final LayoutParams lp = (LayoutParams) view.getLayoutParams(); - lp.width = mSensorBounds.width(); - lp.height = mSensorBounds.height(); - RectF relativeToView = getBoundsRelativeToView(new RectF(mSensorBounds)); - lp.setMarginsRelative((int) relativeToView.left, (int) relativeToView.top, - (int) relativeToView.right, (int) relativeToView.bottom); - parent.addView(view, lp); - - // requires call to invalidate to update the color - mLockScreenFp.addValueCallback(new KeyPath("**"), LottieProperty.COLOR_FILTER, - frameInfo -> new PorterDuffColorFilter(mTextColorPrimary, - PorterDuff.Mode.SRC_ATOP)); - mOnFinishInflateRunnable.run(); - } - }; -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt deleted file mode 100644 index 76bcd6e2863b..000000000000 --- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt +++ /dev/null @@ -1,117 +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.systemui.biometrics - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.graphics.Rect -import android.graphics.RectF -import android.util.AttributeSet -import android.util.Log -import android.view.MotionEvent -import android.widget.FrameLayout -import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams -import com.android.systemui.doze.DozeReceiver - -private const val TAG = "UdfpsView" - -/** - * The main view group containing all UDFPS animations. - */ -class UdfpsView( - context: Context, - attrs: AttributeSet? -) : FrameLayout(context, attrs), DozeReceiver { - // sensorRect may be bigger than the sensor. True sensor dimensions are defined in - // overlayParams.sensorBounds - var sensorRect = Rect() - private var mUdfpsDisplayMode: UdfpsDisplayModeProvider? = null - private val debugTextPaint = Paint().apply { - isAntiAlias = true - color = Color.BLUE - textSize = 32f - } - - /** View controller (can be different for enrollment, BiometricPrompt, Keyguard, etc.). */ - var animationViewController: UdfpsAnimationViewController<*>? = null - - /** Parameters that affect the position and size of the overlay. */ - var overlayParams = UdfpsOverlayParams() - - /** Debug message. */ - var debugMessage: String? = null - set(value) { - field = value - postInvalidate() - } - - /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */ - var isDisplayConfigured: Boolean = false - private set - - fun setUdfpsDisplayModeProvider(udfpsDisplayModeProvider: UdfpsDisplayModeProvider?) { - mUdfpsDisplayMode = udfpsDisplayModeProvider - } - - // Don't propagate any touch events to the child views. - override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { - return (animationViewController == null || !animationViewController!!.shouldPauseAuth()) - } - - override fun dozeTimeTick() { - animationViewController?.dozeTimeTick() - } - - override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - super.onLayout(changed, left, top, right, bottom) - - // Updates sensor rect in relation to the overlay view - animationViewController?.onSensorRectUpdated(RectF(sensorRect)) - } - - override fun onAttachedToWindow() { - super.onAttachedToWindow() - Log.v(TAG, "onAttachedToWindow") - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - Log.v(TAG, "onDetachedFromWindow") - } - - override fun onDraw(canvas: Canvas) { - super.onDraw(canvas) - if (!isDisplayConfigured) { - if (!debugMessage.isNullOrEmpty()) { - canvas.drawText(debugMessage!!, 0f, 160f, debugTextPaint) - } - } - } - - fun configureDisplay(onDisplayConfigured: Runnable) { - isDisplayConfigured = true - animationViewController?.onDisplayConfiguring() - mUdfpsDisplayMode?.enable(onDisplayConfigured) - } - - fun unconfigureDisplay() { - isDisplayConfigured = false - animationViewController?.onDisplayUnconfigured() - mUdfpsDisplayMode?.disable(null /* onDisabled */) - } -} diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt index 7503a8b4362d..a105d663424c 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/UdfpsTouchOverlayBinder.kt @@ -23,7 +23,6 @@ import androidx.lifecycle.repeatOnLifecycle import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor import com.android.systemui.biometrics.ui.view.UdfpsTouchOverlay import com.android.systemui.biometrics.ui.viewmodel.UdfpsTouchOverlayViewModel -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.lifecycle.repeatWhenAttached import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch @@ -42,14 +41,13 @@ object UdfpsTouchOverlayBinder { viewModel: UdfpsTouchOverlayViewModel, udfpsOverlayInteractor: UdfpsOverlayInteractor, ) { - if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) return view.repeatWhenAttached { repeatOnLifecycle(Lifecycle.State.CREATED) { launch { viewModel.shouldHandleTouches.collect { shouldHandleTouches -> Log.d( "UdfpsTouchOverlayBinder", - "[$view]: update shouldHandleTouches=$shouldHandleTouches" + "[$view]: update shouldHandleTouches=$shouldHandleTouches", ) view.isInvisible = !shouldHandleTouches udfpsOverlayInteractor.setHandleTouches(shouldHandleTouches) @@ -58,7 +56,7 @@ object UdfpsTouchOverlayBinder { .invokeOnCompletion { Log.d( "UdfpsTouchOverlayBinder", - "[$view-detached]: update shouldHandleTouches=false" + "[$view-detached]: update shouldHandleTouches=false", ) udfpsOverlayInteractor.setHandleTouches(false) } diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt index f1c3f949ffba..22b2888a51a9 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt @@ -24,7 +24,6 @@ import com.android.systemui.bouncer.shared.model.BouncerDismissActionModel import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.log.dagger.BouncerTableLog import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.log.table.logDiffsForTable @@ -88,7 +87,6 @@ interface KeyguardBouncerRepository { val showMessage: StateFlow<BouncerShowMessageModel?> val resourceUpdateRequests: StateFlow<Boolean> val alternateBouncerVisible: StateFlow<Boolean> - val alternateBouncerUIAvailable: StateFlow<Boolean> /** Last shown security mode of the primary bouncer (ie: pin/pattern/password/SIM) */ val lastShownSecurityMode: StateFlow<KeyguardSecurityModel.SecurityMode> @@ -126,8 +124,6 @@ interface KeyguardBouncerRepository { fun setAlternateVisible(isVisible: Boolean) - fun setAlternateBouncerUIAvailable(isAvailable: Boolean) - fun setLastShownSecurityMode(securityMode: KeyguardSecurityModel.SecurityMode) } @@ -199,9 +195,6 @@ constructor( private val _alternateBouncerVisible = MutableStateFlow(false) override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow() override var lastAlternateBouncerVisibleTime: Long = NOT_VISIBLE - private val _alternateBouncerUIAvailable = MutableStateFlow(false) - override val alternateBouncerUIAvailable: StateFlow<Boolean> = - _alternateBouncerUIAvailable.asStateFlow() init { setUpLogging() @@ -220,11 +213,6 @@ constructor( _alternateBouncerVisible.value = isVisible } - override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - _alternateBouncerUIAvailable.value = isAvailable - } - override fun setPrimaryShow(isShowing: Boolean) { _primaryBouncerShow.value = isShowing } @@ -320,9 +308,6 @@ constructor( resourceUpdateRequests .logDiffsForTable(buffer, "", "ResourceUpdateRequests", false) .launchIn(applicationScope) - alternateBouncerUIAvailable - .logDiffsForTable(buffer, "", "IsAlternateBouncerUIAvailable", false) - .launchIn(applicationScope) alternateBouncerVisible .logDiffsForTable(buffer, "", "AlternateBouncerVisible", false) .launchIn(applicationScope) diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt index 0949ea4d7797..9c2a10a444e2 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractor.kt @@ -17,22 +17,17 @@ package com.android.systemui.bouncer.domain.interactor import android.util.Log -import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor -import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.KeyguardState -import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.kotlin.BooleanFlowOperators.anyOf import com.android.systemui.util.time.SystemClock import dagger.Lazy @@ -55,13 +50,9 @@ import kotlinx.coroutines.flow.stateIn class AlternateBouncerInteractor @Inject constructor( - private val statusBarStateController: StatusBarStateController, - private val keyguardStateController: KeyguardStateController, private val bouncerRepository: KeyguardBouncerRepository, fingerprintPropertyRepository: FingerprintPropertyRepository, - private val biometricSettingsRepository: BiometricSettingsRepository, private val systemClock: SystemClock, - private val keyguardUpdateMonitor: KeyguardUpdateMonitor, private val deviceEntryBiometricsAllowedInteractor: Lazy<DeviceEntryBiometricsAllowedInteractor>, private val keyguardInteractor: Lazy<KeyguardInteractor>, @@ -73,17 +64,10 @@ constructor( val isVisible: Flow<Boolean> = bouncerRepository.alternateBouncerVisible private val alternateBouncerUiAvailableFromSource: HashSet<String> = HashSet() val alternateBouncerSupported: StateFlow<Boolean> = - if (DeviceEntryUdfpsRefactor.isEnabled) { - fingerprintPropertyRepository.sensorType - .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() } - .stateIn( - scope = scope, - started = SharingStarted.Eagerly, - initialValue = false, - ) - } else { - bouncerRepository.alternateBouncerUIAvailable - } + fingerprintPropertyRepository.sensorType + .map { sensorType -> sensorType.isUdfps() || sensorType.isPowerButton() } + .stateIn(scope = scope, started = SharingStarted.Eagerly, initialValue = false) + private val isDozingOrAod: Flow<Boolean> = anyOf( keyguardTransitionInteractor.get().transitionValue(KeyguardState.DOZING).map { @@ -106,7 +90,7 @@ constructor( combine( keyguardTransitionInteractor.get().currentKeyguardState, sceneInteractor.get().currentScene, - ::Pair + ::Pair, ) .flatMapLatest { (currentKeyguardState, transitionState) -> if (currentKeyguardState == KeyguardState.GONE) { @@ -122,7 +106,7 @@ constructor( .isFingerprintAuthCurrentlyAllowed, keyguardInteractor.get().isKeyguardDismissible, bouncerRepository.primaryBouncerShow, - isDozingOrAod + isDozingOrAod, ) { fingerprintAllowed, keyguardDismissible, @@ -141,37 +125,17 @@ constructor( } .distinctUntilChanged() .onEach { Log.d(TAG, "canShowAlternateBouncer changed to $it") } - .stateIn( - scope = scope, - started = WhileSubscribed(), - initialValue = false, - ) + .stateIn(scope = scope, started = WhileSubscribed(), initialValue = false) /** * Always shows the alternate bouncer. Requesters must check [canShowAlternateBouncer]` before * calling this. */ fun forceShow() { - if (DeviceEntryUdfpsRefactor.isUnexpectedlyInLegacyMode()) { - show() - return - } bouncerRepository.setAlternateVisible(true) } /** - * Sets the correct bouncer states to show the alternate bouncer if it can show. - * - * @return whether alternateBouncer is visible - * @deprecated use [forceShow] and manually check [canShowAlternateBouncer] beforehand - */ - fun show(): Boolean { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - bouncerRepository.setAlternateVisible(canShowAlternateBouncerForFingerprint()) - return isVisibleState() - } - - /** * Sets the correct bouncer states to hide the bouncer. Should only be called through * StatusBarKeyguardViewManager until ScrimController is refactored to use * alternateBouncerInteractor. @@ -189,28 +153,8 @@ constructor( return bouncerRepository.alternateBouncerVisible.value } - fun setAlternateBouncerUIAvailable(isAvailable: Boolean, token: String) { - DeviceEntryUdfpsRefactor.assertInLegacyMode() - if (isAvailable) { - alternateBouncerUiAvailableFromSource.add(token) - } else { - alternateBouncerUiAvailableFromSource.remove(token) - } - bouncerRepository.setAlternateBouncerUIAvailable( - alternateBouncerUiAvailableFromSource.isNotEmpty() - ) - } - fun canShowAlternateBouncerForFingerprint(): Boolean { - if (DeviceEntryUdfpsRefactor.isEnabled) { - return canShowAlternateBouncer.value - } - return alternateBouncerSupported.value && - biometricSettingsRepository.isFingerprintAuthCurrentlyAllowed.value && - !keyguardUpdateMonitor.isFingerprintLockedOut && - !keyguardStateController.isUnlocked && - !statusBarStateController.isDozing && - !bouncerRepository.primaryBouncerShow.value + return canShowAlternateBouncer.value } /** diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt index 2c026c0bb5ce..7337e5af51a1 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractor.kt @@ -30,6 +30,7 @@ import com.android.internal.util.EmergencyAffordanceManager import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor import com.android.systemui.bouncer.data.repository.EmergencyServicesRepository import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel +import com.android.systemui.bouncer.ui.helper.BouncerHapticPlayer import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background @@ -74,6 +75,7 @@ constructor( private val metricsLogger: MetricsLogger, private val dozeLogger: DozeLogger, private val sceneInteractor: Lazy<SceneInteractor>, + private val bouncerHapticPlayer: BouncerHapticPlayer, ) { /** The bouncer action button. If `null`, the button should not be shown. */ val actionButton: Flow<BouncerActionButtonModel?> = @@ -111,6 +113,8 @@ constructor( BouncerActionButtonModel( label = applicationContext.getString(R.string.lockscreen_emergency_call), onClick = { + // TODO(b/373930432): haptics should be played at the UI layer -> refactor + bouncerHapticPlayer.playEmergencyButtonClickFeedback() prepareToPerformAction() dozeLogger.logEmergencyCall() startEmergencyDialerActivity() diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt index d6b92115c64b..837390730c7a 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/helper/BouncerHapticPlayer.kt @@ -81,4 +81,11 @@ class BouncerHapticPlayer @Inject constructor(private val msdlPlayer: dagger.Laz /** Deliver MSDL feedback when a numpad key is pressed on the pin bouncer */ fun playNumpadKeyFeedback() = msdlPlayer.get().playToken(MSDLToken.KEYPRESS_STANDARD) + + /** Deliver MSDL feedback when clicking on the emergency button */ + fun playEmergencyButtonClickFeedback() { + if (isEnabled) { + msdlPlayer.get().playToken(MSDLToken.KEYPRESS_RETURN) + } + } } 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 4185aed3095c..148b9ea61e2c 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 @@ -17,7 +17,6 @@ package com.android.systemui.bouncer.ui.viewmodel import android.annotation.StringRes -import com.android.app.tracing.coroutines.flow.collectLatest import com.android.systemui.authentication.domain.interactor.AuthenticationResult import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.bouncer.domain.interactor.BouncerInteractor @@ -28,6 +27,7 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.receiveAsFlow sealed class AuthMethodBouncerViewModel( diff --git a/packages/SystemUI/src/com/android/systemui/common/usagestats/data/repository/UsageStatsRepository.kt b/packages/SystemUI/src/com/android/systemui/common/usagestats/data/repository/UsageStatsRepository.kt index e3f1174d4a5f..a695163ed155 100644 --- a/packages/SystemUI/src/com/android/systemui/common/usagestats/data/repository/UsageStatsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/common/usagestats/data/repository/UsageStatsRepository.kt @@ -19,7 +19,7 @@ package com.android.systemui.common.usagestats.data.repository import android.app.usage.UsageEvents import android.app.usage.UsageEventsQuery import android.app.usage.UsageStatsManager -import com.android.app.tracing.coroutines.withContext +import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.systemui.common.usagestats.data.model.UsageStatsQuery import com.android.systemui.common.usagestats.shared.model.ActivityEventModel import com.android.systemui.common.usagestats.shared.model.ActivityEventModel.Lifecycle diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt index 08a7c395e57f..8510915d55af 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt @@ -16,6 +16,7 @@ package com.android.systemui.communal +import android.os.UserHandle import android.provider.Settings import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey @@ -147,9 +148,10 @@ constructor( .emitOnStart() .onEach { screenTimeout = - systemSettings.getInt( + systemSettings.getIntForUser( Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_TIMEOUT, + UserHandle.USER_CURRENT, ) } .launchIn(bgScope) diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 3a04d026ee84..dc248059b3d4 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -23,7 +23,7 @@ import android.content.pm.UserInfo import android.os.UserHandle import android.os.UserManager import android.provider.Settings -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt index 3826fb40ea3d..428b83ddbb3a 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt @@ -16,7 +16,7 @@ package com.android.systemui.communal.domain.interactor -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.compose.animation.scene.TransitionKey diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt index ba96f4e56421..71bfe0c057e4 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt @@ -24,8 +24,7 @@ import android.view.ViewGroup import android.widget.FrameLayout import androidx.compose.ui.unit.IntSize import androidx.core.view.doOnLayout -import com.android.app.tracing.coroutines.flow.flowOn -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalWidgetResizing import com.android.systemui.common.ui.view.onLayoutChanged import com.android.systemui.communal.domain.model.CommunalContentModel @@ -41,6 +40,7 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn object CommunalAppWidgetHostViewBinder { private const val TAG = "CommunalAppWidgetHostViewBinder" diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt index 87fcdd7b97ee..a519649a8f1c 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/ResizeableItemFrameViewModel.kt @@ -19,7 +19,7 @@ package com.android.systemui.communal.ui.viewmodel import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors import androidx.compose.runtime.snapshotFlow -import com.android.app.tracing.coroutines.coroutineScope +import com.android.app.tracing.coroutines.coroutineScopeTraced as coroutineScope import com.android.systemui.lifecycle.ExclusiveActivatable import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.flow.Flow diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt index 07a7c7cba2fd..d5d3a927181b 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/util/WidgetViewFactory.kt @@ -19,7 +19,7 @@ package com.android.systemui.communal.util import android.content.Context import android.os.Bundle import android.util.SizeF -import com.android.app.tracing.coroutines.withContext +import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.widgets.AppWidgetHostListenerDelegate import com.android.systemui.communal.widgets.CommunalAppWidgetHost diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt index 542b98896986..c894267a61dd 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt @@ -21,7 +21,7 @@ import android.app.PendingIntent import android.content.Intent import android.view.View import android.widget.RemoteViews -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.KeyguardUpdateMonitor import com.android.systemui.Flags.communalWidgetTrampolineFix import com.android.systemui.animation.ActivityTransitionAnimator diff --git a/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt b/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt new file mode 100644 index 000000000000..5b1c9c8b8020 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/coroutines/Tracing.kt @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2024 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.coroutines + +import com.android.app.tracing.coroutines.createCoroutineTracingContext +import kotlin.coroutines.CoroutineContext + +fun newTracingContext(name: String): CoroutineContext { + return createCoroutineTracingContext(name) { className -> + className.startsWith("com.android.systemui.util.kotlin.JavaAdapter") || + className.startsWith("com.android.systemui.lifecycle.RepeatWhenAttached") + } +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt index b8c03c071572..c464a66ea0c3 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt @@ -17,25 +17,17 @@ package com.android.systemui.deviceentry import com.android.keyguard.EmptyLockIconViewController -import com.android.keyguard.LegacyLockIconViewController import com.android.keyguard.LockIconViewController import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigModule -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.keyguard.ui.transitions.DeviceEntryIconTransition import dagger.Lazy import dagger.Module import dagger.Provides import dagger.multibindings.Multibinds -@Module( - includes = - [ - DeviceEntryRepositoryModule::class, - FaceWakeUpTriggersConfigModule::class, - ], -) +@Module(includes = [DeviceEntryRepositoryModule::class, FaceWakeUpTriggersConfigModule::class]) abstract class DeviceEntryModule { /** * A set of DeviceEntryIconTransitions. Ensures that this can be injected even if it's empty. @@ -46,14 +38,9 @@ abstract class DeviceEntryModule { @Provides @SysUISingleton fun provideLockIconViewController( - legacyLockIconViewController: Lazy<LegacyLockIconViewController>, - emptyLockIconViewController: Lazy<EmptyLockIconViewController>, + emptyLockIconViewController: Lazy<EmptyLockIconViewController> ): LockIconViewController { - return if (DeviceEntryUdfpsRefactor.isEnabled) { - emptyLockIconViewController.get() - } else { - legacyLockIconViewController.get() - } + return emptyLockIconViewController.get() } } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt index ffe392a52c9f..88daa5de8816 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/FaceHelpMessageDeferralInteractor.kt @@ -21,7 +21,6 @@ import android.hardware.face.FaceManager import com.android.systemui.biometrics.FaceHelpMessageDeferralFactory import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.deviceentry.shared.model.AcquiredFaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.HelpFaceAuthenticationStatus import javax.inject.Inject @@ -55,9 +54,7 @@ constructor( faceAuthInteractor.authenticationStatus.filterIsInstance<HelpFaceAuthenticationStatus>() init { - if (DeviceEntryUdfpsRefactor.isEnabled) { - startUpdatingFaceHelpMessageDeferral() - } + startUpdatingFaceHelpMessageDeferral() } /** diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt deleted file mode 100644 index b5d5803ca6fb..000000000000 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/DeviceEntryUdfpsRefactor.kt +++ /dev/null @@ -1,53 +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.shared - -import com.android.systemui.Flags -import com.android.systemui.flags.FlagToken -import com.android.systemui.flags.RefactorFlagUtils - -/** Helper for reading or using the device entry udfps refactor flag state. */ -@Suppress("NOTHING_TO_INLINE") -object DeviceEntryUdfpsRefactor { - /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR - - /** A token used for dependency declaration */ - val token: FlagToken - get() = FlagToken(FLAG_NAME, isEnabled) - - /** Is the refactor enabled */ - @JvmStatic - inline val isEnabled - get() = Flags.deviceEntryUdfpsRefactor() - - /** - * Called to ensure code is only run when the flag is enabled. This protects users from the - * unintended behaviors caused by accidentally running new logic, while also crashing on an eng - * build to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun isUnexpectedlyInLegacyMode() = - RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME) - - /** - * Called to ensure code is only run when the flag is disabled. This will throw an exception if - * the flag is enabled to ensure that the refactor author catches issues in testing. - */ - @JvmStatic - inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME) -} diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt index 30624757ebe3..e3fce0007f26 100644 --- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayScopeRepository.kt @@ -17,8 +17,8 @@ package com.android.systemui.display.data.repository import android.view.Display -import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.systemui.CoreStartable +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.core.StatusBarConnectedDisplays @@ -69,7 +69,7 @@ constructor( backgroundApplicationScope } else { CoroutineScope( - backgroundDispatcher + createCoroutineTracingContext("DisplayScope$displayId") + backgroundDispatcher + newTracingContext("DisplayScope$displayId") ) } } diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt new file mode 100644 index 000000000000..2ce3e43389fa --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/PerDisplayStore.kt @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2024 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.display.data.repository + +import android.view.Display +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.qualifiers.Background +import java.io.PrintWriter +import java.util.concurrent.ConcurrentHashMap +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +/** Provides per display instances of [T]. */ +interface PerDisplayStore<T> { + + /** + * The instance for the default/main display of the device. For example, on a phone or a tablet, + * the default display is the internal/built-in display of the device. + * + * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. + */ + val defaultDisplay: T + + /** + * Returns an instance for a specific display id. + * + * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing + * displays. + */ + fun forDisplay(displayId: Int): T +} + +abstract class PerDisplayStoreImpl<T>( + @Background private val backgroundApplicationScope: CoroutineScope, + private val displayRepository: DisplayRepository, +) : PerDisplayStore<T>, CoreStartable { + + private val perDisplayInstances = ConcurrentHashMap<Int, T>() + + /** + * The instance for the default/main display of the device. For example, on a phone or a tablet, + * the default display is the internal/built-in display of the device. + * + * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. + */ + override val defaultDisplay: T + get() = forDisplay(Display.DEFAULT_DISPLAY) + + /** + * Returns an instance for a specific display id. + * + * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing + * displays. + */ + override fun forDisplay(displayId: Int): T { + if (displayRepository.getDisplay(displayId) == null) { + throw IllegalArgumentException("Display with id $displayId doesn't exist.") + } + return perDisplayInstances.computeIfAbsent(displayId) { + createInstanceForDisplay(displayId) + } + } + + abstract fun createInstanceForDisplay(displayId: Int): T + + override fun start() { + val instanceType = instanceClass.simpleName + backgroundApplicationScope.launch(CoroutineName("PerDisplayStore#<$instanceType>start")) { + displayRepository.displayRemovalEvent.collect { removedDisplayId -> + val removedInstance = perDisplayInstances.remove(removedDisplayId) + removedInstance?.let { onDisplayRemovalAction(it) } + } + } + } + + abstract val instanceClass: Class<T> + + /** + * Will be called when the display associated with [instance] was removed. It allows to perform + * any clean up if needed. + */ + open suspend fun onDisplayRemovalAction(instance: T) {} + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println(perDisplayInstances) + } +} + +class SingleDisplayStore<T>(defaultInstance: T) : PerDisplayStore<T> { + override val defaultDisplay: T = defaultInstance + + override fun forDisplay(displayId: Int): T = defaultDisplay +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt index 3992c3fb70b0..724f1c5323cf 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/homecontrols/HomeControlsDreamService.kt @@ -22,6 +22,7 @@ import android.service.controls.ControlsProviderService import android.service.dreams.DreamService import android.window.TaskFragmentInfo import com.android.systemui.controls.settings.ControlsSettingsRepository +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dreams.DreamLogger import com.android.systemui.dreams.homecontrols.domain.interactor.HomeControlsComponentInteractor @@ -39,7 +40,6 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import com.android.app.tracing.coroutines.createCoroutineTracingContext class HomeControlsDreamService @Inject @@ -54,7 +54,8 @@ constructor( ) : DreamService() { private val serviceJob = SupervisorJob() - private val serviceScope = CoroutineScope(bgDispatcher + serviceJob + createCoroutineTracingContext("HomeControlsDreamService")) + private val serviceScope = + CoroutineScope(bgDispatcher + serviceJob + newTracingContext("HomeControlsDreamService")) private val logger = DreamLogger(logBuffer, TAG) private lateinit var taskFragmentComponent: TaskFragmentComponent private val wakeLock: WakeLock by lazy { diff --git a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt index 4caf95b707b1..7fa7da192ad0 100644 --- a/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt +++ b/packages/SystemUI/src/com/android/systemui/education/dagger/ContextualEducationModule.kt @@ -16,10 +16,10 @@ package com.android.systemui.education.dagger -import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.systemui.CoreStartable import com.android.systemui.Flags import com.android.systemui.contextualeducation.GestureType +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.education.data.repository.ContextualEducationRepository import com.android.systemui.education.data.repository.UserContextualEducationRepository @@ -57,7 +57,9 @@ interface ContextualEducationModule { fun provideEduDataStoreScope( @Background bgDispatcher: CoroutineDispatcher ): CoroutineScope { - return CoroutineScope(bgDispatcher + SupervisorJob() + createCoroutineTracingContext("EduDataStoreScope")) + return CoroutineScope( + bgDispatcher + SupervisorJob() + newTracingContext("EduDataStoreScope") + ) } @EduClock diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt index e396767ecf8e..1dbcb3dfe399 100644 --- a/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/compose/ui/SliderHapticsViewModel.kt @@ -22,7 +22,7 @@ import androidx.compose.foundation.interaction.InteractionSource import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.unit.Velocity -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.haptics.slider.SeekableSliderTrackerConfig import com.android.systemui.haptics.slider.SliderDragVelocityProvider import com.android.systemui.haptics.slider.SliderEventType diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt index 6df8355550a0..a94df091230d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/CustomizationProvider.kt @@ -30,7 +30,7 @@ import android.net.Uri import android.os.Binder import android.os.Bundle import android.util.Log -import com.android.app.tracing.coroutines.runBlocking +import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking import com.android.systemui.SystemUIAppComponentFactoryBase import com.android.systemui.SystemUIAppComponentFactoryBase.ContextAvailableCallback import com.android.systemui.dagger.qualifiers.Main diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt index 690ae71aa56e..b7d0d453779f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt @@ -24,7 +24,7 @@ import android.annotation.SuppressLint import android.os.Trace import android.util.Log import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.withContext +import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.keyguard.shared.model.KeyguardState diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt index 96260770d89f..03cf1a4b3e9a 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt @@ -23,15 +23,12 @@ import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor -import com.android.systemui.keyguard.data.repository.BiometricType import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -41,7 +38,6 @@ import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach -import kotlinx.coroutines.launch /** * Encapsulates business logic for device entry events that impact the side fingerprint sensor @@ -51,7 +47,6 @@ import kotlinx.coroutines.launch class DeviceEntrySideFpsOverlayInteractor @Inject constructor( - @Application private val applicationScope: CoroutineScope, @Application private val context: Context, deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository, private val sceneInteractor: SceneInteractor, @@ -60,18 +55,6 @@ constructor( private val keyguardUpdateMonitor: KeyguardUpdateMonitor, ) { - init { - if (!DeviceEntryUdfpsRefactor.isEnabled) { - applicationScope.launch { - deviceEntryFingerprintAuthRepository.availableFpSensorType.collect { sensorType -> - if (sensorType == BiometricType.SIDE_FINGERPRINT) { - alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG) - } - } - } - } - } - private val isSideFpsIndicatorOnPrimaryBouncerEnabled: Boolean get() = context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer) @@ -90,7 +73,7 @@ constructor( primaryBouncerInteractor.startingDisappearAnimation.filterNotNull(), // Bouncer scene visibility changes. isBouncerSceneActive, - deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it } + deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it }, ) .map { isBouncerActive() && diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt index 4cf9ec8890d4..9896365abff9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.Log import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main 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 9a0a85823929..7b757657b1d9 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 @@ -20,7 +20,7 @@ import android.animation.ValueAnimator import android.annotation.SuppressLint import android.app.DreamManager import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalInteractor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt index 6b6a3dce630a..a6f0db5a86aa 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt index 58c8a0456241..606a7a988970 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGoneTransitionInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background 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 6d1d9cbd9aae..4d3727657f3e 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 @@ -19,7 +19,7 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.MathUtils import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor import com.android.systemui.dagger.SysUISingleton 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 2c3b481b9e16..26bf26b34a8a 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 @@ -22,7 +22,7 @@ import android.app.admin.DevicePolicyManager import android.content.Context import android.content.Intent import android.util.Log -import com.android.app.tracing.coroutines.withContext +import com.android.app.tracing.coroutines.withContextTraced as withContext import com.android.compose.animation.scene.ObservableTransitionState import com.android.internal.widget.LockPatternUtils import com.android.keyguard.logging.KeyguardQuickAffordancesLogger diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt index fe5f632c0b6a..23b7b664d4f1 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerMessageAreaViewBinder.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.ui.binder import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.AuthKeyguardMessageArea import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerMessageAreaViewModel import com.android.systemui.lifecycle.repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt index 7ca2c2004452..6ef9863f1112 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerUdfpsViewBinder.kt @@ -21,7 +21,7 @@ import android.content.res.ColorStateList import android.view.View import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerUdfpsIconViewModel import com.android.systemui.lifecycle.repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt index 1891af2d04b0..7a368999ecc9 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/AlternateBouncerViewBinder.kt @@ -29,7 +29,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application 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 a3f3342b9a6c..6c104a0a2470 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 @@ -28,7 +28,7 @@ import androidx.compose.ui.graphics.toArgb import androidx.core.view.isInvisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.keyguard.ui.view.DeviceEntryIconView import com.android.systemui.keyguard.ui.viewmodel.DeviceEntryBackgroundViewModel diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt index 0470e086ad27..5bad0168fe05 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt @@ -22,7 +22,7 @@ import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintSet import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.shared.model.KeyguardBlueprint diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt index 660a650fb916..3bdf7dac75b3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBottomAreaViewBinder.kt @@ -36,7 +36,7 @@ import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.settingslib.Utils import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.animation.Expandable diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt index ba94f45528c7..8b947a3bcb1e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt @@ -22,7 +22,7 @@ import android.view.ViewGroup import android.widget.TextView import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.KeyguardBottomAreaRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt index 76d3389c25b4..2fd9818a38f0 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardLongPressViewBinder.kt @@ -22,7 +22,7 @@ import android.view.accessibility.AccessibilityNodeInfo import androidx.core.view.accessibility.AccessibilityNodeInfoCompat import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.ui.view.LongPressHandlingView import com.android.systemui.keyguard.ui.viewmodel.KeyguardTouchHandlingViewModel import com.android.systemui.lifecycle.repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt index 2d225562081a..21d1db4a5052 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewClockViewBinder.kt @@ -33,7 +33,7 @@ import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.policy.SystemBarUtils import com.android.systemui.customization.R as customR import com.android.systemui.keyguard.shared.model.ClockSizeSetting @@ -129,15 +129,19 @@ object KeyguardPreviewClockViewBinder { constrainMaxHeight(customR.id.lockscreen_clock_view_large, 0) val largeClockTopMargin = SystemBarUtils.getStatusBarHeight(context) + - context.resources.getDimensionPixelSize( - customR.dimen.small_clock_padding_top - ) + + context.resources.getDimensionPixelSize(customR.dimen.small_clock_padding_top) + context.resources.getDimensionPixelSize( R.dimen.keyguard_smartspace_top_offset ) + getDimen(context, DATE_WEATHER_VIEW_HEIGHT) + getDimen(context, ENHANCED_SMARTSPACE_HEIGHT) - connect(customR.id.lockscreen_clock_view_large, TOP, PARENT_ID, TOP, largeClockTopMargin) + connect( + customR.id.lockscreen_clock_view_large, + TOP, + PARENT_ID, + TOP, + largeClockTopMargin, + ) connect(customR.id.lockscreen_clock_view_large, START, PARENT_ID, START) connect( customR.id.lockscreen_clock_view_large, @@ -146,12 +150,11 @@ object KeyguardPreviewClockViewBinder { ConstraintSet.END, ) - // In preview, we'll show UDFPS icon for UDFPS devices and nothing for non-UDFPS // devices, but we need position of device entry icon to constrain clock if (getConstraint(lockId) != null) { connect(customR.id.lockscreen_clock_view_large, BOTTOM, lockId, TOP) - } else { + } else { // Copied calculation codes from applyConstraints in DefaultDeviceEntrySection val bottomPaddingPx = context.resources.getDimensionPixelSize(R.dimen.lock_icon_margin_bottom) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt index 4b75b804e52b..baa681282a0b 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardPreviewSmartspaceViewBinder.kt @@ -22,7 +22,7 @@ import android.view.View import androidx.core.view.isInvisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.shared.model.ClockSizeSetting import com.android.systemui.keyguard.ui.viewmodel.KeyguardPreviewSmartspaceViewModel import com.android.systemui.lifecycle.repeatWhenAttached diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt index 27dd18d2b24e..cfd6481234ed 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt @@ -30,7 +30,7 @@ import androidx.core.view.isVisible import androidx.core.view.updateLayoutParams import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.keyguard.logging.KeyguardQuickAffordancesLogger import com.android.settingslib.Utils import com.android.systemui.animation.Expandable 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 ea70fd02eed5..89bca0c6a373 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 @@ -39,7 +39,7 @@ import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle import com.android.app.animation.Interpolators -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.jank.InteractionJankMonitor import com.android.internal.jank.InteractionJankMonitor.CUJ_SCREEN_OFF_SHOW_AOD import com.android.keyguard.AuthInteractionProperties diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt index 4150ceb8aa31..79360370d937 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSettingsViewBinder.kt @@ -22,7 +22,7 @@ import android.view.View import androidx.core.view.isVisible import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.common.ui.binder.TextViewBinder diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt index 8b74f5dc791d..de4a1b03203c 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt @@ -21,7 +21,7 @@ import androidx.constraintlayout.helper.widget.Layer import androidx.constraintlayout.widget.ConstraintLayout import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.IntraBlueprintTransition.Config diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt index fd27dc39299b..3934917d13c4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindViewBinder.kt @@ -16,7 +16,7 @@ package com.android.systemui.keyguard.ui.binder -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager import com.android.systemui.keyguard.ui.viewmodel.KeyguardSurfaceBehindViewModel import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt index b2ee68967878..2df17c39d90d 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/LightRevealScrimViewBinder.kt @@ -18,7 +18,7 @@ package com.android.systemui.keyguard.ui.binder import androidx.lifecycle.Lifecycle import androidx.lifecycle.repeatOnLifecycle -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.ui.viewmodel.LightRevealScrimViewModel import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.statusbar.LightRevealScrim diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt index ae46dd3a6ef3..b1ce47ee79e6 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/WindowManagerLockscreenVisibilityViewBinder.kt @@ -16,7 +16,7 @@ package com.android.systemui.keyguard.ui.binder -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.keyguard.WindowManagerLockscreenVisibilityManager import com.android.systemui.keyguard.ui.viewmodel.WindowManagerLockscreenVisibilityViewModel import kotlinx.coroutines.CoroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt index dd8980dabb23..08d35a72ab12 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt @@ -47,7 +47,6 @@ import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID import androidx.constraintlayout.widget.ConstraintSet.START import androidx.constraintlayout.widget.ConstraintSet.TOP import androidx.core.view.isInvisible -import com.android.app.tracing.coroutines.createCoroutineTracingContext import com.android.internal.policy.SystemBarUtils import com.android.keyguard.ClockEventController import com.android.keyguard.KeyguardClockSwitch @@ -57,6 +56,7 @@ import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.communal.ui.binder.CommunalTutorialIndicatorViewBinder import com.android.systemui.communal.ui.viewmodel.CommunalTutorialIndicatorViewModel +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -189,7 +189,7 @@ constructor( CoroutineScope( applicationScope.coroutineContext + Job() + - createCoroutineTracingContext("KeyguardPreviewRenderer") + newTracingContext("KeyguardPreviewRenderer") ) disposables += DisposableHandle { coroutineScope.cancel() } clockController.setFallbackWeatherData(WeatherData.getPlaceholderWeatherData()) diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt index 075a1d2893f7..9355200daec7 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt @@ -25,7 +25,7 @@ import android.os.Messenger import android.util.ArrayMap import android.util.Log import androidx.annotation.VisibleForTesting -import com.android.app.tracing.coroutines.runBlocking +import com.android.app.tracing.coroutines.runBlockingTraced as runBlocking import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt index 0d55709e94d6..f765e60c1c70 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.viewmodel import androidx.annotation.VisibleForTesting import com.android.app.tracing.FlowTracing.traceEmissionCount +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor @@ -141,6 +142,7 @@ constructor( fadeInAlpha, fadeOutAlpha, ) + .flowName("transitionAlpha") .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt index df1394bbfa5f..7c02f28c0696 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Hydrator.kt @@ -19,7 +19,7 @@ package com.android.systemui.lifecycle import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.snapshots.StateFactoryMarker -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.app.tracing.coroutines.traceCoroutine import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.coroutineScope diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt index 555969859a1f..a86bfb18fb70 100644 --- a/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt +++ b/packages/SystemUI/src/com/android/systemui/lifecycle/RepeatWhenAttached.kt @@ -17,7 +17,6 @@ package com.android.systemui.lifecycle -import android.os.Trace import android.view.View import android.view.ViewTreeObserver import androidx.annotation.MainThread @@ -25,11 +24,8 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.lifecycleScope -import com.android.app.tracing.coroutines.createCoroutineTracingContext -import com.android.app.tracing.coroutines.traceCoroutine -import com.android.systemui.Flags.coroutineTracing +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.util.Assert -import com.android.systemui.util.Compile import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated import kotlin.coroutines.CoroutineContext @@ -83,25 +79,13 @@ fun View.repeatWhenAttached( // default behavior. Instead, we want it to run on the view's UI thread since the user will // presumably want to call view methods that require being called from said UI thread. val lifecycleCoroutineContext = MAIN_DISPATCHER_SINGLETON + coroutineContext - val traceName = - if (Compile.IS_DEBUG && coroutineTracing()) { - inferTraceSectionName() - } else { - DEFAULT_TRACE_NAME - } var lifecycleOwner: ViewLifecycleOwner? = null val onAttachListener = object : View.OnAttachStateChangeListener { override fun onViewAttachedToWindow(v: View) { Assert.isMainThread() lifecycleOwner?.onDestroy() - lifecycleOwner = - createLifecycleOwnerAndRun( - traceName, - view, - lifecycleCoroutineContext, - block, - ) + lifecycleOwner = createLifecycleOwnerAndRun(view, lifecycleCoroutineContext, block) } override fun onViewDetachedFromWindow(v: View) { @@ -112,13 +96,7 @@ fun View.repeatWhenAttached( addOnAttachStateChangeListener(onAttachListener) if (view.isAttachedToWindow) { - lifecycleOwner = - createLifecycleOwnerAndRun( - traceName, - view, - lifecycleCoroutineContext, - block, - ) + lifecycleOwner = createLifecycleOwnerAndRun(view, lifecycleCoroutineContext, block) } return DisposableHandle { @@ -131,14 +109,15 @@ fun View.repeatWhenAttached( } private fun createLifecycleOwnerAndRun( - nameForTrace: String, view: View, coroutineContext: CoroutineContext, block: suspend LifecycleOwner.(View) -> Unit, ): ViewLifecycleOwner { return ViewLifecycleOwner(view).apply { onCreate() - lifecycleScope.launch(coroutineContext) { traceCoroutine(nameForTrace) { block(view) } } + // TODO(b/370595466): Refactor to support installing CoroutineTracingContext on the + // top-level CoroutineScope used as the lifecycleScope + lifecycleScope.launch(coroutineContext) { block(view) } } } @@ -167,9 +146,7 @@ private fun createLifecycleOwnerAndRun( * └───────────────┴───────────────────┴──────────────┴─────────────────┘ * ``` */ -class ViewLifecycleOwner( - private val view: View, -) : LifecycleOwner { +class ViewLifecycleOwner(private val view: View) : LifecycleOwner { private val windowVisibleListener = ViewTreeObserver.OnWindowVisibilityChangeListener { updateState() } @@ -205,28 +182,6 @@ class ViewLifecycleOwner( } } -private fun isFrameInteresting(frame: StackWalker.StackFrame): Boolean = - frame.className != CURRENT_CLASS_NAME && frame.className != JAVA_ADAPTER_CLASS_NAME - -/** Get a name for the trace section include the name of the call site. */ -private fun inferTraceSectionName(): String { - try { - Trace.traceBegin(Trace.TRACE_TAG_APP, "RepeatWhenAttachedKt#inferTraceSectionName") - val interestingFrame = - StackWalker.getInstance().walk { stream -> - stream.filter(::isFrameInteresting).limit(5).findFirst() - } - return if (interestingFrame.isPresent) { - val f = interestingFrame.get() - "${f.className}#${f.methodName}:${f.lineNumber} [$DEFAULT_TRACE_NAME]" - } else { - DEFAULT_TRACE_NAME - } - } finally { - Trace.traceEnd(Trace.TRACE_TAG_APP) - } -} - /** * Runs the given [block] in a new coroutine when `this` [View]'s Window's [WindowLifecycleState] is * at least at [state] (or immediately after calling this function if the window is already at least @@ -368,8 +323,7 @@ private val ViewTreeObserver.isWindowVisible * an extension function, and plumbing dagger-injected instances for static usage has little * benefit. */ -private val MAIN_DISPATCHER_SINGLETON = - Dispatchers.Main + createCoroutineTracingContext("RepeatWhenAttached") +private val MAIN_DISPATCHER_SINGLETON = Dispatchers.Main + newTracingContext("RepeatWhenAttached") private const val DEFAULT_TRACE_NAME = "repeatWhenAttached" private const val CURRENT_CLASS_NAME = "com.android.systemui.lifecycle.RepeatWhenAttachedKt" private const val JAVA_ADAPTER_CLASS_NAME = "com.android.systemui.util.kotlin.JavaAdapterKt" diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt index 544dbddeb3f0..f40ad065e5fc 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt @@ -16,13 +16,13 @@ package com.android.systemui.mediaprojection.appselector -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.app.Activity import android.content.ComponentName import android.content.Context import android.os.UserHandle import androidx.lifecycle.DefaultLifecycleObserver import com.android.launcher3.icons.IconFactory +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerLabelLoader import com.android.systemui.mediaprojection.appselector.data.ActivityTaskManagerThumbnailLoader @@ -134,7 +134,11 @@ interface MediaProjectionAppSelectorModule { @MediaProjectionAppSelector @MediaProjectionAppSelectorScope fun provideCoroutineScope(@Application applicationScope: CoroutineScope): CoroutineScope = - CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("MediaProjectionAppSelectorScope")) + CoroutineScope( + applicationScope.coroutineContext + + SupervisorJob() + + newTracingContext("MediaProjectionAppSelectorScope") + ) } } diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java index c3729c0dcdfd..212da9ffb9c5 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -122,8 +122,11 @@ public class MediaProjectionPermissionActivity extends Activity { final Intent launchingIntent = getIntent(); mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra( EXTRA_USER_REVIEW_GRANTED_CONSENT, false); - - mPackageName = getCallingPackage(); + if (com.android.systemui.Flags.mediaProjectionRequestAttributionFix()) { + mPackageName = getLaunchedFromPackage(); + } else { + mPackageName = getCallingPackage(); + } // This activity is launched directly by an app, or system server. System server provides // the package name through the intent if so. diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt index 96386e520d5a..0166176721c3 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/domain/GestureInteractor.kt @@ -16,7 +16,6 @@ package com.android.systemui.navigationbar.gestural.domain -import com.android.app.tracing.coroutines.flow.flowOn import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -35,6 +34,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext diff --git a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt index 3aa9daac4866..d0f6f7961889 100644 --- a/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt +++ b/packages/SystemUI/src/com/android/systemui/notetask/NoteTaskController.kt @@ -37,7 +37,7 @@ import android.os.UserManager import android.provider.Settings import android.widget.Toast import androidx.annotation.VisibleForTesting -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 9c5231d716da..49b44cb95e46 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -23,7 +23,9 @@ import android.graphics.Rect import android.os.Bundle import android.util.IndentingPrintWriter import android.view.LayoutInflater +import android.view.MotionEvent import android.view.View +import android.view.ViewConfiguration import android.view.ViewGroup import android.widget.FrameLayout import androidx.activity.OnBackPressedDispatcher @@ -35,6 +37,7 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith +import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer @@ -43,6 +46,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.windowInsetsPadding +import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -83,6 +87,7 @@ import com.android.systemui.Dumpable import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.dump.DumpManager import com.android.systemui.lifecycle.repeatWhenAttached +import com.android.systemui.lifecycle.setSnapshotBinding import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager import com.android.systemui.media.controls.ui.view.MediaHost import com.android.systemui.media.dagger.MediaModule.QS_PANEL @@ -145,6 +150,7 @@ constructor( private val qqsVisible = MutableStateFlow(false) private val qqsPositionOnRoot = Rect() private val composeViewPositionOnScreen = Rect() + private val scrollState = ScrollState(0) // Inside object for namespacing private val notificationScrimClippingParams = @@ -210,6 +216,9 @@ constructor( context, { notificationScrimClippingParams.isEnabled }, { notificationScrimClippingParams.params.top }, + // Only allow scrolling when we are fully expanded. That way, we don't intercept + // swipes in lockscreen (when somehow QS is receiving touches). + { scrollState.canScrollForward && viewModel.expansionState.value.progress >= 1f }, ) frame.addView( composeView, @@ -488,12 +497,8 @@ constructor( private fun setListenerCollections() { lifecycleScope.launch { lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - launch { - // TODO - // setListenerJob( - // scrollListener, - // - // ) + this@QSFragmentCompose.view?.setSnapshotBinding { + scrollListener.value?.onQsPanelScrollChanged(scrollState.value) } launch { setListenerJob( @@ -528,6 +533,7 @@ constructor( viewModel.containerViewModel.quickQuickSettingsViewModel.squishinessViewModel .squishiness .collectAsStateWithLifecycle() + Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) { Box( modifier = @@ -591,7 +597,12 @@ constructor( modifier = Modifier.element(ElementKeys.QuickSettingsContent).fillMaxSize().weight(1f) ) { - Column { + DisposableEffect(Unit) { + lifecycleScope.launch { scrollState.scrollTo(0) } + onDispose { lifecycleScope.launch { scrollState.scrollTo(0) } } + } + + Column(modifier = Modifier.verticalScroll(scrollState)) { Spacer( modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() } ) @@ -601,15 +612,14 @@ constructor( ) } } - QuickSettingsTheme { - FooterActions( - viewModel = viewModel.footerActionsViewModel, - qsVisibilityLifecycleOwner = this@QSFragmentCompose, - modifier = - Modifier.sysuiResTag("qs_footer_actions") - .element(ElementKeys.FooterActions), - ) - } + } + QuickSettingsTheme { + FooterActions( + viewModel = viewModel.footerActionsViewModel, + qsVisibilityLifecycleOwner = this@QSFragmentCompose, + modifier = + Modifier.sysuiResTag("qs_footer_actions").element(ElementKeys.FooterActions), + ) } } } @@ -791,13 +801,17 @@ private class ExpansionTransition(currentProgress: Float) : private const val EDIT_MODE_TIME_MILLIS = 500 /** - * Ignore touches below the value returned by [clippingTopProvider], when clipping is enabled, as - * per [clippingEnabledProvider]. + * Performs different touch handling based on the state of the ComposeView: + * * Ignore touches below the value returned by [clippingTopProvider], when clipping is enabled, as + * per [clippingEnabledProvider]. + * * Intercept touches that would overscroll QS forward and instead allow them to be used to close + * the shade. */ private class FrameLayoutTouchPassthrough( context: Context, private val clippingEnabledProvider: () -> Boolean, private val clippingTopProvider: () -> Int, + private val canScrollForwardQs: () -> Boolean, ) : FrameLayout(context) { override fun isTransformedTouchPointInView( x: Float, @@ -811,4 +825,32 @@ private class FrameLayoutTouchPassthrough( super.isTransformedTouchPointInView(x, y, child, outLocalPoint) } } + + val touchSlop = ViewConfiguration.get(context).scaledTouchSlop + var downY = 0f + + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { + // If there's a touch on this view and we can scroll down, we don't want to be intercepted + val action = ev.actionMasked + + when (action) { + MotionEvent.ACTION_DOWN -> { + // If we can scroll down, make sure none of our parents intercepts us. + if (canScrollForwardQs()) { + parent?.requestDisallowInterceptTouchEvent(true) + } + downY = ev.y + } + + MotionEvent.ACTION_MOVE -> { + val y = ev.y.toInt() + val yDiff: Float = y - downY + if (yDiff < -touchSlop && !canScrollForwardQs()) { + // Intercept touches that are overscrolling. + return true + } + } + } + return super.onInterceptTouchEvent(ev) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt index 26b2e2b7bd66..424be90ba2ec 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/PaginatedGridRepository.kt @@ -38,6 +38,6 @@ constructor( ) { val rows = configurationRepository.onConfigurationChange.emitOnStart().map { - resources.getInteger(R.integer.quick_settings_max_rows) + resources.getInteger(R.integer.quick_settings_paginated_grid_num_rows) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt index f7c71ceb9e6c..ee0cfb304db0 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/data/repository/QuickQuickSettingsRowRepository.kt @@ -34,6 +34,6 @@ constructor( ) { val rows = configurationRepository.onConfigurationChange.emitOnStart().map { - resources.getInteger(R.integer.quick_qs_panel_max_rows) + resources.getInteger(R.integer.quick_qs_paginated_grid_num_rows) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt new file mode 100644 index 000000000000..b9994d7bb821 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/BounceableInfo.kt @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2024 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.panels.ui.compose + +import com.android.compose.animation.Bounceable +import com.android.systemui.qs.panels.shared.model.SizedTile +import com.android.systemui.qs.panels.ui.model.GridCell +import com.android.systemui.qs.panels.ui.model.TileGridCell +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel +import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel + +data class BounceableInfo( + val bounceable: BounceableTileViewModel, + val previousTile: Bounceable?, + val nextTile: Bounceable?, + val bounceEnd: Boolean, +) + +fun List<Pair<GridCell, BounceableTileViewModel>>.bounceableInfo( + index: Int, + columns: Int, +): BounceableInfo { + val cell = this[index].first as TileGridCell + // Only look for neighbor bounceables if they are on the same row + val onLastColumn = cell.onLastColumn(cell.column, columns) + val previousTile = getOrNull(index - 1)?.takeIf { cell.column != 0 } + val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn } + return BounceableInfo(this[index].second, previousTile?.second, nextTile?.second, !onLastColumn) +} + +fun List<BounceableTileViewModel>.bounceableInfo( + sizedTile: SizedTile<TileViewModel>, + index: Int, + column: Int, + columns: Int, +): BounceableInfo { + // Only look for neighbor bounceables if they are on the same row + val onLastColumn = sizedTile.onLastColumn(column, columns) + val previousTile = getOrNull(index - 1)?.takeIf { column != 0 } + val nextTile = getOrNull(index + 1)?.takeIf { !onLastColumn } + return BounceableInfo(this[index], previousTile, nextTile, !onLastColumn) +} + +private fun <T> SizedTile<T>.onLastColumn(column: Int, columns: Int): Boolean { + return column == columns - width +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt index 770fd785723a..74fa0fef21d7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt @@ -116,9 +116,9 @@ class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>, private val c regenerateGrid() _tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell) } else { - // Add the tile with a temporary row which will get reassigned when + // Add the tile with a temporary row/col which will get reassigned when // regenerating spacers - _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0)) + _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0, 0)) } regenerateGrid() diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt index e749475479d8..d55763aaeddb 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt @@ -19,7 +19,7 @@ package com.android.systemui.qs.panels.ui.compose import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.requiredHeight import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons @@ -97,7 +97,9 @@ constructor( TileGrid(tiles = page, modifier = Modifier, editModeStart = {}) } } - Box(modifier = Modifier.height(FooterHeight).fillMaxWidth()) { + // Use requiredHeight so it won't be squished if the view doesn't quite fit. As this is + // expected to be inside a scrollable container, this should not be an issue. + Box(modifier = Modifier.requiredHeight(FooterHeight).fillMaxWidth()) { PagerDots( pagerState = pagerState, activeColor = MaterialTheme.colorScheme.primary, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt index a645b51404e7..1c7a334d3ef2 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt @@ -20,6 +20,8 @@ import androidx.compose.foundation.layout.Box import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap @@ -29,6 +31,7 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.panels.ui.compose.infinitegrid.Tile +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel import com.android.systemui.qs.shared.ui.ElementKeys.toElementKey import com.android.systemui.res.R @@ -38,10 +41,11 @@ fun SceneScope.QuickQuickSettings( viewModel: QuickQuickSettingsViewModel, modifier: Modifier = Modifier, ) { - val sizedTiles by - viewModel.tileViewModels.collectAsStateWithLifecycle(initialValue = emptyList()) + val sizedTiles by viewModel.tileViewModels.collectAsStateWithLifecycle() val tiles = sizedTiles.fastMap { it.tile } + val bounceables = remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by viewModel.squishinessViewModel.squishiness.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() DisposableEffect(tiles) { val token = Any() @@ -49,6 +53,7 @@ fun SceneScope.QuickQuickSettings( onDispose { tiles.forEach { it.stopListening(token) } } } val columns by viewModel.columns.collectAsStateWithLifecycle() + var cellIndex = 0 Box(modifier = modifier) { GridAnchor() VerticalSpannedGrid( @@ -59,11 +64,15 @@ fun SceneScope.QuickQuickSettings( modifier = Modifier.sysuiResTag("qqs_tile_layout"), ) { spanIndex -> val it = sizedTiles[spanIndex] + val column = cellIndex % columns + cellIndex += it.width Tile( tile = it.tile, iconOnly = it.isIcon, modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), squishiness = { squishiness }, + coroutineScope = scope, + bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt index 9ec5a82a18d7..71fa0ac30fb7 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/CommonTile.kt @@ -31,6 +31,7 @@ import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.size 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.LaunchedEffect @@ -54,6 +55,7 @@ import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics import androidx.compose.ui.semantics.stateDescription import androidx.compose.ui.semantics.toggleableState +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import com.android.compose.modifiers.background import com.android.compose.modifiers.thenIf @@ -64,7 +66,6 @@ import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.AccessibilityUiState import com.android.systemui.res.R -import kotlinx.coroutines.delay private const val TEST_TAG_TOGGLE = "qs_tile_toggle_target" @@ -138,13 +139,20 @@ fun LargeTileLabels( accessibilityUiState: AccessibilityUiState? = null, ) { Column(verticalArrangement = Arrangement.Center, modifier = modifier.fillMaxHeight()) { - Text(label, color = colors.label, modifier = Modifier.tileMarquee()) + Text( + label, + style = MaterialTheme.typography.labelLarge, + color = colors.label, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) if (!TextUtils.isEmpty(secondaryLabel)) { Text( secondaryLabel ?: "", color = colors.secondaryLabel, + style = MaterialTheme.typography.bodyMedium, modifier = - Modifier.tileMarquee().thenIf( + Modifier.thenIf( accessibilityUiState?.stateDescription?.contains(secondaryLabel ?: "") == true ) { @@ -182,10 +190,7 @@ fun SmallTileContent( rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = true) } else { var atEnd by remember(icon.res) { mutableStateOf(false) } - LaunchedEffect(key1 = icon.res) { - delay(350) - atEnd = true - } + LaunchedEffect(key1 = icon.res) { atEnd = true } rememberAnimatedVectorPainter(animatedImageVector = image, atEnd = atEnd) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt index 45c1e48840ee..5c2a2bd2b78c 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/EditTile.kt @@ -22,13 +22,13 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.animateFloatAsState -import androidx.compose.animation.core.animateIntAsState import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.LocalOverscrollConfiguration import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxScope @@ -46,7 +46,6 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.lazy.grid.GridCells -import androidx.compose.foundation.lazy.grid.LazyGridItemScope import androidx.compose.foundation.lazy.grid.LazyGridScope import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.rememberScrollState @@ -66,19 +65,17 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.BiasAlignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.drawBehind -import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor -import androidx.compose.ui.graphics.drawscope.Stroke -import androidx.compose.ui.layout.Layout import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onSizeChanged import androidx.compose.ui.layout.positionInRoot @@ -98,23 +95,26 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.util.fastMap -import androidx.compose.ui.zIndex +import com.android.compose.animation.bounceable import com.android.compose.modifiers.background import com.android.compose.modifiers.height import com.android.systemui.common.ui.compose.load import com.android.systemui.qs.panels.shared.model.SizedTile import com.android.systemui.qs.panels.shared.model.SizedTileImpl +import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.DragAndDropState import com.android.systemui.qs.panels.ui.compose.EditTileListState +import com.android.systemui.qs.panels.ui.compose.bounceableInfo import com.android.systemui.qs.panels.ui.compose.dragAndDropRemoveZone import com.android.systemui.qs.panels.ui.compose.dragAndDropTileList import com.android.systemui.qs.panels.ui.compose.dragAndDropTileSource import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileArrangementPadding +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.TileHeight import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.ToggleTargetSize import com.android.systemui.qs.panels.ui.compose.infinitegrid.EditModeTileDefaults.CurrentTilesGridPadding import com.android.systemui.qs.panels.ui.compose.selection.MutableSelectionState -import com.android.systemui.qs.panels.ui.compose.selection.ResizingHandle +import com.android.systemui.qs.panels.ui.compose.selection.ResizableTileContainer import com.android.systemui.qs.panels.ui.compose.selection.TileWidths import com.android.systemui.qs.panels.ui.compose.selection.clearSelectionTile import com.android.systemui.qs.panels.ui.compose.selection.rememberSelectionState @@ -122,11 +122,14 @@ import com.android.systemui.qs.panels.ui.compose.selection.selectableTile import com.android.systemui.qs.panels.ui.model.GridCell import com.android.systemui.qs.panels.ui.model.SpacerGridCell import com.android.systemui.qs.panels.ui.model.TileGridCell +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.shared.model.groupAndSort import com.android.systemui.res.R +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.delay +import kotlinx.coroutines.launch object TileType @@ -241,15 +244,20 @@ private fun CurrentTilesGrid( onSetTiles: (List<TileSpec>) -> Unit, ) { val currentListState by rememberUpdatedState(listState) - val tileHeight = CommonTileDefaults.TileHeight val totalRows = listState.tiles.lastOrNull()?.row ?: 0 val totalHeight by animateDpAsState( - gridHeight(totalRows + 1, tileHeight, TileArrangementPadding, CurrentTilesGridPadding), + gridHeight(totalRows + 1, TileHeight, TileArrangementPadding, CurrentTilesGridPadding), label = "QSEditCurrentTilesGridHeight", ) val gridState = rememberLazyGridState() var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) } + val coroutineScope = rememberCoroutineScope() + + val cells = + remember(listState.tiles) { + listState.tiles.fastMap { Pair(it, BounceableTileViewModel()) } + } TileLazyGrid( state = gridState, @@ -272,7 +280,7 @@ private fun CurrentTilesGrid( } .testTag(CURRENT_TILES_GRID_TEST_TAG), ) { - EditTiles(listState.tiles, listState, selectionState) { spec -> + EditTiles(cells, columns, listState, selectionState, coroutineScope) { spec -> // Toggle the current size of the tile currentListState.isIcon(spec)?.let { onResize(spec, !it) } } @@ -286,10 +294,10 @@ private fun AvailableTileGrid( columns: Int, dragAndDropState: DragAndDropState, ) { - // Available tiles aren't visible during drag and drop, so the row isn't needed + // Available tiles aren't visible during drag and drop, so the row/col isn't needed val groupedTiles = remember(tiles.fastMap { it.tile.category }, tiles.fastMap { it.tile.label }) { - groupAndSort(tiles.fastMap { TileGridCell(it, 0) }) + groupAndSort(tiles.fastMap { TileGridCell(it, 0, 0) }) } val labelColors = EditModeTileDefaults.editTileColors() @@ -349,24 +357,26 @@ private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any { /** * Adds a list of [GridCell] to the lazy grid * - * @param cells the list of [GridCell] + * @param cells the pairs of [GridCell] to [BounceableTileViewModel] * @param dragAndDropState the [DragAndDropState] for this grid * @param selectionState the [MutableSelectionState] for this grid * @param onToggleSize the callback when a tile's size is toggled */ fun LazyGridScope.EditTiles( - cells: List<GridCell>, + cells: List<Pair<GridCell, BounceableTileViewModel>>, + columns: Int, dragAndDropState: DragAndDropState, selectionState: MutableSelectionState, + coroutineScope: CoroutineScope, onToggleSize: (spec: TileSpec) -> Unit, ) { items( count = cells.size, - key = { cells[it].key(it, dragAndDropState) }, - span = { cells[it].span }, + key = { cells[it].first.key(it, dragAndDropState) }, + span = { cells[it].first.span }, contentType = { TileType }, ) { index -> - when (val cell = cells[index]) { + when (val cell = cells[index].first) { is TileGridCell -> if (dragAndDropState.isMoving(cell.tile.tileSpec)) { // If the tile is being moved, replace it with a visible spacer @@ -385,6 +395,9 @@ fun LazyGridScope.EditTiles( dragAndDropState = dragAndDropState, selectionState = selectionState, onToggleSize = onToggleSize, + coroutineScope = coroutineScope, + bounceableInfo = cells.bounceableInfo(index, columns), + modifier = Modifier.animateItem(), ) } is SpacerGridCell -> SpacerGridCell() @@ -393,12 +406,15 @@ fun LazyGridScope.EditTiles( } @Composable -private fun LazyGridItemScope.TileGridCell( +private fun TileGridCell( cell: TileGridCell, index: Int, dragAndDropState: DragAndDropState, selectionState: MutableSelectionState, onToggleSize: (spec: TileSpec) -> Unit, + coroutineScope: CoroutineScope, + bounceableInfo: BounceableInfo, + modifier: Modifier = Modifier, ) { val stateDescription = stringResource(id = R.string.accessibility_qs_edit_position, index + 1) var selected by remember { mutableStateOf(false) } @@ -407,6 +423,9 @@ private fun LazyGridItemScope.TileGridCell( targetValue = if (selected) 1f else 0f, label = "QSEditTileSelectionAlpha", ) + val selectionColor = MaterialTheme.colorScheme.primary + val colors = EditModeTileDefaults.editTileColors() + val currentBounceableInfo by rememberUpdatedState(bounceableInfo) LaunchedEffect(selectionState.selection?.tileSpec) { selectionState.selection?.let { @@ -420,152 +439,61 @@ private fun LazyGridItemScope.TileGridCell( selected = selectionState.selection?.tileSpec == cell.tile.tileSpec } - val modifier = - Modifier.animateItem() - .semantics(mergeDescendants = true) { - this.stateDescription = stateDescription - contentDescription = cell.tile.label.text - customActions = - listOf( - // TODO(b/367748260): Add final accessibility actions - CustomAccessibilityAction("Toggle size") { - onToggleSize(cell.tile.tileSpec) - true - } - ) - } - .height(CommonTileDefaults.TileHeight) - .fillMaxWidth() - - val content = - @Composable { - EditTile( - tileViewModel = cell.tile, - iconOnly = cell.isIcon, - selectionAlpha = { selectionAlpha }, - modifier = - Modifier.fillMaxSize() - .selectableTile(cell.tile.tileSpec, selectionState) - .dragAndDropTileSource( - SizedTileImpl(cell.tile, cell.width), - dragAndDropState, - selectionState::unSelect, - ), - ) - } - - if (selected) { - SelectedTile( - isIcon = cell.isIcon, - selectionAlpha = { selectionAlpha }, - selectionState = selectionState, - modifier = modifier.zIndex(2f), // 2f to display this tile over neighbors when dragged - content = content, - ) - } else { - UnselectedTile( - selectionAlpha = { selectionAlpha }, - selectionState = selectionState, - modifier = modifier, - content = content, - ) - } -} - -@Composable -private fun SelectedTile( - isIcon: Boolean, - selectionAlpha: () -> Float, - selectionState: MutableSelectionState, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { // Current base, min and max width of this tile var tileWidths: TileWidths? by remember { mutableStateOf(null) } - - // Animated diff between the current width and the resized width of the tile. We can't use - // animateContentSize here as the tile is sometimes unbounded. - val remainingOffset by - animateIntAsState( - selectionState.resizingState?.let { tileWidths?.base?.minus(it.width) ?: 0 } ?: 0, - label = "QSEditTileWidthOffset", - ) - val padding = with(LocalDensity.current) { TileArrangementPadding.roundToPx() } - Box( - modifier.onSizeChanged { - val min = if (isIcon) it.width else (it.width - padding) / 2 - val max = if (isIcon) (it.width * 2) + padding else it.width - tileWidths = TileWidths(it.width, min, max) - } + + ResizableTileContainer( + selected = selected, + selectionState = selectionState, + selectionAlpha = { selectionAlpha }, + selectionColor = selectionColor, + tileWidths = { tileWidths }, + modifier = + modifier + .height(TileHeight) + .fillMaxWidth() + .onSizeChanged { + // Grab the size before the bounceable to get the idle width + val min = if (cell.isIcon) it.width else (it.width - padding) / 2 + val max = if (cell.isIcon) (it.width * 2) + padding else it.width + tileWidths = TileWidths(it.width, min, max) + } + .bounceable( + bounceable = currentBounceableInfo.bounceable, + previousBounceable = currentBounceableInfo.previousTile, + nextBounceable = currentBounceableInfo.nextTile, + orientation = Orientation.Horizontal, + bounceEnd = currentBounceableInfo.bounceEnd, + ), ) { - val handle = - @Composable { - ResizingHandle( - enabled = true, - selectionState = selectionState, - transition = selectionAlpha, - tileWidths = { tileWidths }, + Box( + modifier + .fillMaxSize() + .semantics(mergeDescendants = true) { + this.stateDescription = stateDescription + contentDescription = cell.tile.label.text + customActions = + listOf( + // TODO(b/367748260): Add final accessibility actions + CustomAccessibilityAction("Toggle size") { + onToggleSize(cell.tile.tileSpec) + true + } + ) + } + .selectableTile(cell.tile.tileSpec, selectionState) { + coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() } + } + .dragAndDropTileSource( + SizedTileImpl(cell.tile, cell.width), + dragAndDropState, + selectionState::unSelect, ) - } - - Layout(contents = listOf(content, handle)) { - (contentMeasurables, handleMeasurables), - constraints -> - // Grab the width from the resizing state if a resize is in progress, otherwise fill the - // max width - val width = - selectionState.resizingState?.width ?: (constraints.maxWidth - remainingOffset) - val contentPlaceable = - contentMeasurables.first().measure(constraints.copy(maxWidth = width)) - val handlePlaceable = handleMeasurables.first().measure(constraints) - - // Place the dot vertically centered on the right edge - val handleX = contentPlaceable.width - (handlePlaceable.width / 2) - val handleY = (contentPlaceable.height / 2) - (handlePlaceable.height / 2) - - layout(constraints.maxWidth, constraints.maxHeight) { - contentPlaceable.place(0, 0) - handlePlaceable.place(handleX, handleY) - } - } - } -} - -@Composable -private fun UnselectedTile( - selectionAlpha: () -> Float, - selectionState: MutableSelectionState, - modifier: Modifier = Modifier, - content: @Composable () -> Unit, -) { - val handle = - @Composable { - ResizingHandle( - enabled = false, - selectionState = selectionState, - transition = selectionAlpha, - ) - } - - Box(modifier) { - Layout(contents = listOf(content, handle)) { - (contentMeasurables, handleMeasurables), - constraints -> - val contentPlaceable = - contentMeasurables - .first() - .measure(constraints.copy(maxWidth = constraints.maxWidth)) - val handlePlaceable = handleMeasurables.first().measure(constraints) - - // Place the dot vertically centered on the right edge - val handleX = contentPlaceable.width - (handlePlaceable.width / 2) - val handleY = (contentPlaceable.height / 2) - (handlePlaceable.height / 2) - - layout(constraints.maxWidth, constraints.maxHeight) { - contentPlaceable.place(0, 0) - handlePlaceable.place(handleX, handleY) - } + .tileBackground(colors.background) + .tilePadding() + ) { + EditTile(tile = cell.tile, iconOnly = cell.isIcon) } } } @@ -588,19 +516,19 @@ private fun AvailableTileGridCell( verticalArrangement = spacedBy(CommonTileDefaults.TilePadding, Alignment.Top), modifier = modifier, ) { - EditTileContainer( - colors = colors, - modifier = - Modifier.fillMaxWidth() - .height(CommonTileDefaults.TileHeight) - .clearSelectionTile(selectionState) - .semantics(mergeDescendants = true) { - onClick(onClickActionName) { false } - this.stateDescription = stateDescription - } - .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) { - selectionState.unSelect() - }, + Box( + Modifier.fillMaxWidth() + .height(TileHeight) + .clearSelectionTile(selectionState) + .semantics(mergeDescendants = true) { + onClick(onClickActionName) { false } + this.stateDescription = stateDescription + } + .dragAndDropTileSource(SizedTileImpl(cell.tile, cell.width), dragAndDropState) { + selectionState.unSelect() + } + .tileBackground(colors.background) + .tilePadding() ) { // Icon SmallTileContent( @@ -626,16 +554,14 @@ private fun AvailableTileGridCell( @Composable private fun SpacerGridCell(modifier: Modifier = Modifier) { // By default, spacers are invisible and exist purely to catch drag movements - Box(modifier.height(CommonTileDefaults.TileHeight).fillMaxWidth()) + Box(modifier.height(TileHeight).fillMaxWidth()) } @Composable -fun EditTile( - tileViewModel: EditTileViewModel, +fun BoxScope.EditTile( + tile: EditTileViewModel, iconOnly: Boolean, - modifier: Modifier = Modifier, colors: TileColors = EditModeTileDefaults.editTileColors(), - selectionAlpha: () -> Float = { 1f }, ) { // Animated horizontal alignment from center (0f) to start (-1f) val alignmentValue by @@ -646,68 +572,36 @@ fun EditTile( val alignment by remember { derivedStateOf { BiasAlignment(horizontalBias = alignmentValue, verticalBias = 0f) } } + // Icon + Box(Modifier.size(ToggleTargetSize).align(alignment)) { + SmallTileContent( + icon = tile.icon, + color = colors.icon, + animateToEnd = true, + modifier = Modifier.align(Alignment.Center), + ) + } - EditTileContainer(colors = colors, selectionAlpha = selectionAlpha, modifier = modifier) { - // Icon - Box(Modifier.size(ToggleTargetSize).align(alignment)) { - SmallTileContent( - icon = tileViewModel.icon, - color = colors.icon, - animateToEnd = true, - modifier = Modifier.align(Alignment.Center), - ) - } - - // Labels, positioned after the icon - AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { - LargeTileLabels( - label = tileViewModel.label.text, - secondaryLabel = tileViewModel.appName?.text, - colors = colors, - modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), - ) - } + // Labels, positioned after the icon + AnimatedVisibility(visible = !iconOnly, enter = fadeIn(), exit = fadeOut()) { + LargeTileLabels( + label = tile.label.text, + secondaryLabel = tile.appName?.text, + colors = colors, + modifier = Modifier.padding(start = ToggleTargetSize + TileArrangementPadding), + ) } } -@Composable -private fun EditTileContainer( - colors: TileColors, - modifier: Modifier = Modifier, - selectionAlpha: () -> Float = { 0f }, - selectionColor: Color = MaterialTheme.colorScheme.primary, - content: @Composable BoxScope.() -> Unit = {}, -) { - Box( - Modifier.wrapContentSize().drawWithContent { - drawContent() - drawRoundRect( - SolidColor(selectionColor), - cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), - style = Stroke(EditModeTileDefaults.SelectedBorderWidth.toPx()), - alpha = selectionAlpha(), - ) - } - ) { - Box( - modifier = - modifier - .drawBehind { - drawRoundRect( - SolidColor(colors.background), - cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), - ) - } - .tilePadding(), - content = content, - ) +private fun Modifier.tileBackground(color: Color): Modifier { + return drawBehind { + drawRoundRect(SolidColor(color), cornerRadius = CornerRadius(InactiveCornerRadius.toPx())) } } private object EditModeTileDefaults { const val PLACEHOLDER_ALPHA = .3f val EditGridHeaderHeight = 60.dp - val SelectedBorderWidth = 2.dp val CurrentTilesGridPadding = 8.dp @Composable diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt index 6920e498bdde..e5c213519415 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/InfiniteGridLayout.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.util.fastMap @@ -29,7 +30,9 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.grid.ui.compose.VerticalSpannedGrid import com.android.systemui.qs.panels.shared.model.SizedTileImpl import com.android.systemui.qs.panels.ui.compose.PaginatableGridLayout +import com.android.systemui.qs.panels.ui.compose.bounceableInfo import com.android.systemui.qs.panels.ui.compose.rememberEditListState +import com.android.systemui.qs.panels.ui.viewmodel.BounceableTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel import com.android.systemui.qs.panels.ui.viewmodel.IconTilesViewModel import com.android.systemui.qs.panels.ui.viewmodel.QSColumnsViewModel @@ -62,7 +65,11 @@ constructor( } val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle() val sizedTiles = tiles.map { SizedTileImpl(it, it.spec.width()) } + val bounceables = + remember(sizedTiles) { List(sizedTiles.size) { BounceableTileViewModel() } } val squishiness by squishinessViewModel.squishiness.collectAsStateWithLifecycle() + val scope = rememberCoroutineScope() + var cellIndex = 0 VerticalSpannedGrid( columns = columns, @@ -71,11 +78,15 @@ constructor( spans = sizedTiles.fastMap { it.width }, ) { spanIndex -> val it = sizedTiles[spanIndex] + val column = cellIndex % columns + cellIndex += it.width Tile( tile = it.tile, iconOnly = iconTilesViewModel.isIconTile(it.tile.spec), modifier = Modifier.element(it.tile.spec.toElementKey(spanIndex)), squishiness = { squishiness }, + coroutineScope = scope, + bounceableInfo = bounceables.bounceableInfo(it, spanIndex, column, columns), ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt index 4bd5b2d68c4c..52d526123430 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/infinitegrid/Tile.kt @@ -23,8 +23,8 @@ import android.service.quicksettings.Tile.STATE_ACTIVE import android.service.quicksettings.Tile.STATE_INACTIVE import androidx.compose.animation.core.animateDpAsState import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.gestures.Orientation import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box @@ -44,6 +44,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.ReadOnlyComposable import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -61,11 +62,13 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.android.compose.animation.Expandable +import com.android.compose.animation.bounceable import com.android.compose.modifiers.thenIf import com.android.systemui.animation.Expandable import com.android.systemui.common.shared.model.Icon import com.android.systemui.compose.modifiers.sysuiResTag import com.android.systemui.plugins.qs.QSTile +import com.android.systemui.qs.panels.ui.compose.BounceableInfo import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.longPressLabel import com.android.systemui.qs.panels.ui.viewmodel.TileUiState @@ -74,6 +77,8 @@ import com.android.systemui.qs.panels.ui.viewmodel.toUiState import com.android.systemui.qs.tileimpl.QSTileImpl import com.android.systemui.res.R import java.util.function.Supplier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch private const val TEST_TAG_SMALL = "qs_tile_small" private const val TEST_TAG_LARGE = "qs_tile_large" @@ -102,9 +107,12 @@ fun Tile( tile: TileViewModel, iconOnly: Boolean, squishiness: () -> Float, + coroutineScope: CoroutineScope, + bounceableInfo: BounceableInfo, modifier: Modifier = Modifier, ) { val state by tile.state.collectAsStateWithLifecycle(tile.currentState) + val currentBounceableInfo by rememberUpdatedState(bounceableInfo) val resources = resources() val uiState = remember(state, resources) { state.toUiState(resources) } val colors = TileDefaults.getColorForState(uiState) @@ -112,7 +120,7 @@ fun Tile( // TODO(b/361789146): Draw the shapes instead of clipping val tileShape = TileDefaults.animateTileShape(uiState.state) - TileContainer( + TileExpandable( color = if (iconOnly || !uiState.handlesSecondaryClick) { colors.iconBackground @@ -120,93 +128,101 @@ fun Tile( colors.background }, shape = tileShape, - iconOnly = iconOnly, - onClick = tile::onClick, - onLongClick = tile::onLongClick, - uiState = uiState, squishiness = squishiness, - modifier = modifier, + modifier = + modifier + .fillMaxWidth() + .bounceable( + bounceable = currentBounceableInfo.bounceable, + previousBounceable = currentBounceableInfo.previousTile, + nextBounceable = currentBounceableInfo.nextTile, + orientation = Orientation.Horizontal, + bounceEnd = currentBounceableInfo.bounceEnd, + ), ) { expandable -> - val icon = getTileIcon(icon = uiState.icon) - if (iconOnly) { - SmallTileContent( - icon = icon, - color = colors.icon, - modifier = Modifier.align(Alignment.Center), - ) - } else { - val iconShape = TileDefaults.animateIconShape(uiState.state) - LargeTileContent( - label = uiState.label, - secondaryLabel = uiState.secondaryLabel, - icon = icon, - colors = colors, - iconShape = iconShape, - toggleClickSupported = state.handlesSecondaryClick, - onClick = { - if (state.handlesSecondaryClick) { - tile.onSecondaryClick() - } - }, - onLongClick = { tile.onLongClick(expandable) }, - accessibilityUiState = uiState.accessibilityUiState, - squishiness = squishiness, - ) + TileContainer( + onClick = { + tile.onClick(expandable) + if (uiState.accessibilityUiState.toggleableState != null) { + coroutineScope.launch { currentBounceableInfo.bounceable.animateBounce() } + } + }, + onLongClick = { tile.onLongClick(expandable) }, + uiState = uiState, + iconOnly = iconOnly, + ) { + val icon = getTileIcon(icon = uiState.icon) + if (iconOnly) { + SmallTileContent( + icon = icon, + color = colors.icon, + modifier = Modifier.align(Alignment.Center), + ) + } else { + val iconShape = TileDefaults.animateIconShape(uiState.state) + LargeTileContent( + label = uiState.label, + secondaryLabel = uiState.secondaryLabel, + icon = icon, + colors = colors, + iconShape = iconShape, + toggleClickSupported = state.handlesSecondaryClick, + onClick = { + if (state.handlesSecondaryClick) { + tile.onSecondaryClick() + } + }, + onLongClick = { tile.onLongClick(expandable) }, + accessibilityUiState = uiState.accessibilityUiState, + squishiness = squishiness, + ) + } } } } @Composable -private fun TileContainer( +private fun TileExpandable( color: Color, shape: Shape, - iconOnly: Boolean, - uiState: TileUiState, squishiness: () -> Float, modifier: Modifier = Modifier, - onClick: (Expandable) -> Unit = {}, - onLongClick: (Expandable) -> Unit = {}, - content: @Composable BoxScope.(Expandable) -> Unit, + content: @Composable (Expandable) -> Unit, ) { Expandable( color = color, shape = shape, modifier = modifier.clip(shape).verticalSquish(squishiness), ) { - val longPressLabel = longPressLabel() - Box( - modifier = - Modifier.height(CommonTileDefaults.TileHeight) - .fillMaxWidth() - .combinedClickable( - onClick = { onClick(it) }, - onLongClick = { onLongClick(it) }, - onClickLabel = uiState.accessibilityUiState.clickLabel, - onLongClickLabel = longPressLabel, - ) - .semantics { - role = uiState.accessibilityUiState.accessibilityRole - if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) { - uiState.accessibilityUiState.toggleableState?.let { - toggleableState = it - } - } - stateDescription = uiState.accessibilityUiState.stateDescription - } - .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) - .thenIf(iconOnly) { - Modifier.semantics { - contentDescription = uiState.accessibilityUiState.contentDescription - } - } - .tilePadding() - ) { - content(it) - } + content(it) } } @Composable +fun TileContainer( + onClick: () -> Unit, + onLongClick: () -> Unit, + uiState: TileUiState, + iconOnly: Boolean, + content: @Composable BoxScope.() -> Unit, +) { + Box( + modifier = + Modifier.height(CommonTileDefaults.TileHeight) + .fillMaxWidth() + .tileCombinedClickable( + onClick = onClick, + onLongClick = onLongClick, + uiState = uiState, + iconOnly = iconOnly, + ) + .sysuiResTag(if (iconOnly) TEST_TAG_SMALL else TEST_TAG_LARGE) + .tilePadding(), + content = content, + ) +} + +@Composable private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon { val context = LocalContext.current return icon.get()?.let { @@ -222,14 +238,38 @@ fun tileHorizontalArrangement(): Arrangement.Horizontal { return spacedBy(space = CommonTileDefaults.TileArrangementPadding, alignment = Alignment.Start) } -fun Modifier.tileMarquee(): Modifier { - return basicMarquee(iterations = 1, initialDelayMillis = 200) -} - fun Modifier.tilePadding(): Modifier { return padding(CommonTileDefaults.TilePadding) } +@Composable +fun Modifier.tileCombinedClickable( + onClick: () -> Unit, + onLongClick: () -> Unit, + uiState: TileUiState, + iconOnly: Boolean, +): Modifier { + val longPressLabel = longPressLabel() + return combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + onClickLabel = uiState.accessibilityUiState.clickLabel, + onLongClickLabel = longPressLabel, + ) + .semantics { + role = uiState.accessibilityUiState.accessibilityRole + if (uiState.accessibilityUiState.accessibilityRole == Role.Switch) { + uiState.accessibilityUiState.toggleableState?.let { toggleableState = it } + } + stateDescription = uiState.accessibilityUiState.stateDescription + } + .thenIf(iconOnly) { + Modifier.semantics { + contentDescription = uiState.accessibilityUiState.contentDescription + } + } +} + data class TileColors( val background: Color, val iconBackground: Color, diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt index 441d96289d86..1d36aee4eb85 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/MutableSelectionState.kt @@ -89,8 +89,11 @@ class MutableSelectionState( * Listens for click events to select/unselect the given [TileSpec]. Use this on current tiles as * they can be selected. */ -@Composable -fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelectionState): Modifier { +fun Modifier.selectableTile( + tileSpec: TileSpec, + selectionState: MutableSelectionState, + onClick: () -> Unit = {}, +): Modifier { return pointerInput(Unit) { detectTapGestures( onTap = { @@ -99,6 +102,7 @@ fun Modifier.selectableTile(tileSpec: TileSpec, selectionState: MutableSelection } else { selectionState.select(tileSpec, manual = true) } + onClick() } ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt index e0f0b6aa8919..9f13a3788f53 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/selection/Selection.kt @@ -16,63 +16,117 @@ package com.android.systemui.qs.panels.ui.compose.selection +import androidx.compose.animation.core.animateIntAsState import androidx.compose.foundation.Canvas import androidx.compose.foundation.gestures.detectHorizontalDragGestures import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.size import androidx.compose.foundation.systemGestureExclusion import androidx.compose.material3.LocalMinimumInteractiveComponentSize import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawWithContent +import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.layout.layout +import androidx.compose.ui.unit.Constraints import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize +import androidx.compose.ui.zIndex +import com.android.compose.modifiers.thenIf +import com.android.systemui.qs.panels.ui.compose.infinitegrid.CommonTileDefaults.InactiveCornerRadius import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.ResizingDotSize +import com.android.systemui.qs.panels.ui.compose.selection.SelectionDefaults.SelectedBorderWidth /** - * Dot handling resizing drag events. Use this on the selected tile to resize it + * Places a dot to handle resizing drag events. Use this on tiles to resize. * - * @param enabled whether resizing drag events should be handled + * The dot is placed vertically centered on the right border. The [content] will have a border when + * selected. + * + * @param selected whether resizing drag events should be handled * @param selectionState the [MutableSelectionState] on the grid - * @param transition the animated value for the dot, used for its alpha and scale + * @param selectionAlpha the animated value for the dot and border alpha + * @param selectionColor the [Color] of the dot and border * @param tileWidths the [TileWidths] of the selected tile - * @param onResize the callback when the drag passes the resizing threshold */ @Composable -fun ResizingHandle( +fun ResizableTileContainer( + selected: Boolean, + selectionState: MutableSelectionState, + selectionAlpha: () -> Float, + selectionColor: Color, + tileWidths: () -> TileWidths?, + modifier: Modifier = Modifier, + content: @Composable BoxScope.() -> Unit = {}, +) { + Box( + modifier + .resizable(selected, selectionState, tileWidths) + .selectionBorder(selectionColor, selectionAlpha) + ) { + content() + ResizingHandle( + enabled = selected, + selectionState = selectionState, + transition = selectionAlpha, + tileWidths = tileWidths, + modifier = + // Higher zIndex to make sure the handle is drawn above the content + Modifier.zIndex(2f), + ) + } +} + +@Composable +private fun ResizingHandle( enabled: Boolean, selectionState: MutableSelectionState, transition: () -> Float, - tileWidths: () -> TileWidths? = { null }, + tileWidths: () -> TileWidths?, + modifier: Modifier = Modifier, ) { - if (enabled) { - // Manually creating the touch target around the resizing dot to ensure that the next tile - // does - // not receive the touch input accidentally. - val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current - Box( - Modifier.size(minTouchTargetSize) - .systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) } - .pointerInput(Unit) { - detectHorizontalDragGestures( - onHorizontalDrag = { _, offset -> selectionState.onResizingDrag(offset) }, - onDragStart = { - tileWidths()?.let { selectionState.onResizingDragStart(it) } - }, - onDragEnd = selectionState::onResizingDragEnd, - onDragCancel = selectionState::onResizingDragEnd, + // Manually creating the touch target around the resizing dot to ensure that the next tile + // does not receive the touch input accidentally. + val minTouchTargetSize = LocalMinimumInteractiveComponentSize.current + Box( + modifier + .layout { measurable, constraints -> + val size = minTouchTargetSize.roundToPx() + val placeable = measurable.measure(Constraints(size, size, size, size)) + layout(placeable.width, placeable.height) { + placeable.place( + x = constraints.maxWidth - placeable.width / 2, + y = constraints.maxHeight / 2 - placeable.height / 2, ) } - ) { - ResizingDot(transition = transition, modifier = Modifier.align(Alignment.Center)) - } - } else { - ResizingDot(transition = transition) + } + .thenIf(enabled) { + Modifier.systemGestureExclusion { Rect(Offset.Zero, it.size.toSize()) } + .pointerInput(Unit) { + detectHorizontalDragGestures( + onHorizontalDrag = { _, offset -> + selectionState.onResizingDrag(offset) + }, + onDragStart = { + tileWidths()?.let { selectionState.onResizingDragStart(it) } + }, + onDragEnd = selectionState::onResizingDragEnd, + onDragCancel = selectionState::onResizingDragEnd, + ) + } + } + ) { + ResizingDot(transition = transition, modifier = Modifier.align(Alignment.Center)) } } @@ -88,6 +142,45 @@ private fun ResizingDot( } } +private fun Modifier.selectionBorder( + selectionColor: Color, + selectionAlpha: () -> Float = { 0f }, +): Modifier { + return drawWithContent { + drawContent() + drawRoundRect( + SolidColor(selectionColor), + cornerRadius = CornerRadius(InactiveCornerRadius.toPx()), + style = Stroke(SelectedBorderWidth.toPx()), + alpha = selectionAlpha(), + ) + } +} + +@Composable +private fun Modifier.resizable( + selected: Boolean, + selectionState: MutableSelectionState, + tileWidths: () -> TileWidths?, +): Modifier { + if (!selected) return zIndex(1f) + + // Animated diff between the current width and the resized width of the tile. We can't use + // animateContentSize here as the tile is sometimes unbounded. + val remainingOffset by + animateIntAsState( + selectionState.resizingState?.let { tileWidths()?.base?.minus(it.width) ?: 0 } ?: 0, + label = "QSEditTileWidthOffset", + ) + return zIndex(2f).layout { measurable, constraints -> + // Grab the width from the resizing state if a resize is in progress + val width = selectionState.resizingState?.width ?: (constraints.maxWidth - remainingOffset) + val placeable = measurable.measure(constraints.copy(minWidth = width, maxWidth = width)) + layout(constraints.maxWidth, placeable.height) { placeable.place(0, 0) } + } +} + private object SelectionDefaults { val ResizingDotSize = 16.dp + val SelectedBorderWidth = 2.dp } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt index b1841c4c5ffa..c0441f8a38a1 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt @@ -31,8 +31,8 @@ sealed interface GridCell { } /** - * Represents a [EditTileViewModel] from a grid associated with a tile format and the row it's - * positioned at + * Represents a [EditTileViewModel] from a grid associated with a tile format and the row and column + * it's positioned at */ @Immutable data class TileGridCell( @@ -41,13 +41,15 @@ data class TileGridCell( override val width: Int, override val span: GridItemSpan = GridItemSpan(width), override val s: String = "${tile.tileSpec.spec}-$row-$width", + val column: Int, ) : GridCell, SizedTile<EditTileViewModel>, CategoryAndName by tile { val key: String = "${tile.tileSpec.spec}-$row" constructor( sizedTile: SizedTile<EditTileViewModel>, row: Int, - ) : this(tile = sizedTile.tile, row = row, width = sizedTile.width) + column: Int, + ) : this(tile = sizedTile.tile, row = row, column = column, width = sizedTile.width) } /** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */ @@ -73,7 +75,13 @@ fun List<SizedTile<EditTileViewModel>>.toGridCells( return splitInRowsSequence(this, columns) .flatMapIndexed { rowIndex, sizedTiles -> val correctedRowIndex = rowIndex + startingRow - val row: List<GridCell> = sizedTiles.map { TileGridCell(it, correctedRowIndex) } + var column = 0 + val row: List<GridCell> = + sizedTiles.map { + TileGridCell(it, correctedRowIndex, column).also { cell -> + column += cell.width + } + } // Fill the incomplete rows with spacers val numSpacers = columns - sizedTiles.sumOf { it.width } diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt new file mode 100644 index 000000000000..506b05256880 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/BounceableTileViewModel.kt @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2024 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.panels.ui.viewmodel + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.VectorConverter +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.android.compose.animation.Bounceable + +class BounceableTileViewModel : Bounceable { + private val animatableBounce = Animatable(0.dp, Dp.VectorConverter) + override val bounce: Dp + get() = animatableBounce.value + + suspend fun animateBounce() { + animatableBounce.animateTo(BounceSize) + animatableBounce.animateTo(0.dp) + } + + private companion object { + val BounceSize = 8.dp + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt index ee12736f6db4..be6ce5c5b4f4 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditTileViewModel.kt @@ -43,13 +43,13 @@ data class UnloadedEditTileViewModel( ) { fun load(context: Context): EditTileViewModel { return EditTileViewModel( - tileSpec, - icon, - label.toAnnotatedString(context) ?: AnnotatedString(tileSpec.spec), - appName?.toAnnotatedString(context), - isCurrent, - availableEditActions, - category, + tileSpec = tileSpec, + icon = icon, + label = label.toAnnotatedString(context) ?: AnnotatedString(tileSpec.spec), + appName = appName?.toAnnotatedString(context), + isCurrent = isCurrent, + availableEditActions = availableEditActions, + category = category, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt index b8f4ab40bb1d..dde36289f139 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileCoroutineScopeFactory.kt @@ -16,7 +16,7 @@ package com.android.systemui.qs.tiles.base.viewmodel -import com.android.app.tracing.coroutines.createCoroutineTracingContext +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import javax.inject.Inject import kotlinx.coroutines.CoroutineScope @@ -28,5 +28,7 @@ class QSTileCoroutineScopeFactory constructor(@Application private val applicationScope: CoroutineScope) { fun create(): CoroutineScope = - CoroutineScope(applicationScope.coroutineContext + SupervisorJob() + createCoroutineTracingContext("QSTileScope")) + CoroutineScope( + applicationScope.coroutineContext + SupervisorJob() + newTracingContext("QSTileScope") + ) } diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt index ae56c2aad4e9..f6749715e1fd 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogManager.kt @@ -15,12 +15,12 @@ */ package com.android.systemui.qs.tiles.dialog -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.util.Log import com.android.internal.jank.InteractionJankMonitor import com.android.systemui.animation.DialogCuj import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.animation.Expandable +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.statusbar.phone.SystemUIDialog @@ -63,7 +63,7 @@ constructor( } return } else { - coroutineScope = CoroutineScope(bgDispatcher + createCoroutineTracingContext("InternetDialogScope")) + coroutineScope = CoroutineScope(bgDispatcher + newTracingContext("InternetDialogScope")) dialog = dialogFactory .create(aboveStatusBar, canConfigMobileData, canConfigWifi, coroutineScope) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt new file mode 100644 index 000000000000..8dd611f9911a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/HearingDevicesTileMapper.kt @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2024 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.hearingdevices.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.hearingdevices.domain.model.HearingDevicesTileModel +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 [HearingDevicesTileModel] to [QSTileState]. */ +class HearingDevicesTileMapper +@Inject +constructor(@Main private val resources: Resources, private val theme: Resources.Theme) : + QSTileDataToStateMapper<HearingDevicesTileModel> { + + override fun map(config: QSTileConfig, data: HearingDevicesTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + label = resources.getString(R.string.quick_settings_hearing_devices_label) + iconRes = R.drawable.qs_hearing_devices_icon + val loadedIcon = + Icon.Loaded(resources.getDrawable(iconRes!!, theme), contentDescription = null) + icon = { loadedIcon } + sideViewIcon = QSTileState.SideViewIcon.Chevron + contentDescription = label + if (data.isAnyActiveHearingDevice) { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = + resources.getString(R.string.quick_settings_hearing_devices_connected) + } else if (data.isAnyPairedHearingDevice) { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = + resources.getString(R.string.quick_settings_hearing_devices_disconnected) + } else { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = "" + } + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt new file mode 100644 index 000000000000..ec0a4e9db896 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileDataInteractor.kt @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2024 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.hearingdevices.domain.interactor + +import android.os.UserHandle +import com.android.systemui.Flags +import com.android.systemui.accessibility.hearingaid.HearingDevicesChecker +import com.android.systemui.dagger.qualifiers.Background +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.hearingdevices.domain.model.HearingDevicesTileModel +import com.android.systemui.statusbar.policy.BluetoothController +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn + +/** Observes hearing devices state changes providing the [HearingDevicesTileModel]. */ +class HearingDevicesTileDataInteractor +@Inject +constructor( + @Background private val backgroundContext: CoroutineContext, + private val bluetoothController: BluetoothController, + private val hearingDevicesChecker: HearingDevicesChecker, +) : QSTileDataInteractor<HearingDevicesTileModel> { + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger>, + ): Flow<HearingDevicesTileModel> = + conflatedCallbackFlow { + val callback = + object : BluetoothController.Callback { + override fun onBluetoothStateChange(enabled: Boolean) { + trySend(getModel()) + } + + override fun onBluetoothDevicesChanged() { + trySend(getModel()) + } + } + bluetoothController.addCallback(callback) + awaitClose { bluetoothController.removeCallback(callback) } + } + .flowOn(backgroundContext) + .distinctUntilChanged() + + override fun availability(user: UserHandle): Flow<Boolean> = + flowOf(Flags.hearingAidsQsTileDialog()) + + private fun getModel() = + HearingDevicesTileModel( + hearingDevicesChecker.isAnyActiveHearingDevice, + hearingDevicesChecker.isAnyPairedHearingDevice, + ) +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt new file mode 100644 index 000000000000..5e7172ee3ba7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/interactor/HearingDevicesTileUserActionInteractor.kt @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2024 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.hearingdevices.domain.interactor + +import android.content.Intent +import android.provider.Settings +import com.android.systemui.accessibility.hearingaid.HearingDevicesDialogManager +import com.android.systemui.accessibility.hearingaid.HearingDevicesUiEventLogger.Companion.LAUNCH_SOURCE_QS_TILE +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.hearingdevices.domain.model.HearingDevicesTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.withContext + +/** Handles hearing devices tile clicks. */ +class HearingDevicesTileUserActionInteractor +@Inject +constructor( + @Main private val mainContext: CoroutineContext, + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val hearingDevicesDialogManager: HearingDevicesDialogManager, +) : QSTileUserActionInteractor<HearingDevicesTileModel> { + + override suspend fun handleInput(input: QSTileInput<HearingDevicesTileModel>) = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + withContext(mainContext) { + hearingDevicesDialogManager.showDialog( + action.expandable, + LAUNCH_SOURCE_QS_TILE, + ) + } + } + is QSTileUserAction.LongClick -> { + qsTileIntentUserActionHandler.handle( + action.expandable, + Intent(Settings.ACTION_HEARING_DEVICES_SETTINGS), + ) + } + is QSTileUserAction.ToggleClick -> {} + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/model/HearingDevicesTileModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/model/HearingDevicesTileModel.kt new file mode 100644 index 000000000000..4e37b771c49b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/hearingdevices/domain/model/HearingDevicesTileModel.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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.hearingdevices.domain.model + +/** Hearing devices tile model */ +data class HearingDevicesTileModel( + val isAnyActiveHearingDevice: Boolean, + val isAnyPairedHearingDevice: Boolean, +) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt index ac75932b8fee..14115444fe49 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/interactor/LocationTileUserActionInteractor.kt @@ -16,9 +16,9 @@ package com.android.systemui.qs.tiles.impl.location.domain.interactor -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.content.Intent import android.provider.Settings +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.plugins.ActivityStarter @@ -53,9 +53,11 @@ constructor( val wasEnabled: Boolean = input.data.isEnabled if (keyguardController.isMethodSecure() && keyguardController.isShowing()) { activityStarter.postQSRunnableDismissingKeyguard { - CoroutineScope(applicationScope.coroutineContext + createCoroutineTracingContext("LocationTileScope")).launch { - locationController.setLocationEnabled(!wasEnabled) - } + CoroutineScope( + applicationScope.coroutineContext + + newTracingContext("LocationTileScope") + ) + .launch { locationController.setLocationEnabled(!wasEnabled) } } } else { withContext(coroutineContext) { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt index 40591bf56e0a..cc14e71986f5 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/domain/interactor/ModesTileDataInteractor.kt @@ -18,7 +18,7 @@ package com.android.systemui.qs.tiles.impl.modes.domain.interactor import android.content.Context import android.os.UserHandle -import com.android.app.tracing.coroutines.flow.map +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.common.shared.model.Icon import com.android.systemui.common.shared.model.asIcon import com.android.systemui.dagger.qualifiers.Background @@ -37,6 +37,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map class ModesTileDataInteractor @Inject @@ -59,6 +60,7 @@ constructor( fun tileData() = zenModeInteractor.activeModes .map { activeModes -> buildTileData(activeModes) } + .flowName("tileData") .flowOn(bgDispatcher) .distinctUntilChanged() diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt index 801a0ce4b744..537b56bccae8 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/modes/ui/ModesTileMapper.kt @@ -39,7 +39,7 @@ constructor(@Main private val resources: Resources, val theme: Resources.Theme) val loadedIcon: Icon.Loaded = when (val dataIcon = data.icon) { is Icon.Resource -> { - if (iconRes != dataIcon.res) { + if (data.iconResId != dataIcon.res) { Log.wtf( "ModesTileMapper", "Icon.Resource.res & iconResId are not identical", 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 index 468e180a6e41..f03c7521931c 100644 --- 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 @@ -16,12 +16,12 @@ package com.android.systemui.qs.tiles.impl.saver.domain -import com.android.app.tracing.coroutines.createCoroutineTracingContext 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.coroutines.newTracingContext 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 @@ -45,9 +45,8 @@ class DataSaverDialogDelegate( setTitle(R.string.data_saver_enable_title) setMessage(R.string.data_saver_description) setPositiveButton(R.string.data_saver_enable_button) { _: DialogInterface?, _ -> - CoroutineScope(backgroundContext + createCoroutineTracingContext("DataSaverDialogScope")).launch { - dataSaverController.setDataSaverEnabled(true) - } + CoroutineScope(backgroundContext + newTracingContext("DataSaverDialogScope")) + .launch { dataSaverController.setDataSaverEnabled(true) } sharedPreferences .edit() diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt index 7b6b0f614cc2..6097ef53f8df 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlag.kt @@ -20,7 +20,6 @@ package com.android.systemui.scene.shared.flag import com.android.systemui.Flags.FLAG_SCENE_CONTAINER import com.android.systemui.Flags.sceneContainer -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils import com.android.systemui.keyguard.KeyguardBottomAreaRefactor @@ -42,8 +41,7 @@ object SceneContainerFlag { KeyguardWmStateRefactor.isEnabled && MigrateClocksToBlueprint.isEnabled && NotificationThrottleHun.isEnabled && - PredictiveBackSysUiFlag.isEnabled && - DeviceEntryUdfpsRefactor.isEnabled + PredictiveBackSysUiFlag.isEnabled // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer @@ -58,7 +56,6 @@ object SceneContainerFlag { MigrateClocksToBlueprint.token, NotificationThrottleHun.token, PredictiveBackSysUiFlag.token, - DeviceEntryUdfpsRefactor.token, // NOTE: Changes should also be made in isEnabled and @EnableSceneContainer ) diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt index ce942fefa393..47254775618c 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt @@ -19,7 +19,7 @@ package com.android.systemui.scene.ui.viewmodel import android.view.MotionEvent import android.view.View import androidx.compose.runtime.getValue -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.compose.animation.scene.ContentKey import com.android.compose.animation.scene.DefaultEdgeDetector import com.android.compose.animation.scene.ObservableTransitionState diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt index 54e0319da58f..17b1b6d91229 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionExecutor.kt @@ -26,7 +26,7 @@ import android.os.UserHandle import android.util.Log import android.util.Pair import android.view.Window -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.app.ChooserActivity import com.android.systemui.dagger.qualifiers.Application import dagger.assisted.Assisted diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt index 9e622801a01b..7b01c36489fb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ActionIntentExecutor.kt @@ -31,7 +31,7 @@ import android.view.RemoteAnimationAdapter import android.view.RemoteAnimationTarget import android.view.WindowManager import android.view.WindowManagerGlobal -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.internal.infra.ServiceConnector import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java index acfcd13738f0..2259b55dc268 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotController.java @@ -313,7 +313,9 @@ public class LegacyScreenshotController implements InteractiveScreenshotHandler setWindowFocusable(true); mViewProxy.requestFocus(); - enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); + if (screenshot.getType() != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { + enqueueScrollCaptureRequest(requestId, screenshot.getUserHandle()); + } attachWindow(); diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt index f5c605211520..08214c456897 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.kt @@ -35,6 +35,7 @@ import android.util.Log import android.view.Display import android.view.ScrollCaptureResponse import android.view.ViewRootImpl.ActivityConfigCallback +import android.view.WindowManager import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE import android.widget.Toast import android.window.WindowContext @@ -217,7 +218,9 @@ internal constructor( window.setFocusable(true) viewProxy.requestFocus() - enqueueScrollCaptureRequest(requestId, screenshot.userHandle) + if (screenshot.type != WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE) { + enqueueScrollCaptureRequest(requestId, screenshot.userHandle) + } window.attachWindow() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt index c8067df114fc..6df22f036273 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotProxyService.kt @@ -21,7 +21,7 @@ import android.os.RemoteException import android.util.Log import androidx.lifecycle.LifecycleService import androidx.lifecycle.lifecycleScope -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.plugins.ActivityStarter import com.android.systemui.shade.ShadeExpansionStateManager diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt index d3a7fc4a3e4a..7aeec47241cb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotSoundController.kt @@ -18,7 +18,7 @@ package com.android.systemui.screenshot import android.media.MediaPlayer import android.util.Log -import com.android.app.tracing.coroutines.async +import com.android.app.tracing.coroutines.asyncTraced as async import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background import javax.inject.Inject diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 757e37e9341f..d652d5bec27b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -123,7 +123,6 @@ import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.doze.DozeLog; import com.android.systemui.dump.DumpManager; import com.android.systemui.dump.DumpsysTableLogger; @@ -1859,16 +1858,11 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump /** Returns space between top of lock icon and bottom of NotificationStackScrollLayout. */ private float getLockIconPadding() { float lockIconPadding = 0f; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - View deviceEntryIconView = mKeyguardViewConfigurator.getKeyguardRootView() - .findViewById(R.id.device_entry_icon_view); - if (deviceEntryIconView != null) { - lockIconPadding = mNotificationStackScrollLayoutController.getBottom() - - deviceEntryIconView.getTop(); - } - } else if (mLockIconViewController.getTop() != 0f) { + View deviceEntryIconView = mKeyguardViewConfigurator.getKeyguardRootView() + .findViewById(R.id.device_entry_icon_view); + if (deviceEntryIconView != null) { lockIconPadding = mNotificationStackScrollLayoutController.getBottom() - - mLockIconViewController.getTop(); + - deviceEntryIconView.getTop(); } return lockIconPadding; } @@ -5059,8 +5053,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump return false; } - if (DeviceEntryUdfpsRefactor.isEnabled() - && mAlternateBouncerInteractor.isVisibleState()) { + if (mAlternateBouncerInteractor.isVisibleState()) { // never send touches to shade if the alternate bouncer is showing return false; } diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java index cae141d014a8..5699557c14e7 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java @@ -35,7 +35,6 @@ import androidx.core.view.ViewKt; import com.android.internal.annotations.VisibleForTesting; import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.KeyguardUnfoldTransition; -import com.android.keyguard.LockIconViewController; import com.android.systemui.Dumpable; import com.android.systemui.animation.ActivityTransitionAnimator; import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor; @@ -44,7 +43,6 @@ import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags; import com.android.systemui.bouncer.ui.binder.BouncerViewBinder; import com.android.systemui.classifier.FalsingCollector; import com.android.systemui.dagger.SysUISingleton; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.dock.DockManager; import com.android.systemui.dump.DumpManager; import com.android.systemui.flags.FeatureFlagsClassic; @@ -75,7 +73,6 @@ import com.android.systemui.statusbar.phone.CentralSurfaces; import com.android.systemui.statusbar.phone.DozeScrimController; import com.android.systemui.statusbar.phone.DozeServiceHost; import com.android.systemui.statusbar.phone.PhoneStatusBarViewController; -import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; import com.android.systemui.statusbar.window.StatusBarWindowStateController; import com.android.systemui.unfold.SysUIUnfoldComponent; import com.android.systemui.unfold.UnfoldTransitionProgressProvider; @@ -101,9 +98,7 @@ public class NotificationShadeWindowViewController implements Dumpable { private final NotificationShadeDepthController mDepthController; private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController; private final LockscreenShadeTransitionController mLockscreenShadeTransitionController; - private final LockIconViewController mLockIconViewController; private final ShadeLogger mShadeLogger; - private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; private final StatusBarWindowStateController mStatusBarWindowStateController; private final KeyguardUnlockAnimationController mKeyguardUnlockAnimationController; private final AmbientState mAmbientState; @@ -174,9 +169,7 @@ public class NotificationShadeWindowViewController implements Dumpable { PanelExpansionInteractor panelExpansionInteractor, ShadeExpansionStateManager shadeExpansionStateManager, NotificationStackScrollLayoutController notificationStackScrollLayoutController, - StatusBarKeyguardViewManager statusBarKeyguardViewManager, StatusBarWindowStateController statusBarWindowStateController, - LockIconViewController lockIconViewController, CentralSurfaces centralSurfaces, DozeServiceHost dozeServiceHost, DozeScrimController dozeScrimController, @@ -210,9 +203,7 @@ public class NotificationShadeWindowViewController implements Dumpable { mShadeExpansionStateManager = shadeExpansionStateManager; mDepthController = depthController; mNotificationStackScrollLayoutController = notificationStackScrollLayoutController; - mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; mStatusBarWindowStateController = statusBarWindowStateController; - mLockIconViewController = lockIconViewController; mShadeLogger = shadeLogger; mService = centralSurfaces; mDozeServiceHost = dozeServiceHost; @@ -259,7 +250,6 @@ public class NotificationShadeWindowViewController implements Dumpable { mDisableSubpixelTextTransitionListener)); } - lockIconViewController.setLockIconView(mView.findViewById(R.id.lock_icon_view)); dumpManager.registerDumpable(this); } @@ -392,9 +382,6 @@ public class NotificationShadeWindowViewController implements Dumpable { } if (mKeyguardUnlockAnimationController.isPlayingCannedUnlockAnimation()) { - // If the user was sliding their finger across the lock screen, - // we may have been intercepting the touch and forwarding it to the - // UDFPS affordance via mStatusBarKeyguardViewManager.onTouch (see below). // If this touch ended up unlocking the device, we want to cancel the touch // immediately, so we don't cause swipe or expand animations afterwards. cancelCurrentTouch(); @@ -419,9 +406,6 @@ public class NotificationShadeWindowViewController implements Dumpable { && mDreamingWakeupGestureHandler.onTouchEvent(ev)) { return logDownDispatch(ev, "dream wakeup gesture handled", true); } - if (mStatusBarKeyguardViewManager.dispatchTouchEvent(ev)) { - return logDownDispatch(ev, "dispatched to Keyguard", true); - } if (mBrightnessMirror != null && mBrightnessMirror.getVisibility() == View.VISIBLE) { // Disallow new pointers while the brightness mirror is visible. This is so that @@ -512,7 +496,6 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarStateController.isDozing() && !mDozeServiceHost.isPulsing() && !mDockManager.isDocked() - && !mLockIconViewController.willHandleTouchWhileDozing(ev) ) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { mShadeLogger.d("NSWVC: capture all touch events in always-on"); @@ -520,22 +503,8 @@ public class NotificationShadeWindowViewController implements Dumpable { return true; } - if (mStatusBarKeyguardViewManager.shouldInterceptTouchEvent(ev)) { - // Don't allow touches to proceed to underlying views if alternate - // bouncer is showing - if (ev.getAction() == MotionEvent.ACTION_DOWN) { - mShadeLogger.d("NSWVC: alt bouncer showing"); - } - return true; - } - - boolean bouncerShowing; - if (DeviceEntryUdfpsRefactor.isEnabled()) { - bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing() + boolean bouncerShowing = mPrimaryBouncerInteractor.isBouncerShowing() || mAlternateBouncerInteractor.isVisibleState(); - } else { - bouncerShowing = mService.isBouncerShowing(); - } if (mPanelExpansionInteractor.isFullyExpanded() && !bouncerShowing && !mStatusBarStateController.isDozing()) { @@ -603,9 +572,6 @@ public class NotificationShadeWindowViewController implements Dumpable { if (mStatusBarStateController.isDozing()) { handled = !mDozeServiceHost.isPulsing(); } - if (mStatusBarKeyguardViewManager.onTouch(ev)) { - return true; - } if (MigrateClocksToBlueprint.isEnabled()) { if (mLastInterceptWasDragDownHelper && (mDragDownHelper.isDraggingDown())) { // we still want to finish our drag down gesture when locking the screen diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt index 949d2aa36bf3..460bfbbcb3ab 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractorImpl.kt @@ -16,6 +16,7 @@ package com.android.systemui.shade.domain.interactor +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.keyguard.data.repository.KeyguardRepository @@ -62,17 +63,20 @@ constructor( override val isShadeEnabled: StateFlow<Boolean> = disableFlagsRepository.disableFlags .map { it.isShadeEnabled() } + .flowName("isShadeEnabled") .stateIn(scope, SharingStarted.Eagerly, initialValue = false) override val isQsEnabled: StateFlow<Boolean> = disableFlagsRepository.disableFlags .map { it.isQuickSettingsEnabled() } + .flowName("isQsEnabled") .stateIn(scope, SharingStarted.Eagerly, initialValue = false) override val isAnyFullyExpanded: StateFlow<Boolean> = anyExpansion .map { it >= 1f } .distinctUntilChanged() + .flowName("isAnyFullyExpanded") .stateIn(scope, SharingStarted.Eagerly, initialValue = false) override val isShadeFullyExpanded: Flow<Boolean> = @@ -81,6 +85,7 @@ constructor( override val isShadeAnyExpanded: StateFlow<Boolean> = baseShadeInteractor.shadeExpansion .map { it > 0 } + .flowName("isShadeAnyExpanded") .stateIn(scope, SharingStarted.Eagerly, false) override val isShadeFullyCollapsed: Flow<Boolean> = @@ -88,6 +93,7 @@ constructor( override val isUserInteracting: StateFlow<Boolean> = combine(isUserInteractingWithShade, isUserInteractingWithQs) { shade, qs -> shade || qs } + .flowName("isUserInteracting") .stateIn(scope, SharingStarted.Eagerly, false) override val isShadeTouchable: Flow<Boolean> = diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt index 3113dc462e6a..4bdd36773655 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeUserActionsViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.shade.ui.viewmodel -import com.android.app.tracing.coroutines.flow.map import com.android.compose.animation.scene.Swipe import com.android.compose.animation.scene.SwipeDirection import com.android.compose.animation.scene.UserAction @@ -33,6 +32,7 @@ import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map /** * Models the UI state for the user actions that the user can perform to navigate to other scenes. diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt index 1481b734ff61..1ec5357d0d30 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt @@ -15,7 +15,6 @@ import androidx.annotation.VisibleForTesting import com.android.systemui.Dumpable import com.android.systemui.ExpandHelper import com.android.systemui.Gefingerpoken -import com.android.systemui.biometrics.UdfpsKeyguardViewControllerLegacy import com.android.systemui.classifier.Classifier import com.android.systemui.classifier.FalsingCollector import com.android.systemui.dagger.SysUISingleton @@ -90,6 +89,7 @@ constructor( @get:VisibleForTesting var fractionToShade: Float = 0f private set + private var useSplitShade: Boolean = false private lateinit var nsslController: NotificationStackScrollLayoutController lateinit var centralSurfaces: CentralSurfaces @@ -161,9 +161,6 @@ constructor( val distanceUntilShowingPulsingNotifications get() = fullTransitionDistance - /** The udfpsKeyguardViewController if it exists. */ - var mUdfpsKeyguardViewControllerLegacy: UdfpsKeyguardViewControllerLegacy? = null - /** The touch helper responsible for the drag down animation. */ val touchHelper = DragDownHelper( @@ -171,7 +168,7 @@ constructor( this, naturalScrollingSettingObserver, shadeRepository, - context + context, ) private val splitShadeOverScroller: SplitShadeLockScreenOverScroller by lazy { @@ -448,7 +445,6 @@ constructor( val udfpsProgress = MathUtils.saturate(dragDownAmount / udfpsTransitionDistance) shadeRepository.setUdfpsTransitionToFullShadeProgress(udfpsProgress) - mUdfpsKeyguardViewControllerLegacy?.setTransitionToFullShadeProgress(udfpsProgress) val statusBarProgress = MathUtils.saturate(dragDownAmount / statusBarTransitionDistance) centralSurfaces.setTransitionToFullShadeProgress(statusBarProgress) @@ -457,7 +453,7 @@ constructor( private fun setDragDownAmountAnimated( target: Float, delay: Long = 0, - endlistener: (() -> Unit)? = null + endlistener: (() -> Unit)? = null, ) { logger.logDragDownAnimation(target) val dragDownAnimator = ValueAnimator.ofFloat(dragDownAmount, target) @@ -553,7 +549,7 @@ constructor( private fun goToLockedShadeInternal( expandView: View?, animationHandler: ((Long) -> Unit)? = null, - cancelAction: Runnable? = null + cancelAction: Runnable? = null, ) { if (!shadeInteractor.isShadeEnabled.value) { cancelAction?.run() @@ -564,10 +560,7 @@ constructor( var entry: NotificationEntry? = null if (expandView is ExpandableNotificationRow) { entry = expandView.entry - entry.setUserExpanded( - /* userExpanded= */ true, - /* allowChildExpansion= */ true, - ) + entry.setUserExpanded(/* userExpanded= */ true, /* allowChildExpansion= */ true) // Indicate that the group expansion is changing at this time -- this way the group // and children backgrounds / divider animations will look correct. entry.setGroupExpansionChanging(true) @@ -594,9 +587,7 @@ constructor( statusBarStateController.setLeaveOpenOnKeyguardHide(false) draggedDownEntry?.apply { setUserLocked(false) - notifyHeightChanged( - /* needsAnimation= */ false, - ) + notifyHeightChanged(/* needsAnimation= */ false) draggedDownEntry = null } cancelAction?.run() @@ -614,9 +605,7 @@ constructor( // This call needs to be after updating the shade state since otherwise // the scrimstate resets too early if (animationHandler != null) { - animationHandler.invoke( - /* delay= */ 0, - ) + animationHandler.invoke(/* delay= */ 0) } else { performDefaultGoToFullShadeAnimation(0) } @@ -757,7 +746,7 @@ class DragDownHelper( private val dragDownCallback: LockscreenShadeTransitionController, private val naturalScrollingSettingObserver: NaturalScrollingSettingObserver, private val shadeRepository: ShadeRepository, - context: Context + context: Context, ) : Gefingerpoken { private var dragDownAmountOnStart = 0.0f @@ -932,7 +921,7 @@ class DragDownHelper( @VisibleForTesting fun cancelChildExpansion( child: ExpandableView, - animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS + animationDuration: Long = SPRING_BACK_ANIMATION_LENGTH_MS, ) { if (child.actualHeight == child.collapsedHeight) { expandCallback.setUserLockedChild(child, false) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS index 32d37aef7707..c019f308d3e5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS +++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS @@ -22,3 +22,7 @@ per-file *RemoteInput* = set noparent per-file *RemoteInput* = file:notification/OWNERS per-file *EmptyShadeView* = set noparent per-file *EmptyShadeView* = file:notification/OWNERS +per-file *Lockscreen* = set noparent +per-file *Lockscreen* = file:../keyguard/OWNERS +per-file *Scrim* = set noparent +per-file *Scrim* = file:../keyguard/OWNERS diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt index be733d4e1e8e..8ce0dbf8e171 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/StatusBarChipsModule.kt @@ -20,7 +20,7 @@ import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel import dagger.Binds import dagger.Module import dagger.Provides @@ -31,8 +31,8 @@ import dagger.multibindings.IntoMap abstract class StatusBarChipsModule { @Binds @IntoMap - @ClassKey(DemoRonChipViewModel::class) - abstract fun binds(impl: DemoRonChipViewModel): CoreStartable + @ClassKey(DemoNotifChipViewModel::class) + abstract fun binds(impl: DemoNotifChipViewModel): CoreStartable companion object { @Provides diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt index cce9a1624d51..5fa19ddef1be 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModel.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel +package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel import android.content.pm.PackageManager import android.content.pm.PackageManager.NameNotFoundException @@ -22,7 +22,7 @@ import android.graphics.drawable.Drawable import com.android.systemui.CoreStartable import com.android.systemui.common.shared.model.Icon import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.ColorsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.viewmodel.OngoingActivityChipViewModel @@ -37,25 +37,25 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow /** - * A view model that will emit demo RON chips (rich ongoing notification chips) from [chip] based on - * adb commands sent by the user. + * A view model that will emit demo promoted ongoing notification chips from [chip] based on adb + * commands sent by the user. * * Example adb commands: * * To show a chip with the SysUI icon and custom text and color: * ``` - * adb shell cmd statusbar demo-ron -p com.android.systemui -t 10min -c "\\#434343" + * adb shell cmd statusbar demo-notif -p com.android.systemui -t 10min -c "\\#434343" * ``` * * To hide the chip: * ``` - * adb shell cmd statusbar demo-ron --hide + * adb shell cmd statusbar demo-notif --hide * ``` * - * See [DemoRonCommand] for more information on the adb command spec. + * See [DemoNotifCommand] for more information on the adb command spec. */ @SysUISingleton -class DemoRonChipViewModel +class DemoNotifChipViewModel @Inject constructor( private val commandRegistry: CommandRegistry, @@ -63,19 +63,19 @@ constructor( private val systemClock: SystemClock, ) : OngoingActivityChipViewModel, CoreStartable { override fun start() { - commandRegistry.registerCommand("demo-ron") { DemoRonCommand() } + commandRegistry.registerCommand(DEMO_COMMAND_NAME) { DemoNotifCommand() } } private val _chip = MutableStateFlow<OngoingActivityChipModel>(OngoingActivityChipModel.Hidden()) override val chip: StateFlow<OngoingActivityChipModel> = _chip.asStateFlow() - private inner class DemoRonCommand : ParseableCommand("demo-ron") { + private inner class DemoNotifCommand : ParseableCommand(DEMO_COMMAND_NAME) { private val packageName: String? by param( longName = "packageName", shortName = "p", - description = "The package name for the demo RON app", + description = "The package name for app \"posting\" the demo notification", valueParser = Type.String, ) @@ -99,15 +99,12 @@ constructor( ) private val hide by - flag( - longName = "hide", - description = "Hides any existing demo RON chip", - ) + flag(longName = "hide", description = "Hides any existing demo notification chip") override fun execute(pw: PrintWriter) { - if (!StatusBarRonChips.isEnabled) { + if (!StatusBarNotifChips.isEnabled) { pw.println( - "Error: com.android.systemui.status_bar_ron_chips must be enabled " + + "Error: com.android.systemui.status_bar_notification_chips must be enabled " + "before using this demo feature" ) return @@ -167,8 +164,12 @@ constructor( return null } return OngoingActivityChipModel.ChipIcon.FullColorAppIcon( - Icon.Loaded(drawable = iconDrawable, contentDescription = null), + Icon.Loaded(drawable = iconDrawable, contentDescription = null) ) } } + + companion object { + private const val DEMO_COMMAND_NAME = "demo-notif" + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt index 4ef190991f19..47ffbafe3735 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ron/shared/StatusBarRonChips.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/shared/StatusBarNotifChips.kt @@ -14,17 +14,17 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.shared +package com.android.systemui.statusbar.chips.notification.shared import com.android.systemui.Flags import com.android.systemui.flags.FlagToken import com.android.systemui.flags.RefactorFlagUtils -/** Helper for reading or using the status bar RON chips flag state. */ +/** Helper for reading or using the status bar promoted notification chips flag state. */ @Suppress("NOTHING_TO_INLINE") -object StatusBarRonChips { +object StatusBarNotifChips { /** The aconfig flag name */ - const val FLAG_NAME = Flags.FLAG_STATUS_BAR_RON_CHIPS + const val FLAG_NAME = Flags.FLAG_STATUS_BAR_NOTIFICATION_CHIPS /** A token used for dependency declaration */ val token: FlagToken @@ -33,7 +33,7 @@ object StatusBarRonChips { /** Is the refactor enabled */ @JvmStatic inline val isEnabled - get() = Flags.statusBarRonChips() + get() = Flags.statusBarNotificationChips() /** * Called to ensure code is only run when the flag is enabled. This protects users from the diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt new file mode 100644 index 000000000000..6ae92637bde7 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModel.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 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.chips.notification.ui.viewmodel + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.statusbar.chips.ui.model.ColorsModel +import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor +import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +/** A view model for status bar chips for promoted ongoing notifications. */ +@SysUISingleton +class NotifChipsViewModel +@Inject +constructor(activeNotificationsInteractor: ActiveNotificationsInteractor) { + /** + * A flow modeling the notification chips that should be shown. Emits an empty list if there are + * no notifications that should show a status bar chip. + */ + val chips: Flow<List<OngoingActivityChipModel.Shown>> = + activeNotificationsInteractor.promotedOngoingNotifications.map { notifications -> + notifications.mapNotNull { it.toChipModel() } + } + + /** + * Converts the notification to the [OngoingActivityChipModel] object. Returns null if the + * notification has invalid data such that it can't be displayed as a chip. + */ + private fun ActiveNotificationModel.toChipModel(): OngoingActivityChipModel.Shown? { + // TODO(b/364653005): Log error if there's no icon view. + val rawIcon = this.statusBarChipIconView ?: return null + val icon = OngoingActivityChipModel.ChipIcon.StatusBarView(rawIcon) + // TODO(b/364653005): Use the notification color if applicable. + val colors = ColorsModel.Themed + // TODO(b/364653005): When the chip is clicked, show the HUN. + val onClickListener = null + return OngoingActivityChipModel.Shown.ShortTimeDelta( + icon, + colors, + time = this.whenTime, + onClickListener, + ) + // TODO(b/364653005): If Notification.showWhen = false, don't show the time delta. + // TODO(b/364653005): If Notification.whenTime is in the past, show "ago" in the text. + // TODO(b/364653005): If Notification.shortCriticalText is set, use that instead of `when`. + // TODO(b/364653005): If the app that posted the notification is in the foreground, don't + // show that app's chip. + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt index 3b1e565e122b..f4462a434477 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/binder/OngoingActivityChipBinder.kt @@ -21,13 +21,14 @@ import android.content.res.ColorStateList import android.graphics.drawable.GradientDrawable import android.view.View import android.view.ViewGroup +import android.widget.DateTimeView import android.widget.FrameLayout import android.widget.ImageView import android.widget.TextView import com.android.systemui.common.ui.binder.IconViewBinder import com.android.systemui.res.R import com.android.systemui.statusbar.StatusBarIconView -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.chips.ui.view.ChipBackgroundContainer import com.android.systemui.statusbar.chips.ui.view.ChipChronometer @@ -42,6 +43,8 @@ object OngoingActivityChipBinder { val chipTimeView: ChipChronometer = chipView.requireViewById(R.id.ongoing_activity_chip_time) val chipTextView: TextView = chipView.requireViewById(R.id.ongoing_activity_chip_text) + val chipShortTimeDeltaView: DateTimeView = + chipView.requireViewById(R.id.ongoing_activity_chip_short_time_delta) val chipBackgroundView: ChipBackgroundContainer = chipView.requireViewById(R.id.ongoing_activity_chip_background) @@ -49,13 +52,14 @@ object OngoingActivityChipBinder { is OngoingActivityChipModel.Shown -> { // Data setChipIcon(chipModel, chipBackgroundView, chipDefaultIconView) - setChipMainContent(chipModel, chipTextView, chipTimeView) + setChipMainContent(chipModel, chipTextView, chipTimeView, chipShortTimeDeltaView) chipView.setOnClickListener(chipModel.onClickListener) updateChipPadding( chipModel, chipBackgroundView, chipTextView, chipTimeView, + chipShortTimeDeltaView, ) // Accessibility @@ -65,6 +69,7 @@ object OngoingActivityChipBinder { val textColor = chipModel.colors.text(chipContext) chipTimeView.setTextColor(textColor) chipTextView.setTextColor(textColor) + chipShortTimeDeltaView.setTextColor(textColor) (chipBackgroundView.background as GradientDrawable).color = chipModel.colors.background(chipContext) } @@ -97,7 +102,7 @@ object OngoingActivityChipBinder { defaultIconView.tintView(iconTint) } is OngoingActivityChipModel.ChipIcon.FullColorAppIcon -> { - StatusBarRonChips.assertInNewMode() + StatusBarNotifChips.assertInNewMode() IconViewBinder.bind(icon.impl, defaultIconView) defaultIconView.visibility = View.VISIBLE defaultIconView.untintView() @@ -161,6 +166,7 @@ object OngoingActivityChipBinder { chipModel: OngoingActivityChipModel.Shown, chipTextView: TextView, chipTimeView: ChipChronometer, + chipShortTimeDeltaView: DateTimeView, ) { when (chipModel) { is OngoingActivityChipModel.Shown.Countdown -> { @@ -168,21 +174,35 @@ object OngoingActivityChipBinder { chipTextView.visibility = View.VISIBLE chipTimeView.hide() + chipShortTimeDeltaView.visibility = View.GONE } is OngoingActivityChipModel.Shown.Text -> { chipTextView.text = chipModel.text chipTextView.visibility = View.VISIBLE chipTimeView.hide() + chipShortTimeDeltaView.visibility = View.GONE } is OngoingActivityChipModel.Shown.Timer -> { ChipChronometerBinder.bind(chipModel.startTimeMs, chipTimeView) chipTimeView.visibility = View.VISIBLE chipTextView.visibility = View.GONE + chipShortTimeDeltaView.visibility = View.GONE + } + is OngoingActivityChipModel.Shown.ShortTimeDelta -> { + chipShortTimeDeltaView.setTime(chipModel.time) + // TODO(b/364653005): DateTimeView's relative time doesn't quite match the format we + // want in the status bar chips. + chipShortTimeDeltaView.isShowRelativeTime = true + chipShortTimeDeltaView.visibility = View.VISIBLE + + chipTextView.visibility = View.GONE + chipTimeView.hide() } is OngoingActivityChipModel.Shown.IconOnly -> { chipTextView.visibility = View.GONE + chipShortTimeDeltaView.visibility = View.GONE chipTimeView.hide() } } @@ -200,6 +220,7 @@ object OngoingActivityChipBinder { backgroundView: View, chipTextView: TextView, chipTimeView: ChipChronometer, + chipShortTimeDeltaView: DateTimeView, ) { if (chipModel.icon != null) { if (chipModel.icon is OngoingActivityChipModel.ChipIcon.StatusBarView) { @@ -209,15 +230,18 @@ object OngoingActivityChipBinder { backgroundView.setBackgroundPaddingForEmbeddedPaddingIcon() chipTextView.setTextPaddingForEmbeddedPaddingIcon() chipTimeView.setTextPaddingForEmbeddedPaddingIcon() + chipShortTimeDeltaView.setTextPaddingForEmbeddedPaddingIcon() } else { backgroundView.setBackgroundPaddingForNormalIcon() chipTextView.setTextPaddingForNormalIcon() chipTimeView.setTextPaddingForNormalIcon() + chipShortTimeDeltaView.setTextPaddingForNormalIcon() } } else { backgroundView.setBackgroundPaddingForNoIcon() chipTextView.setTextPaddingForNoIcon() chipTimeView.setTextPaddingForNoIcon() + chipShortTimeDeltaView.setTextPaddingForNoIcon() } } @@ -258,23 +282,13 @@ object OngoingActivityChipBinder { context.resources.getDimensionPixelSize( R.dimen.ongoing_activity_chip_side_padding_for_embedded_padding_icon ) - setPaddingRelative( - sidePadding, - paddingTop, - sidePadding, - paddingBottom, - ) + setPaddingRelative(sidePadding, paddingTop, sidePadding, paddingBottom) } private fun View.setBackgroundPaddingForNormalIcon() { val sidePadding = context.resources.getDimensionPixelSize(R.dimen.ongoing_activity_chip_side_padding) - setPaddingRelative( - sidePadding, - paddingTop, - sidePadding, - paddingBottom, - ) + setPaddingRelative(sidePadding, paddingTop, sidePadding, paddingBottom) } private fun View.setBackgroundPaddingForNoIcon() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt index 62622a893ec5..cf07af1fc5dd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/model/OngoingActivityChipModel.kt @@ -20,6 +20,7 @@ import android.view.View import com.android.systemui.Flags import com.android.systemui.common.shared.model.Icon import com.android.systemui.statusbar.StatusBarIconView +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips /** Model representing the display of an ongoing activity as a chip in the status bar. */ sealed class OngoingActivityChipModel { @@ -79,6 +80,24 @@ sealed class OngoingActivityChipModel { } /** + * The chip shows the time delta between now and [time] in a short format, e.g. "15min" or + * "1hr ago". + */ + data class ShortTimeDelta( + override val icon: ChipIcon, + override val colors: ColorsModel, + /** The time of the event that this chip represents. */ + val time: Long, + override val onClickListener: View.OnClickListener?, + ) : Shown(icon, colors, onClickListener) { + init { + StatusBarNotifChips.assertInNewMode() + } + + override val logName = "Shown.ShortTimeDelta" + } + + /** * This chip shows a countdown using [secondsUntilStarted]. Used to inform users that an * event is about to start. Typically, a [Countdown] chip will turn into a [Timer] chip. */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt index 24c1b879f429..ed325970ebb2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModel.kt @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.chips.ui.viewmodel -import com.android.systemui.Flags import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.LogBuffer @@ -24,19 +23,20 @@ import com.android.systemui.log.core.LogLevel import com.android.systemui.statusbar.chips.StatusBarChipsLog import com.android.systemui.statusbar.chips.call.ui.viewmodel.CallChipViewModel import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.CastToOtherDeviceChipViewModel -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.DemoRonChipViewModel -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.DemoNotifChipViewModel +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips +import com.android.systemui.statusbar.chips.notification.ui.viewmodel.NotifChipsViewModel import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.ScreenRecordChipViewModel import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.ShareToAppChipViewModel import com.android.systemui.statusbar.chips.ui.model.MultipleOngoingActivityChipsModel import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel +import com.android.systemui.util.kotlin.combine import com.android.systemui.util.kotlin.pairwise import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @@ -55,7 +55,8 @@ constructor( shareToAppChipViewModel: ShareToAppChipViewModel, castToOtherDeviceChipViewModel: CastToOtherDeviceChipViewModel, callChipViewModel: CallChipViewModel, - demoRonChipViewModel: DemoRonChipViewModel, + notifChipsViewModel: NotifChipsViewModel, + demoNotifChipViewModel: DemoNotifChipViewModel, @StatusBarChipsLog private val logger: LogBuffer, ) { private enum class ChipType { @@ -63,8 +64,9 @@ constructor( ShareToApp, CastToOtherDevice, Call, - /** A demo of a RON chip (rich ongoing notification chip), used just for testing. */ - DemoRon, + Notification, + /** A demo of a notification chip, used just for testing. */ + DemoNotification, } /** Model that helps us internally track the various chip states from each of the types. */ @@ -85,7 +87,8 @@ constructor( val shareToApp: OngoingActivityChipModel.Hidden, val castToOtherDevice: OngoingActivityChipModel.Hidden, val call: OngoingActivityChipModel.Hidden, - val demoRon: OngoingActivityChipModel.Hidden, + val notifs: OngoingActivityChipModel.Hidden, + val demoNotif: OngoingActivityChipModel.Hidden, ) : InternalChipModel } @@ -94,7 +97,8 @@ constructor( val shareToApp: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), val castToOtherDevice: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), val call: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), - val demoRon: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), + val notifs: List<OngoingActivityChipModel.Shown> = emptyList(), + val demoNotif: OngoingActivityChipModel = OngoingActivityChipModel.Hidden(), ) /** Bundles all the incoming chips into one object to easily pass to various flows. */ @@ -104,8 +108,9 @@ constructor( shareToAppChipViewModel.chip, castToOtherDeviceChipViewModel.chip, callChipViewModel.chip, - demoRonChipViewModel.chip, - ) { screenRecord, shareToApp, castToOtherDevice, call, demoRon -> + notifChipsViewModel.chips, + demoNotifChipViewModel.chip, + ) { screenRecord, shareToApp, castToOtherDevice, call, notifs, demoNotif -> logger.log( TAG, LogLevel.INFO, @@ -121,16 +126,19 @@ constructor( LogLevel.INFO, { str1 = call.logName - str2 = demoRon.logName + // TODO(b/364653005): Log other information for notification chips. + str2 = notifs.map { it.logName }.toString() + str3 = demoNotif.logName }, - { "... > Call=$str1 > DemoRon=$str2" } + { "... > Call=$str1 > Notifs=$str2 > DemoNotif=$str3" }, ) ChipBundle( screenRecord = screenRecord, shareToApp = shareToApp, castToOtherDevice = castToOtherDevice, call = call, - demoRon = demoRon, + notifs = notifs, + demoNotif = demoNotif, ) } // Some of the chips could have timers in them and we don't want the start time @@ -189,9 +197,9 @@ constructor( * actually displaying the chip. */ val chips: StateFlow<MultipleOngoingActivityChipsModel> = - if (!Flags.statusBarRonChips()) { - // Multiple chips are only allowed with RONs. If the flag isn't on, use just the - // primary chip. + if (!StatusBarNotifChips.isEnabled) { + // Multiple chips are only allowed with notification chips. If the flag isn't on, use + // just the primary chip. primaryChip .map { MultipleOngoingActivityChipsModel( @@ -199,11 +207,7 @@ constructor( secondary = OngoingActivityChipModel.Hidden(), ) } - .stateIn( - scope, - SharingStarted.Lazily, - MultipleOngoingActivityChipsModel(), - ) + .stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModel()) } else { internalChips .pairwise(initialValue = DEFAULT_MULTIPLE_INTERNAL_HIDDEN_MODEL) @@ -212,11 +216,7 @@ constructor( val correctSecondary = createOutputModel(old.secondary, new.secondary) MultipleOngoingActivityChipsModel(correctPrimary, correctSecondary) } - .stateIn( - scope, - SharingStarted.Lazily, - MultipleOngoingActivityChipsModel(), - ) + .stateIn(scope, SharingStarted.Lazily, MultipleOngoingActivityChipsModel()) } /** A data class representing the return result of [pickMostImportantChip]. */ @@ -251,7 +251,7 @@ constructor( // suppress the share-to-app chip to make sure they don't both show. // See b/296461748. shareToApp = OngoingActivityChipModel.Hidden(), - ) + ), ) bundle.shareToApp is OngoingActivityChipModel.Shown -> MostImportantChipResult( @@ -274,11 +274,19 @@ constructor( mostImportantChip = InternalChipModel.Shown(ChipType.Call, bundle.call), remainingChips = bundle.copy(call = OngoingActivityChipModel.Hidden()), ) - bundle.demoRon is OngoingActivityChipModel.Shown -> { - StatusBarRonChips.assertInNewMode() + bundle.notifs.isNotEmpty() -> + MostImportantChipResult( + mostImportantChip = + InternalChipModel.Shown(ChipType.Notification, bundle.notifs.first()), + remainingChips = + bundle.copy(notifs = bundle.notifs.subList(1, bundle.notifs.size)), + ) + bundle.demoNotif is OngoingActivityChipModel.Shown -> { + StatusBarNotifChips.assertInNewMode() MostImportantChipResult( - mostImportantChip = InternalChipModel.Shown(ChipType.DemoRon, bundle.demoRon), - remainingChips = bundle.copy(demoRon = OngoingActivityChipModel.Hidden()), + mostImportantChip = + InternalChipModel.Shown(ChipType.DemoNotification, bundle.demoNotif), + remainingChips = bundle.copy(demoNotif = OngoingActivityChipModel.Hidden()), ) } else -> { @@ -287,7 +295,8 @@ constructor( check(bundle.shareToApp is OngoingActivityChipModel.Hidden) check(bundle.castToOtherDevice is OngoingActivityChipModel.Hidden) check(bundle.call is OngoingActivityChipModel.Hidden) - check(bundle.demoRon is OngoingActivityChipModel.Hidden) + check(bundle.notifs.isEmpty()) + check(bundle.demoNotif is OngoingActivityChipModel.Hidden) MostImportantChipResult( mostImportantChip = InternalChipModel.Hidden( @@ -295,7 +304,8 @@ constructor( shareToApp = bundle.shareToApp, castToOtherDevice = bundle.castToOtherDevice, call = bundle.call, - demoRon = bundle.demoRon, + notifs = OngoingActivityChipModel.Hidden(), + demoNotif = bundle.demoNotif, ), // All the chips are already hidden, so no need to filter anything out of the // bundle. @@ -323,7 +333,8 @@ constructor( ChipType.ShareToApp -> new.shareToApp ChipType.CastToOtherDevice -> new.castToOtherDevice ChipType.Call -> new.call - ChipType.DemoRon -> new.demoRon + ChipType.Notification -> new.notifs + ChipType.DemoNotification -> new.demoNotif } } else if (new is InternalChipModel.Shown) { // If we have a chip to show, always show it. @@ -344,7 +355,8 @@ constructor( shareToApp = OngoingActivityChipModel.Hidden(), castToOtherDevice = OngoingActivityChipModel.Hidden(), call = OngoingActivityChipModel.Hidden(), - demoRon = OngoingActivityChipModel.Hidden(), + notifs = OngoingActivityChipModel.Hidden(), + demoNotif = OngoingActivityChipModel.Hidden(), ) private val DEFAULT_MULTIPLE_INTERNAL_HIDDEN_MODEL = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt new file mode 100644 index 000000000000..b64a577ef77e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarStarter.kt @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2024 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.core + +import android.view.Display +import com.android.systemui.CoreStartable +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.display.data.repository.DisplayRepository +import com.android.systemui.display.data.repository.DisplayScopeRepository +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore +import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore +import com.android.systemui.util.kotlin.pairwiseBy +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.launch + +/** + * Responsible for creating and starting the status bar components for each display. Also does it + * for newly added displays. + */ +@SysUISingleton +class MultiDisplayStatusBarStarter +@Inject +constructor( + @Application private val applicationScope: CoroutineScope, + private val displayScopeRepository: DisplayScopeRepository, + private val statusBarOrchestratorFactory: StatusBarOrchestrator.Factory, + private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore, + private val statusBarModeRepositoryStore: StatusBarModeRepositoryStore, + private val displayRepository: DisplayRepository, + private val initializerStore: StatusBarInitializerStore, + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, + private val statusBarInitializerStore: StatusBarInitializerStore, +) : CoreStartable { + + init { + StatusBarConnectedDisplays.assertInNewMode() + } + + override fun start() { + applicationScope.launch { + displayRepository.displays + .pairwiseBy { previousDisplays, currentDisplays -> + currentDisplays - previousDisplays + } + .onStart { emit(displayRepository.displays.value) } + .collect { newDisplays -> + newDisplays.forEach { createAndStartComponentsForDisplay(it) } + } + } + } + + private fun createAndStartComponentsForDisplay(display: Display) { + val displayId = display.displayId + createAndStartOrchestratorForDisplay(displayId) + createAndStartInitializerForDisplay(displayId) + } + + private fun createAndStartOrchestratorForDisplay(displayId: Int) { + statusBarOrchestratorFactory + .create( + displayId, + displayScopeRepository.scopeForDisplay(displayId), + statusBarWindowStateRepositoryStore.forDisplay(displayId), + statusBarModeRepositoryStore.forDisplay(displayId), + initializerStore.forDisplay(displayId), + statusBarWindowControllerStore.forDisplay(displayId), + ) + .start() + } + + private fun createAndStartInitializerForDisplay(displayId: Int) { + statusBarInitializerStore.forDisplay(displayId).start() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt index 7eff8124368b..2c94632abcda 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializer.kt @@ -26,7 +26,7 @@ import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions import com.android.systemui.statusbar.phone.PhoneStatusBarViewController import com.android.systemui.statusbar.phone.fragment.CollapsedStatusBarFragment import com.android.systemui.statusbar.phone.fragment.dagger.StatusBarFragmentComponent -import com.android.systemui.statusbar.window.StatusBarWindowControllerStore +import com.android.systemui.statusbar.window.StatusBarWindowController import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -37,7 +37,7 @@ import javax.inject.Provider * Responsible for creating the status bar window and initializing the root components of that * window (see [CollapsedStatusBarFragment]) */ -interface StatusBarInitializer { +interface StatusBarInitializer : CoreStartable { var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? @@ -68,18 +68,17 @@ interface StatusBarInitializer { } interface Factory { - fun create(displayId: Int): StatusBarInitializer + fun create(statusBarWindowController: StatusBarWindowController): StatusBarInitializer } } class StatusBarInitializerImpl @AssistedInject constructor( - @Assisted private val displayId: Int, - private val statusBarWindowControllerStore: StatusBarWindowControllerStore, + @Assisted private val statusBarWindowController: StatusBarWindowController, private val collapsedStatusBarFragmentProvider: Provider<CollapsedStatusBarFragment>, private val creationListeners: Set<@JvmSuppressWildcards OnStatusBarViewInitializedListener>, -) : CoreStartable, StatusBarInitializer { +) : StatusBarInitializer { private var component: StatusBarFragmentComponent? = null @get:VisibleForTesting @@ -111,7 +110,7 @@ constructor( private fun doStart() { initialized = true - statusBarWindowControllerStore.defaultDisplay.fragmentHostManager + statusBarWindowController.fragmentHostManager .addTagListener( CollapsedStatusBarFragment.TAG, object : FragmentHostManager.FragmentListener { @@ -145,6 +144,8 @@ constructor( @AssistedFactory interface Factory : StatusBarInitializer.Factory { - override fun create(displayId: Int): StatusBarInitializerImpl + override fun create( + statusBarWindowController: StatusBarWindowController + ): StatusBarInitializerImpl } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt index 8d044bb9ce87..041f0b0fdf93 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarInitializerStore.kt @@ -16,84 +16,52 @@ package com.android.systemui.statusbar.core -import android.view.Display -import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DisplayRepository -import java.util.concurrent.ConcurrentHashMap +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.display.data.repository.SingleDisplayStore +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import javax.inject.Inject -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch /** Provides per display instances of [StatusBarInitializer]. */ -interface StatusBarInitializerStore { - /** - * The instance for the default/main display of the device. For example, on a phone or a tablet, - * the default display is the internal/built-in display of the device. - * - * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. - */ - val defaultDisplay: StatusBarInitializer - - /** - * Returns an instance for a specific display id. - * - * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing - * displays. - */ - fun forDisplay(displayId: Int): StatusBarInitializer -} +interface StatusBarInitializerStore : PerDisplayStore<StatusBarInitializer> @SysUISingleton class MultiDisplayStatusBarInitializerStore @Inject constructor( - @Background private val backgroundApplicationScope: CoroutineScope, + @Background backgroundApplicationScope: CoroutineScope, + displayRepository: DisplayRepository, private val factory: StatusBarInitializer.Factory, - private val displayRepository: DisplayRepository, -) : StatusBarInitializerStore, CoreStartable { + private val statusBarWindowControllerStore: StatusBarWindowControllerStore, +) : + StatusBarInitializerStore, + PerDisplayStoreImpl<StatusBarInitializer>(backgroundApplicationScope, displayRepository) { init { StatusBarConnectedDisplays.assertInNewMode() } - private val perDisplayInitializers = ConcurrentHashMap<Int, StatusBarInitializer>() - - override val defaultDisplay: StatusBarInitializer - get() = forDisplay(Display.DEFAULT_DISPLAY) - - override fun forDisplay(displayId: Int): StatusBarInitializer { - if (displayRepository.getDisplay(displayId) == null) { - throw IllegalArgumentException("Display with id $displayId doesn't exist.") - } - return perDisplayInitializers.computeIfAbsent(displayId) { factory.create(displayId) } + override fun createInstanceForDisplay(displayId: Int): StatusBarInitializer { + return factory.create( + statusBarWindowController = statusBarWindowControllerStore.forDisplay(displayId) + ) } - override fun start() { - backgroundApplicationScope.launch( - CoroutineName("MultiDisplayStatusBarInitializerStore#start") - ) { - displayRepository.displayRemovalEvent.collect { removedDisplayId -> - perDisplayInitializers.remove(removedDisplayId) - } - } - } + override val instanceClass = StatusBarInitializer::class.java } @SysUISingleton class SingleDisplayStatusBarInitializerStore @Inject -constructor(factory: StatusBarInitializerImpl.Factory) : StatusBarInitializerStore { +constructor(defaultInitializer: StatusBarInitializer) : + StatusBarInitializerStore, + PerDisplayStore<StatusBarInitializer> by SingleDisplayStore(defaultInitializer) { init { StatusBarConnectedDisplays.assertInLegacyMode() } - - private val defaultInstance = factory.create(Display.DEFAULT_DISPLAY) - - override val defaultDisplay: StatusBarInitializer = defaultInstance - - override fun forDisplay(displayId: Int): StatusBarInitializer = defaultDisplay } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt index d372eb29b27b..47e6c57a5ca7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/core/StatusBarOrchestrator.kt @@ -16,12 +16,12 @@ package com.android.systemui.statusbar.core +import android.view.Display import android.view.View import com.android.systemui.CoreStartable import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor -import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.demomode.DemoModeController +import com.android.systemui.dump.DumpManager import com.android.systemui.plugins.DarkIconDispatcher import com.android.systemui.plugins.PluginDependencyProvider import com.android.systemui.plugins.statusbar.StatusBarStateController @@ -31,19 +31,21 @@ import com.android.systemui.shade.ShadeSurface import com.android.systemui.statusbar.AutoHideUiElement import com.android.systemui.statusbar.NotificationRemoteInputManager import com.android.systemui.statusbar.data.model.StatusBarMode -import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore +import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository import com.android.systemui.statusbar.phone.AutoHideController import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions import com.android.systemui.statusbar.phone.PhoneStatusBarViewController -import com.android.systemui.statusbar.window.StatusBarWindowControllerStore +import com.android.systemui.statusbar.window.StatusBarWindowController import com.android.systemui.statusbar.window.data.model.StatusBarWindowState -import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore +import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStatePerDisplayRepository import com.android.wm.shell.bubbles.Bubbles import dagger.Lazy +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import java.io.PrintWriter import java.util.Optional -import javax.inject.Inject import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.combine @@ -57,27 +59,35 @@ import kotlinx.coroutines.launch * It is a temporary class, created to pull status bar related logic out of CentralSurfacesImpl. The * plan is break it out into individual classes. */ -@SysUISingleton class StatusBarOrchestrator -@Inject +@AssistedInject constructor( - @Application private val applicationScope: CoroutineScope, - private val statusBarInitializer: StatusBarInitializer, - private val statusBarModeRepository: StatusBarModeRepositoryStore, - private val statusBarWindowControllerStore: StatusBarWindowControllerStore, + @Assisted private val displayId: Int, + @Assisted private val coroutineScope: CoroutineScope, + @Assisted private val statusBarWindowStateRepository: StatusBarWindowStatePerDisplayRepository, + @Assisted private val statusBarModeRepository: StatusBarModePerDisplayRepository, + @Assisted private val statusBarInitializer: StatusBarInitializer, + @Assisted private val statusBarWindowController: StatusBarWindowController, private val demoModeController: DemoModeController, private val pluginDependencyProvider: PluginDependencyProvider, private val autoHideController: AutoHideController, private val remoteInputManager: NotificationRemoteInputManager, private val notificationShadeWindowViewControllerLazy: - Lazy<NotificationShadeWindowViewController>, + Lazy<NotificationShadeWindowViewController>, private val shadeSurface: ShadeSurface, private val bubblesOptional: Optional<Bubbles>, - private val statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore, + private val dumpManager: DumpManager, powerInteractor: PowerInteractor, primaryBouncerInteractor: PrimaryBouncerInteractor, ) : CoreStartable { + private val dumpableName: String = + if (displayId == Display.DEFAULT_DISPLAY) { + javaClass.simpleName + } else { + "${javaClass.simpleName}$displayId" + } + private val phoneStatusBarViewController = MutableStateFlow<PhoneStatusBarViewController?>(value = null) @@ -86,9 +96,9 @@ constructor( private val shouldAnimateNextBarModeChange = combine( - statusBarModeRepository.defaultDisplay.isTransientShown, + statusBarModeRepository.isTransientShown, powerInteractor.isAwake, - statusBarWindowStateRepositoryStore.defaultDisplay.windowState, + statusBarWindowStateRepository.windowState, ) { isTransientShown, isDeviceAwake, statusBarWindowState -> !isTransientShown && isDeviceAwake && @@ -107,8 +117,8 @@ constructor( private val statusBarVisible = combine( - statusBarModeRepository.defaultDisplay.statusBarMode, - statusBarWindowStateRepositoryStore.defaultDisplay.windowState, + statusBarModeRepository.statusBarMode, + statusBarWindowStateRepository.windowState, ) { mode, statusBarWindowState -> mode != StatusBarMode.LIGHTS_OUT && mode != StatusBarMode.LIGHTS_OUT_TRANSPARENT && @@ -119,7 +129,7 @@ constructor( combine( shouldAnimateNextBarModeChange, phoneStatusBarTransitions.filterNotNull(), - statusBarModeRepository.defaultDisplay.statusBarMode, + statusBarModeRepository.statusBarMode, ::Triple, ) .distinctUntilChangedBy { (_, barTransitions, statusBarMode) -> @@ -130,26 +140,29 @@ constructor( override fun start() { StatusBarSimpleFragment.assertInNewMode() - applicationScope.launch { - launch { - controllerAndBouncerShowing.collect { (controller, bouncerShowing) -> - setBouncerShowingForStatusBarComponents(controller, bouncerShowing) + coroutineScope + .launch { + dumpManager.registerCriticalDumpable(dumpableName, this@StatusBarOrchestrator) + launch { + controllerAndBouncerShowing.collect { (controller, bouncerShowing) -> + setBouncerShowingForStatusBarComponents(controller, bouncerShowing) + } } - } - launch { - barTransitionsAndDeviceAsleep.collect { (barTransitions, deviceAsleep) -> - if (deviceAsleep) { - barTransitions.finishAnimations() + launch { + barTransitionsAndDeviceAsleep.collect { (barTransitions, deviceAsleep) -> + if (deviceAsleep) { + barTransitions.finishAnimations() + } } } - } - launch { statusBarVisible.collect { updateBubblesVisibility(it) } } - launch { - barModeUpdate.collect { (animate, barTransitions, statusBarMode) -> - updateBarMode(animate, barTransitions, statusBarMode) + launch { statusBarVisible.collect { updateBubblesVisibility(it) } } + launch { + barModeUpdate.collect { (animate, barTransitions, statusBarMode) -> + updateBarMode(animate, barTransitions, statusBarMode) + } } } - } + .invokeOnCompletion { dumpManager.unregisterDumpable(dumpableName) } createAndAddWindow() setupPluginDependencies() setUpAutoHide() @@ -157,7 +170,7 @@ constructor( private fun createAndAddWindow() { initializeStatusBarFragment() - statusBarWindowControllerStore.defaultDisplay.attach() + statusBarWindowController.attach() } private fun initializeStatusBarFragment() { @@ -170,6 +183,10 @@ constructor( phoneStatusBarViewController.value = statusBarViewController phoneStatusBarTransitions.value = statusBarTransitions + if (displayId != Display.DEFAULT_DISPLAY) { + return + } + // TODO(b/373310629): shade should be display id aware notificationShadeWindowViewControllerLazy .get() .setStatusBarViewController(statusBarViewController) @@ -189,6 +206,10 @@ constructor( } private fun setUpAutoHide() { + if (displayId != Display.DEFAULT_DISPLAY) { + return + } + // TODO(b/373309973): per display implementation of auto hide controller autoHideController.setStatusBar( object : AutoHideUiElement { override fun synchronizeState() {} @@ -198,13 +219,14 @@ constructor( } override fun isVisible(): Boolean { - return statusBarModeRepository.defaultDisplay.isTransientShown.value + return statusBarModeRepository.isTransientShown.value } override fun hide() { - statusBarModeRepository.defaultDisplay.clearTransient() + statusBarModeRepository.clearTransient() } - }) + } + ) } private fun updateBarMode( @@ -215,11 +237,18 @@ constructor( if (!demoModeController.isInDemoMode) { barTransitions.transitionTo(barMode.toTransitionModeInt(), animate) } - autoHideController.touchAutoHide() + if (displayId == Display.DEFAULT_DISPLAY) { + // TODO(b/373309973): per display implementation of auto hide controller + autoHideController.touchAutoHide() + } } private fun updateBubblesVisibility(statusBarVisible: Boolean) { + if (displayId != Display.DEFAULT_DISPLAY) { + return + } bubblesOptional.ifPresent { bubbles: Bubbles -> + // TODO(b/373311537): per display implementation of Bubbles bubbles.onStatusBarVisibilityChanged(statusBarVisible) } } @@ -238,11 +267,23 @@ constructor( } override fun dump(pw: PrintWriter, args: Array<out String>) { - pw.println(statusBarWindowStateRepositoryStore.defaultDisplay.windowState.value) + pw.println(statusBarWindowStateRepository.windowState.value) CentralSurfaces.dumpBarTransitions( pw, "PhoneStatusBarTransitions", phoneStatusBarTransitions.value, ) } + + @AssistedFactory + interface Factory { + fun create( + displayId: Int, + displayScope: CoroutineScope, + statusBarWindowStateRepository: StatusBarWindowStatePerDisplayRepository, + statusBarModeRepository: StatusBarModePerDisplayRepository, + statusBarInitializer: StatusBarInitializer, + statusBarWindowController: StatusBarWindowController, + ): StatusBarOrchestrator + } } 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 5aad11fe1034..f6f4503b210a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/StatusBarModule.kt @@ -17,17 +17,20 @@ package com.android.systemui.statusbar.dagger import android.content.Context -import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.systemui.CoreStartable +import com.android.systemui.SysUICutoutProvider import com.android.systemui.dagger.SysUISingleton import com.android.systemui.log.LogBuffer import com.android.systemui.log.LogBufferFactory import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.data.StatusBarDataLayerModule import com.android.systemui.statusbar.phone.LightBarController +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProvider +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl import com.android.systemui.statusbar.phone.StatusBarSignalPolicy import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallController import com.android.systemui.statusbar.phone.ongoingcall.OngoingCallLog +import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.statusbar.ui.SystemBarUtilsProxyImpl import com.android.systemui.statusbar.window.MultiDisplayStatusBarWindowControllerStore import com.android.systemui.statusbar.window.SingleDisplayStatusBarWindowControllerStore @@ -108,5 +111,16 @@ abstract class StatusBarModule { fun provideOngoingCallLogBuffer(factory: LogBufferFactory): LogBuffer { return factory.create("OngoingCall", 75) } + + @Provides + @SysUISingleton + fun contentInsetsProvider( + factory: StatusBarContentInsetsProviderImpl.Factory, + context: Context, + configurationController: ConfigurationController, + sysUICutoutProvider: SysUICutoutProvider, + ): StatusBarContentInsetsProvider { + return factory.create(context, configurationController, sysUICutoutProvider) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt index 962cb0953f97..154be1f96e8b 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryStore.kt @@ -31,6 +31,7 @@ import javax.inject.Inject interface StatusBarModeRepositoryStore { val defaultDisplay: StatusBarModePerDisplayRepository + fun forDisplay(displayId: Int): StatusBarModePerDisplayRepository } @@ -39,7 +40,7 @@ class StatusBarModeRepositoryImpl @Inject constructor( @DisplayId private val displayId: Int, - factory: StatusBarModePerDisplayRepositoryFactory + factory: StatusBarModePerDisplayRepositoryFactory, ) : StatusBarModeRepositoryStore, CoreStartable, @@ -47,11 +48,9 @@ constructor( override val defaultDisplay = factory.create(displayId) override fun forDisplay(displayId: Int) = - if (this.displayId == displayId) { - defaultDisplay - } else { - TODO("b/127878649 implement multi-display state management") - } + // TODO(b/369337087): implement per display status bar modes. + // For now just use default display instance. + defaultDisplay override fun start() { defaultDisplay.start() diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java index 0927a728783f..a61463823613 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/HeadsUpTouchHelper.java @@ -115,23 +115,23 @@ public class HeadsUpTouchHelper implements Gefingerpoken { final float h = y - mInitialTouchY; if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop && Math.abs(h) > Math.abs(x - mInitialTouchX)) { - setTrackingHeadsUp(true); - mCollapseSnoozes = h < 0; - mInitialTouchX = x; - mInitialTouchY = y; - int startHeight = (int) (mPickedChild.getActualHeight() - + mPickedChild.getTranslationY()); - mPanel.setHeadsUpDraggingStartingHeight(startHeight); - mPanel.startExpand(x, y, true /* startTracking */, startHeight); - if (!SceneContainerFlag.isEnabled()) { + setTrackingHeadsUp(true); + mCollapseSnoozes = h < 0; + mInitialTouchX = x; + mInitialTouchY = y; + int startHeight = (int) (mPickedChild.getActualHeight() + + mPickedChild.getTranslationY()); + mPanel.setHeadsUpDraggingStartingHeight(startHeight); + mPanel.startExpand(x, y, true /* startTracking */, startHeight); + // This call needs to be after the expansion start otherwise we will get a // flicker of one frame as it's not expanded yet. mHeadsUpManager.unpinAll(true); - } - clearNotificationEffects(); - endMotion(); + clearNotificationEffects(); + endMotion(); + } return true; } break; @@ -167,17 +167,70 @@ public class HeadsUpTouchHelper implements Gefingerpoken { @Override public boolean onTouchEvent(MotionEvent event) { - if (!mTrackingHeadsUp) { + if (SceneContainerFlag.isEnabled()) { + int pointerIndex = event.findPointerIndex(mTrackingPointer); + if (pointerIndex < 0) { + pointerIndex = 0; + mTrackingPointer = event.getPointerId(pointerIndex); + } + final float x = event.getX(pointerIndex); + final float y = event.getY(pointerIndex); + switch (event.getActionMasked()) { + case MotionEvent.ACTION_POINTER_UP: + final int upPointer = event.getPointerId(event.getActionIndex()); + if (mTrackingPointer == upPointer) { + // gesture is ongoing, find a new pointer to track + final int newIndex = event.getPointerId(0) != upPointer ? 0 : 1; + mTrackingPointer = event.getPointerId(newIndex); + mInitialTouchX = event.getX(newIndex); + mInitialTouchY = event.getY(newIndex); + } + break; + case MotionEvent.ACTION_MOVE: + final float h = y - mInitialTouchY; + if (mTouchingHeadsUpView && Math.abs(h) > mTouchSlop + && Math.abs(h) > Math.abs(x - mInitialTouchX)) { + setTrackingHeadsUp(true); + mCollapseSnoozes = h < 0; + mInitialTouchX = x; + mInitialTouchY = y; + int startHeight = (int) (mPickedChild.getActualHeight() + + mPickedChild.getTranslationY()); + mPanel.setHeadsUpDraggingStartingHeight(startHeight); + mPanel.startExpand(x, y, true /* startTracking */, startHeight); + + clearNotificationEffects(); + endMotion(); + return true; + } + break; + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + if (mPickedChild != null && mTouchingHeadsUpView) { + // We may swallow this click if the heads up just came in. + if (mHeadsUpManager.shouldSwallowClick( + mPickedChild.getEntry().getSbn().getKey())) { + endMotion(); + return true; + } + } + endMotion(); + return false; + } return false; + } else { + if (!mTrackingHeadsUp) { + return false; + } + switch (event.getActionMasked()) { + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + endMotion(); + setTrackingHeadsUp(false); + break; + } + return true; } - switch (event.getActionMasked()) { - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - endMotion(); - setTrackingHeadsUp(false); - break; - } - return true; } private void endMotion() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt index 9b382e61b2e3..697a6ce52ba9 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/domain/interactor/ActiveNotificationsInteractor.kt @@ -17,6 +17,7 @@ package com.android.systemui.statusbar.notification.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.notification.collection.render.NotifStats import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository import com.android.systemui.statusbar.notification.shared.ActiveNotificationGroupModel @@ -26,6 +27,7 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map @@ -74,6 +76,17 @@ constructor( val allNotificationsCountValue: Int get() = repository.activeNotifications.value.individuals.size + /** The notifications that are promoted and ongoing. Sorted by priority order. */ + val promotedOngoingNotifications: Flow<List<ActiveNotificationModel>> = + if (StatusBarNotifChips.isEnabled) { + // TODO(b/364653005): Filter all the notifications down to just the promoted ones. + // TODO(b/364653005): [ongoingCallNotification] should be incorporated into this flow + // instead of being separate. + topLevelRepresentativeNotifications + } else { + flowOf(emptyList()) + } + /** * The priority ongoing call notification, or null if there is no ongoing call. * diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt index 695e088b5f8b..fbec6406e9d4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/emptyshade/ui/viewmodel/EmptyShadeViewModel.kt @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.notification.emptyshade.ui.viewmodel import android.content.Context import android.icu.text.MessageFormat -import com.android.app.tracing.coroutines.flow.flowOn import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dump.DumpManager import com.android.systemui.modes.shared.ModesUi @@ -40,6 +39,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java index 560028cb5640..d538f52fd637 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ActivatableNotificationView.java @@ -449,7 +449,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } // We need to reset the View state, even if the animation was cancelled - onAppearAnimationFinished(isAppearing); + onAppearAnimationFinished(isAppearing, /* cancelled = */ !mRunWithoutInterruptions); if (mRunWithoutInterruptions) { InteractionJankMonitor.getInstance().end(getCujType(isAppearing)); @@ -463,6 +463,7 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView if (onStartedRunnable != null) { onStartedRunnable.run(); } + onAppearAnimationStarted(isAppearing); mRunWithoutInterruptions = true; Configuration.Builder builder = Configuration.Builder .withView(getCujType(isAppearing), ActivatableNotificationView.this); @@ -486,6 +487,8 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView frameTimeNanos -> { if (mAppearAnimator == cachedAnimator) { mAppearAnimator.start(); + } else { + onAppearAnimationSkipped(isAppearing); } }, delay); } @@ -502,7 +505,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView } } - protected void onAppearAnimationFinished(boolean wasAppearing) { + protected void onAppearAnimationStarted(boolean isAppear) { + } + + protected void onAppearAnimationSkipped(boolean isAppear) { + } + + protected void onAppearAnimationFinished(boolean wasAppearing, boolean cancelled) { } private void cancelAppearAnimation() { @@ -830,4 +839,13 @@ public abstract class ActivatableNotificationView extends ExpandableOutlineView }); } } + + protected void dumpAppearAnimationProperties(IndentingPrintWriter pw, String[] args) { + pw.print("AppearAnimation: "); + pw.print("mDrawingAppearAnimation", mDrawingAppearAnimation); + pw.print("mAppearAnimationFraction", mAppearAnimationFraction); + pw.print("mIsHeadsUpAnimation", mIsHeadsUpAnimation); + pw.print("mTargetPoint", mTargetPoint); + pw.println(); + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java index 49153d1d8eef..38e66099022c 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java @@ -1835,6 +1835,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView void logSkipResetAllContentAlphas( NotificationEntry entry ); + + /** Called when we start an appear animation. */ + void logStartAppearAnimation(NotificationEntry entry, boolean isAppear); + + /** Called when we cancel the running appear animation. */ + void logCancelAppearDrawing(NotificationEntry entry, boolean wasDrawing); + + /** Called when the animator of the appear animation is started. */ + void logAppearAnimationStarted(NotificationEntry entry, boolean isAppear); + + /** Called when we prepared an appear animation, but the animator was never started. */ + void logAppearAnimationSkipped(NotificationEntry entry, boolean isAppear); + + /** Called when the animator of the appear animation is finished. */ + void logAppearAnimationFinished( + NotificationEntry entry, + boolean isAppear, + boolean cancelled + ); } /** @@ -3165,6 +3184,13 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, + Runnable onFinishRunnable) { + mLogger.logStartAppearAnimation(getEntry(), /* isAppear = */ true); + super.performAddAnimation(delay, duration, isHeadsUpAppear, onFinishRunnable); + } + + @Override public long performRemoveAnimation( long duration, long delay, @@ -3173,6 +3199,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView Runnable onStartedRunnable, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener, ClipSide clipSide) { + mLogger.logStartAppearAnimation(getEntry(), /* isAppear = */ false); if (mMenuRow != null && mMenuRow.isMenuVisible()) { Animator anim = getTranslateViewAnimator(0f, null /* listener */); if (anim != null) { @@ -3201,8 +3228,25 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override - protected void onAppearAnimationFinished(boolean wasAppearing) { - super.onAppearAnimationFinished(wasAppearing); + protected void onAppearAnimationStarted(boolean isAppear) { + mLogger.logAppearAnimationStarted(getEntry(), /* isAppear = */ isAppear); + super.onAppearAnimationStarted(isAppear); + } + + @Override + protected void onAppearAnimationSkipped(boolean isAppear) { + mLogger.logAppearAnimationSkipped(getEntry(), /* isAppear = */ isAppear); + super.onAppearAnimationSkipped(isAppear); + } + + @Override + protected void onAppearAnimationFinished(boolean wasAppearing, boolean cancelled) { + mLogger.logAppearAnimationFinished( + /* entry = */ getEntry(), + /* isAppear = */ wasAppearing, + /* cancelled = */ cancelled + ); + super.onAppearAnimationFinished(wasAppearing, cancelled); if (wasAppearing) { // During the animation the visible view might have changed, so let's make sure all // alphas are reset @@ -3218,6 +3262,12 @@ public class ExpandableNotificationRow extends ActivatableNotificationView } @Override + public void cancelAppearDrawing() { + mLogger.logCancelAppearDrawing(getEntry(), isDrawingAppearAnimation()); + super.cancelAppearDrawing(); + } + + @Override public void resetAllContentAlphas() { mLogger.logResetAllContentAlphas(getEntry()); mPrivateLayout.setAlpha(1f); @@ -3883,6 +3933,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView dumpHeights(pw); } showingLayout.dump(pw, args); + dumpAppearAnimationProperties(pw, args); dumpCustomOutline(pw, args); dumpClipping(pw, args); if (getViewState() != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java index c31a2cb8908b..23a2facf4c5a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowController.java @@ -209,6 +209,32 @@ public class ExpandableNotificationRowController implements NotifViewController ) { mLogBufferLogger.logSkipResetAllContentAlphas(entry); } + + @Override + public void logStartAppearAnimation(NotificationEntry entry, boolean isAppear) { + mLogBufferLogger.logStartAppearAnimation(entry, isAppear); + } + + @Override + public void logCancelAppearDrawing(NotificationEntry entry, boolean wasDrawing) { + mLogBufferLogger.logCancelAppearDrawing(entry, wasDrawing); + } + + @Override + public void logAppearAnimationStarted(NotificationEntry entry, boolean isAppear) { + mLogBufferLogger.logAppearAnimationStarted(entry, isAppear); + } + + @Override + public void logAppearAnimationSkipped(NotificationEntry entry, boolean isAppear) { + mLogBufferLogger.logAppearAnimationSkipped(entry, isAppear); + } + + @Override + public void logAppearAnimationFinished(NotificationEntry entry, boolean isAppear, + boolean cancelled) { + mLogBufferLogger.logAppearAnimationFinished(entry, isAppear, cancelled); + } }; diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt index b90aa107d617..71f9c07ba2d1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifRemoteViewsFactoryContainer.kt @@ -19,6 +19,7 @@ package com.android.systemui.statusbar.notification.row import android.widget.flags.Flags.notifLinearlayoutOptimized import com.android.systemui.flags.FeatureFlags import com.android.systemui.flags.Flags +import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory import com.android.systemui.statusbar.notification.shared.NotificationViewFlipperPausing import javax.inject.Inject import javax.inject.Provider @@ -35,6 +36,7 @@ constructor( bigPictureLayoutInflaterFactory: BigPictureLayoutInflaterFactory, optimizedLinearLayoutFactory: NotificationOptimizedLinearLayoutFactory, notificationViewFlipperFactory: Provider<NotificationViewFlipperFactory>, + notificationRowIconViewInflaterFactory: NotificationRowIconViewInflaterFactory, ) : NotifRemoteViewsFactoryContainer { override val factories: Set<NotifRemoteViewsFactory> = buildSet { add(precomputedTextViewFactory) @@ -47,5 +49,8 @@ constructor( if (NotificationViewFlipperPausing.isEnabled) { add(notificationViewFlipperFactory.get()) } + if (android.app.Flags.notificationsRedesignAppIcons()) { + add(notificationRowIconViewInflaterFactory) + } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index e10fd8f3b3fb..41abac1d47f7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -342,7 +342,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder * Cancel any pending content view frees from {@link #freeNotificationView} for the provided * content views. * - * @param row top level notification row containing the content views + * @param row top level notification row containing the content views * @param contentViews content views to cancel pending frees on */ private void cancelContentViewFrees( @@ -478,6 +478,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_HEADS_UP)); setRemoteViewsInflaterFactory(result.newPublicView, notifLayoutInflaterFactoryProvider.provide(row, FLAG_CONTENT_VIEW_PUBLIC)); + if (android.app.Flags.notificationsRedesignAppIcons()) { + setRemoteViewsInflaterFactory(result.mNewGroupHeaderView, + notifLayoutInflaterFactoryProvider.provide(row, FLAG_GROUP_SUMMARY_HEADER)); + setRemoteViewsInflaterFactory(result.mNewMinimizedGroupHeaderView, + notifLayoutInflaterFactoryProvider.provide(row, + FLAG_LOW_PRIORITY_GROUP_SUMMARY_HEADER)); + } } private static void setRemoteViewsInflaterFactory(RemoteViews remoteViews, @@ -516,6 +523,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder logger.logAsyncTaskProgress(entry, "contracted view applied"); result.inflatedContentView = v; } + @Override public RemoteViews getRemoteView() { return result.newContentView; @@ -1406,6 +1414,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder @VisibleForTesting abstract static class ApplyCallback { public abstract void setResultView(View v); + public abstract RemoteViews getRemoteView(); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt index b1e90329e01a..6e78f561cfc1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowLogger.kt @@ -30,7 +30,7 @@ class NotificationRowLogger @Inject constructor( @NotificationLog private val buffer: LogBuffer, - @NotificationRenderLog private val notificationRenderBuffer: LogBuffer + @NotificationRenderLog private val notificationRenderBuffer: LogBuffer, ) { fun logKeepInParentChildDetached(child: NotificationEntry, oldParent: NotificationEntry?) { buffer.log( @@ -40,7 +40,7 @@ constructor( str1 = child.logKey str2 = oldParent.logKey }, - { "Detach child $str1 kept in parent $str2" } + { "Detach child $str1 kept in parent $str2" }, ) } @@ -52,13 +52,13 @@ constructor( str1 = child.logKey str2 = newParent.logKey }, - { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" } + { "Skipping to attach $str1 to $str2, because it still flagged to keep in parent" }, ) } fun logRemoveTransientFromContainer( childEntry: NotificationEntry, - containerEntry: NotificationEntry + containerEntry: NotificationEntry, ) { notificationRenderBuffer.log( TAG, @@ -67,25 +67,20 @@ constructor( str1 = childEntry.logKey str2 = containerEntry.logKey }, - { "RemoveTransientRow from ChildrenContainer: childKey: $str1 -- containerKey: $str2" } + { "RemoveTransientRow from ChildrenContainer: childKey: $str1 -- containerKey: $str2" }, ) } - fun logRemoveTransientFromNssl( - childEntry: NotificationEntry, - ) { + fun logRemoveTransientFromNssl(childEntry: NotificationEntry) { notificationRenderBuffer.log( TAG, LogLevel.INFO, { str1 = childEntry.logKey }, - { "RemoveTransientRow from Nssl: childKey: $str1" } + { "RemoveTransientRow from Nssl: childKey: $str1" }, ) } - fun logRemoveTransientFromViewGroup( - childEntry: NotificationEntry, - containerView: ViewGroup, - ) { + fun logRemoveTransientFromViewGroup(childEntry: NotificationEntry, containerView: ViewGroup) { notificationRenderBuffer.log( TAG, LogLevel.WARNING, @@ -93,14 +88,14 @@ constructor( str1 = childEntry.logKey str2 = containerView.toString() }, - { "RemoveTransientRow from other ViewGroup: childKey: $str1 -- ViewGroup: $str2" } + { "RemoveTransientRow from other ViewGroup: childKey: $str1 -- ViewGroup: $str2" }, ) } fun logAddTransientRow( childEntry: NotificationEntry, containerEntry: NotificationEntry, - index: Int + index: Int, ) { notificationRenderBuffer.log( TAG, @@ -110,14 +105,11 @@ constructor( str2 = containerEntry.logKey int1 = index }, - { "addTransientRow to row: childKey: $str1 -- containerKey: $str2 -- index: $int1" } + { "addTransientRow to row: childKey: $str1 -- containerKey: $str2 -- index: $int1" }, ) } - fun logRemoveTransientRow( - childEntry: NotificationEntry, - containerEntry: NotificationEntry, - ) { + fun logRemoveTransientRow(childEntry: NotificationEntry, containerEntry: NotificationEntry) { notificationRenderBuffer.log( TAG, LogLevel.ERROR, @@ -125,7 +117,7 @@ constructor( str1 = childEntry.logKey str2 = containerEntry.logKey }, - { "removeTransientRow from row: childKey: $str1 -- containerKey: $str2" } + { "removeTransientRow from row: childKey: $str1 -- containerKey: $str2" }, ) } @@ -134,7 +126,7 @@ constructor( TAG, LogLevel.INFO, { str1 = entry.logKey }, - { "resetAllContentAlphas: $str1" } + { "resetAllContentAlphas: $str1" }, ) } @@ -143,7 +135,72 @@ constructor( TAG, LogLevel.INFO, { str1 = entry.logKey }, - { "Skip resetAllContentAlphas: $str1" } + { "Skip resetAllContentAlphas: $str1" }, + ) + } + + fun logStartAppearAnimation(entry: NotificationEntry, isAppear: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = entry.logKey + bool1 = isAppear + }, + { "startAppearAnimation childKey: $str1 isAppear:$bool1" }, + ) + } + + fun logCancelAppearDrawing(entry: NotificationEntry, wasDrawing: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.WARNING, + { + str1 = entry.logKey + bool1 = wasDrawing + }, + { "cancelAppearDrawing childKey: $str1 wasDrawing:$bool1" }, + ) + } + + fun logAppearAnimationStarted(entry: NotificationEntry, isAppear: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = entry.logKey + bool1 = isAppear + }, + { "onAppearAnimationStarted childKey: $str1 isAppear:$bool1" }, + ) + } + + fun logAppearAnimationSkipped(entry: NotificationEntry, isAppear: Boolean) { + notificationRenderBuffer.log( + TAG, + LogLevel.WARNING, + { + str1 = entry.logKey + bool1 = isAppear + }, + { "Skipped an appear animation childKey: $str1 isAppear:$bool1" }, + ) + } + + fun logAppearAnimationFinished( + entry: NotificationEntry, + isAppear: Boolean, + cancelled: Boolean, + ) { + notificationRenderBuffer.log( + TAG, + LogLevel.DEBUG, + { + str1 = entry.logKey + bool1 = isAppear + bool2 = cancelled + }, + { "onAppearAnimationFinished childKey: $str1 isAppear:$bool1 cancelled:$bool2" }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java index 84f2f6670839..4feb78a46d60 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationRowModule.java @@ -17,6 +17,8 @@ package com.android.systemui.statusbar.notification.row; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.statusbar.notification.row.icon.AppIconProviderModule; +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProviderModule; import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor; import dagger.Binds; @@ -28,7 +30,7 @@ import javax.inject.Provider; /** * Dagger Module containing notification row and view inflation implementations. */ -@Module +@Module(includes = {AppIconProviderModule.class, NotificationIconStyleProviderModule.class}) public abstract class NotificationRowModule { /** diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt new file mode 100644 index 000000000000..24b5cf1aa33b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/AppIconProvider.kt @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2024 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.row.icon + +import android.app.ActivityManager +import android.app.Flags +import android.content.Context +import android.content.pm.PackageManager.NameNotFoundException +import android.graphics.Color +import android.graphics.drawable.BitmapDrawable +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.util.Log +import com.android.internal.R +import com.android.launcher3.icons.BaseIconFactory +import com.android.systemui.dagger.SysUISingleton +import dagger.Module +import dagger.Provides +import javax.inject.Inject +import javax.inject.Provider + +/** A provider used to cache and fetch app icons used by notifications. */ +interface AppIconProvider { + @Throws(NameNotFoundException::class) + fun getOrFetchAppIcon(packageName: String, context: Context): Drawable +} + +@SysUISingleton +class AppIconProviderImpl @Inject constructor(private val sysuiContext: Context) : AppIconProvider { + private val iconFactory: BaseIconFactory + get() { + val isLowRam = ActivityManager.isLowRamDeviceStatic() + val res = sysuiContext.resources + val iconSize: Int = + res.getDimensionPixelSize( + if (isLowRam) R.dimen.notification_small_icon_size_low_ram + else R.dimen.notification_small_icon_size + ) + return BaseIconFactory(sysuiContext, res.configuration.densityDpi, iconSize) + } + + override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable { + val icon = context.packageManager.getApplicationIcon(packageName) + return BitmapDrawable( + context.resources, + iconFactory.createScaledBitmap(icon, BaseIconFactory.MODE_HARDWARE), + ) + } +} + +class NoOpIconProvider : AppIconProvider { + companion object { + const val TAG = "NoOpIconProvider" + } + + override fun getOrFetchAppIcon(packageName: String, context: Context): Drawable { + Log.wtf(TAG, "NoOpIconProvider should not be used anywhere.") + return ColorDrawable(Color.WHITE) + } +} + +@Module +class AppIconProviderModule { + @Provides + @SysUISingleton + fun provideImpl(realImpl: Provider<AppIconProviderImpl>): AppIconProvider = + if (Flags.notificationsRedesignAppIcons()) { + realImpl.get() + } else { + NoOpIconProvider() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt new file mode 100644 index 000000000000..165c1a7803a9 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProvider.kt @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2024 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.row.icon + +import android.annotation.WorkerThread +import android.app.Flags +import android.content.Context +import android.content.pm.ApplicationInfo +import android.service.notification.StatusBarNotification +import android.util.Log +import com.android.systemui.dagger.SysUISingleton +import dagger.Module +import dagger.Provides +import javax.inject.Inject +import javax.inject.Provider + +/** + * A provider used to cache and fetch information about which icon should be displayed by + * notifications. + */ +interface NotificationIconStyleProvider { + @WorkerThread + fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean +} + +@SysUISingleton +class NotificationIconStyleProviderImpl @Inject constructor() : NotificationIconStyleProvider { + override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean { + val packageContext = notification.getPackageContext(context) + return !belongsToHeadlessSystemApp(packageContext) + } + + @WorkerThread + private fun belongsToHeadlessSystemApp(context: Context): Boolean { + val info = context.applicationInfo + if (info != null) { + if ((info.flags and ApplicationInfo.FLAG_SYSTEM) == 0) { + // It's not a system app at all. + return false + } else { + // If there's no launch intent, it's probably a headless app. + val pm = context.packageManager + return (pm.getLaunchIntentForPackage(info.packageName) == null) + } + } else { + // If for some reason we don't have the app info, we don't know; best assume it's + // not a system app. + return false + } + } +} + +class NoOpIconStyleProvider : NotificationIconStyleProvider { + companion object { + const val TAG = "NoOpIconStyleProvider" + } + + override fun shouldShowAppIcon(notification: StatusBarNotification, context: Context): Boolean { + Log.wtf(TAG, "NoOpIconStyleProvider should not be used anywhere.") + return true + } +} + +@Module +class NotificationIconStyleProviderModule { + @Provides + @SysUISingleton + fun provideImpl( + realImpl: Provider<NotificationIconStyleProviderImpl> + ): NotificationIconStyleProvider = + if (Flags.notificationsRedesignAppIcons()) { + realImpl.get() + } else { + NoOpIconStyleProvider() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt new file mode 100644 index 000000000000..79defd255de0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/icon/NotificationRowIconViewInflaterFactory.kt @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2024 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.row.icon + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.view.View +import com.android.internal.widget.NotificationRowIconView +import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow +import com.android.systemui.statusbar.notification.row.NotifRemoteViewsFactory +import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder +import javax.inject.Inject + +/** + * A factory which owns the construction of any NotificationRowIconView inside of Notifications in + * SystemUI. This allows overriding the small icon with the app icon in notifications. + */ +class NotificationRowIconViewInflaterFactory +@Inject +constructor( + private val appIconProvider: AppIconProvider, + private val iconStyleProvider: NotificationIconStyleProvider, +) : NotifRemoteViewsFactory { + override fun instantiate( + row: ExpandableNotificationRow, + @NotificationRowContentBinder.InflationFlag layoutType: Int, + parent: View?, + name: String, + context: Context, + attrs: AttributeSet, + ): View? { + return when (name) { + NotificationRowIconView::class.java.name -> + NotificationRowIconView(context, attrs).also { view -> + val sbn = row.entry.sbn + view.setIconProvider( + object : NotificationRowIconView.NotificationIconProvider { + override fun shouldShowAppIcon(): Boolean { + return iconStyleProvider.shouldShowAppIcon(row.entry.sbn, context) + } + + override fun getAppIcon(): Drawable { + return appIconProvider.getOrFetchAppIcon(sbn.packageName, context) + } + } + ) + } + else -> null + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java index 00c5c40fc8ac..379a67ed3942 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java @@ -2122,9 +2122,6 @@ public class NotificationStackScrollLayoutController implements Dumpable { boolean hunWantsIt = false; if (shouldHeadsUpHandleTouch()) { hunWantsIt = mHeadsUpTouchHelper.onInterceptTouchEvent(ev); - if (hunWantsIt) { - mView.startDraggingOnHun(); - } } boolean swipeWantsIt = false; if (mLongPressedView == null && !mView.isBeingDragged() @@ -2210,6 +2207,9 @@ public class NotificationStackScrollLayoutController implements Dumpable { boolean hunWantsIt = false; if (shouldHeadsUpHandleTouch()) { hunWantsIt = mHeadsUpTouchHelper.onTouchEvent(ev); + if (hunWantsIt) { + mView.startDraggingOnHun(); + } } // Check if we need to clear any snooze leavebehinds diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt index 87d70ba12012..fb42ee7908b2 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/NotificationScrollViewBinder.kt @@ -17,7 +17,8 @@ package com.android.systemui.statusbar.notification.stack.ui.viewbinder import android.util.Log -import com.android.app.tracing.coroutines.flow.filter +import com.android.app.tracing.coroutines.flow.collectTraced +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.common.ui.ConfigurationState import com.android.systemui.common.ui.view.onLayoutChanged import com.android.systemui.dagger.SysUISingleton @@ -37,7 +38,6 @@ import kotlinx.coroutines.DisposableHandle import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.launch /** Binds the [NotificationScrollView]. */ @SysUISingleton @@ -79,39 +79,39 @@ constructor( launch { viewModel .shadeScrimShape(cornerRadius = scrimRadius, viewLeftOffset = viewLeftOffset) - .collect { view.setScrimClippingShape(it) } + .collectTraced { view.setScrimClippingShape(it) } } - launch { viewModel.maxAlpha.collect { view.setMaxAlpha(it) } } - launch { viewModel.scrolledToTop.collect { view.setScrolledToTop(it) } } + launch { viewModel.maxAlpha.collectTraced { view.setMaxAlpha(it) } } + launch { viewModel.scrolledToTop.collectTraced { view.setScrolledToTop(it) } } launch { - viewModel.expandFraction.collect { view.setExpandFraction(it.coerceIn(0f, 1f)) } + viewModel.expandFraction.collectTraced { view.setExpandFraction(it.coerceIn(0f, 1f)) } } - launch { viewModel.qsExpandFraction.collect { view.setQsExpandFraction(it) } } + launch { viewModel.qsExpandFraction.collectTraced { view.setQsExpandFraction(it) } } launch { - viewModel.isShowingStackOnLockscreen.collect { + viewModel.isShowingStackOnLockscreen.collectTraced { view.setShowingStackOnLockscreen(it) } } launch { - viewModel.alphaForLockscreenFadeIn.collect { view.setAlphaForLockscreenFadeIn(it) } + viewModel.alphaForLockscreenFadeIn.collectTraced { view.setAlphaForLockscreenFadeIn(it) } } - launch { viewModel.isScrollable.collect { view.setScrollingEnabled(it) } } - launch { viewModel.isDozing.collect { isDozing -> view.setDozing(isDozing) } } + launch { viewModel.isScrollable.collectTraced { view.setScrollingEnabled(it) } } + launch { viewModel.isDozing.collectTraced { isDozing -> view.setDozing(isDozing) } } launch { - viewModel.isPulsing.collect { isPulsing -> + viewModel.isPulsing.collectTraced { isPulsing -> view.setPulsing(isPulsing, viewModel.shouldAnimatePulse.value) } } launch { viewModel.shouldResetStackTop .filter { it } - .collect { view.setStackTop(-(view.getHeadsUpInset().toFloat())) } + .collectTraced { view.setStackTop(-(view.getHeadsUpInset().toFloat())) } } launch { - viewModel.shouldCloseGuts.filter { it }.collect { view.closeGutsOnSceneTouch() } + viewModel.shouldCloseGuts.filter { it }.collectTraced { view.closeGutsOnSceneTouch() } } - launch { viewModel.suppressHeightUpdates.collect { view.suppressHeightUpdates(it) } } + launch { viewModel.suppressHeightUpdates.collectTraced { view.suppressHeightUpdates(it) } } launchAndDispose { view.setSyntheticScrollConsumer(viewModel.syntheticScrollConsumer) 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 f39af18afcea..878ae91391e9 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 @@ -20,6 +20,7 @@ package com.android.systemui.statusbar.notification.stack.ui.viewmodel import androidx.annotation.VisibleForTesting +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.common.shared.model.NotificationContainerBounds import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.dagger.SysUISingleton @@ -158,6 +159,7 @@ constructor( ) { shadeExpansion, qsExpansion -> shadeExpansion || qsExpansion } + .flowName("isAnyExpanded") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -175,6 +177,7 @@ constructor( isAnyExpanded -> isShadeLocked && isAnyExpanded } + .flowName("isShadeLocked") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -227,6 +230,7 @@ constructor( ), keyguardTransitionInteractor.transitionValue(LOCKSCREEN).map { it > 0f }, ) + .flowName("isOnLockscreen") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -239,6 +243,7 @@ constructor( combine(isOnLockscreen, isAnyExpanded) { isKeyguard, isAnyExpanded -> isKeyguard && !isAnyExpanded } + .flowName("isOnLockscreenWithoutShade") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -274,6 +279,7 @@ constructor( combine(isOnGlanceableHub, isAnyExpanded) { isGlanceableHub, isAnyExpanded -> isGlanceableHub && !isAnyExpanded } + .flowName("isOnGlanceableHubWithoutShade") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -288,6 +294,7 @@ constructor( isAnyExpanded -> isDreaming && !isAnyExpanded } + .flowName("isDreamingWithoutShade") .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -345,6 +352,7 @@ constructor( } } } + .flowName("shadeCollapseFadeIn") .stateIn( scope = applicationScope, started = SharingStarted.WhileSubscribed(), @@ -382,6 +390,7 @@ constructor( bounds.copy(top = top, isAnimated = animate) } } + .flowName("bounds") .stateIn( scope = applicationScope, started = SharingStarted.Lazily, @@ -495,6 +504,7 @@ constructor( // flatMapLatest below, the last value gets emitted, to avoid the randomness of `merge`. val alphaForTransitionsAndShade = merge(alphaForTransitions(viewState), alphaForShadeAndQsExpansion) + .flowName("alphaForTransitionsAndShade") .stateIn( // Use view-level scope instead of ApplicationScope, to prevent collection that // never stops diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java index cb4454d88b2d..3d7cd9c9fbcd 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -128,7 +128,6 @@ import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dagger.qualifiers.UiBackground; import com.android.systemui.demomode.DemoMode; import com.android.systemui.demomode.DemoModeController; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.emergency.EmergencyGesture; import com.android.systemui.emergency.EmergencyGestureModule.EmergencyGestureIntentFactory; import com.android.systemui.flags.FeatureFlags; @@ -2825,23 +2824,13 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mScrimController.setExpansionAffectsAlpha(!unlocking); if (mAlternateBouncerInteractor.isVisibleState()) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) - && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED - || mTransitionToFullShadeProgress > 0f)) { - // Assume scrim state for shade is already correct and do nothing - } else { - // Safeguard which prevents the scrim from being stuck in the wrong state - mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); - } + if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) + && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED + || mTransitionToFullShadeProgress > 0f)) { + // Assume scrim state for shade is already correct and do nothing } else { - if ((!mKeyguardStateController.isOccluded() || mShadeSurface.isPanelExpanded()) - && (mState == StatusBarState.SHADE || mState == StatusBarState.SHADE_LOCKED - || mTransitionToFullShadeProgress > 0f)) { - mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED_SHADE); - } else { - mScrimController.legacyTransitionTo(ScrimState.AUTH_SCRIMMED); - } + // Safeguard which prevents the scrim from being stuck in the wrong state + mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); } // This will cancel the keyguardFadingAway animation if it is running. We need to do // this as otherwise it can remain pending and leave keyguard in a weird state. @@ -3168,12 +3157,8 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { public void onDozeAmountChanged(float linear, float eased) { if (!lightRevealMigration() && !(mLightRevealScrim.getRevealEffect() instanceof CircleReveal)) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - // If wakeAndUnlocking, this is handled in AuthRippleInteractor - if (!mBiometricUnlockController.isWakeAndUnlock()) { - mLightRevealScrim.setRevealAmount(1f - linear); - } - } else { + // If wakeAndUnlocking, this is handled in AuthRippleInteractor + if (!mBiometricUnlockController.isWakeAndUnlock()) { mLightRevealScrim.setRevealAmount(1f - linear); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt index 316e1f13bc2b..a34ac2e11c2e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBypassController.kt @@ -22,7 +22,7 @@ import android.content.res.Resources import android.hardware.biometrics.BiometricSourceType import android.provider.Settings import com.android.app.tracing.ListenersTracing.forEachTraced -import com.android.app.tracing.coroutines.launch +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java index 069c6241491c..40e52935daf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimController.java @@ -1715,26 +1715,6 @@ public class ScrimController implements ViewTreeObserver.OnPreDrawListener, Dump updateScrims(); } - public void setHasBackdrop(boolean hasBackdrop) { - for (ScrimState state : ScrimState.values()) { - state.setHasBackdrop(hasBackdrop); - } - - // Backdrop event may arrive after state was already applied, - // in this case, back-scrim needs to be re-evaluated - if (mState == ScrimState.AOD || mState == ScrimState.PULSING) { - float newBehindAlpha = mState.getBehindAlpha(); - if (isNaN(newBehindAlpha)) { - throw new IllegalStateException("Scrim opacity is NaN for state: " + mState - + ", back: " + mBehindAlpha); - } - if (mBehindAlpha != newBehindAlpha) { - mBehindAlpha = newBehindAlpha; - updateScrims(); - } - } - } - private void setKeyguardFadingAway(boolean fadingAway, long duration) { for (ScrimState state : ScrimState.values()) { state.setKeyguardFadingAway(fadingAway, duration); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java index fbba3dc107f1..a0ab6120f372 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ScrimState.java @@ -210,7 +210,7 @@ public enum ScrimState { @Override public float getMaxLightRevealScrimAlpha() { - return mWallpaperSupportsAmbientMode && !mHasBackdrop ? 0f : 1f; + return mWallpaperSupportsAmbientMode ? 0f : 1f; } @Override @@ -369,7 +369,6 @@ public enum ScrimState { DockManager mDockManager; boolean mDisplayRequiresBlanking; boolean mWallpaperSupportsAmbientMode; - boolean mHasBackdrop; boolean mLaunchingAffordanceWithPreview; boolean mOccludeAnimationPlaying; boolean mWakeLockScreenSensorActive; @@ -489,10 +488,6 @@ public enum ScrimState { return false; } - public void setHasBackdrop(boolean hasBackdrop) { - mHasBackdrop = hasBackdrop; - } - public void setWakeLockScreenSensorActive(boolean active) { mWakeLockScreenSensorActive = active; } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt index 613efaa148f5..c6f6bd90fce6 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarContentInsetsProvider.kt @@ -34,10 +34,10 @@ import com.android.systemui.Dumpable import com.android.systemui.StatusBarInsetsCommand import com.android.systemui.SysUICutoutInformation import com.android.systemui.SysUICutoutProvider -import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dump.DumpManager import com.android.systemui.res.R import com.android.systemui.statusbar.commandline.CommandRegistry +import com.android.systemui.statusbar.phone.StatusBarContentInsetsProviderImpl.CacheKey import com.android.systemui.statusbar.policy.CallbackController import com.android.systemui.statusbar.policy.ConfigurationController import com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE @@ -47,9 +47,11 @@ import com.android.systemui.util.leak.RotationUtils.ROTATION_UPSIDE_DOWN import com.android.systemui.util.leak.RotationUtils.Rotation import com.android.systemui.util.leak.RotationUtils.getExactRotation import com.android.systemui.util.leak.RotationUtils.getResourcesForRotation +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import java.io.PrintWriter import java.lang.Math.max -import javax.inject.Inject /** * Encapsulates logic that can solve for the left/right insets required for the status bar contents. @@ -64,19 +66,87 @@ import javax.inject.Inject * * NOTE: This class is not threadsafe */ -@SysUISingleton -class StatusBarContentInsetsProvider -@Inject +interface StatusBarContentInsetsProvider : + CallbackController<StatusBarContentInsetsChangedListener> { + + /** + * Some views may need to care about whether or not the current top display cutout is located in + * the corner rather than somewhere in the center. In the case of a corner cutout, the status + * bar area is contiguous. + */ + fun currentRotationHasCornerCutout(): Boolean + + /** + * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy + * dot in the coordinates relative to the given rotation. + * + * @param rotation the rotation for which the bounds are required. This is an absolute value + * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from + * which this method is called) + */ + fun getBoundingRectForPrivacyChipForRotation( + @Rotation rotation: Int, + displayCutout: DisplayCutout?, + ): Rect + + /** + * Calculate the distance from the left, right and top edges of the screen to the status bar + * content area. This differs from the content area rects in that these values can be used + * directly as padding. + * + * @param rotation the target rotation for which to calculate insets + */ + fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets + + /** + * Calculate the insets for the status bar content in the device's current rotation + * + * @see getStatusBarContentAreaForRotation + */ + fun getStatusBarContentInsetsForCurrentRotation(): Insets + + /** + * Calculates the area of the status bar contents invariant of the current device rotation, in + * the target rotation's coordinates + * + * @param rotation the rotation for which the bounds are required. This is an absolute value + * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from + * which this method is called) + */ + fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect + + /** Get the status bar content area for the given rotation, in absolute bounds */ + fun getStatusBarContentAreaForCurrentRotation(): Rect + + fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int + + interface Factory { + fun create( + context: Context, + configurationController: ConfigurationController, + sysUICutoutProvider: SysUICutoutProvider, + ): StatusBarContentInsetsProvider + } +} + +class StatusBarContentInsetsProviderImpl +@AssistedInject constructor( - val context: Context, - val configurationController: ConfigurationController, + @Assisted val context: Context, + @Assisted val configurationController: ConfigurationController, val dumpManager: DumpManager, val commandRegistry: CommandRegistry, - val sysUICutoutProvider: SysUICutoutProvider, -) : - CallbackController<StatusBarContentInsetsChangedListener>, - ConfigurationController.ConfigurationListener, - Dumpable { + @Assisted val sysUICutoutProvider: SysUICutoutProvider, +) : StatusBarContentInsetsProvider, ConfigurationController.ConfigurationListener, Dumpable { + + @AssistedFactory + interface Factory : StatusBarContentInsetsProvider.Factory { + override fun create( + context: Context, + configurationController: ConfigurationController, + sysUICutoutProvider: SysUICutoutProvider, + ): StatusBarContentInsetsProviderImpl + } // Limit cache size as potentially we may connect large number of displays // (e.g. network displays) @@ -95,7 +165,7 @@ constructor( object : StatusBarInsetsCommand.Callback { override fun onExecute( command: StatusBarInsetsCommand, - printWriter: PrintWriter + printWriter: PrintWriter, ) { executeCommand(command, printWriter) } @@ -133,12 +203,7 @@ constructor( listeners.forEach { it.onStatusBarContentInsetsChanged() } } - /** - * Some views may need to care about whether or not the current top display cutout is located in - * the corner rather than somewhere in the center. In the case of a corner cutout, the status - * bar area is contiguous. - */ - fun currentRotationHasCornerCutout(): Boolean { + override fun currentRotationHasCornerCutout(): Boolean { val cutout = checkNotNull(context.display).cutout ?: return false val topBounds = cutout.boundingRectTop @@ -148,17 +213,9 @@ constructor( return topBounds.left <= 0 || topBounds.right >= point.x } - /** - * Calculates the maximum bounding rectangle for the privacy chip animation + ongoing privacy - * dot in the coordinates relative to the given rotation. - * - * @param rotation the rotation for which the bounds are required. This is an absolute value - * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from - * which this method is called) - */ - fun getBoundingRectForPrivacyChipForRotation( + override fun getBoundingRectForPrivacyChipForRotation( @Rotation rotation: Int, - displayCutout: DisplayCutout? + displayCutout: DisplayCutout?, ): Rect { val key = getCacheKey(rotation, displayCutout) var insets = insetsCache[key] @@ -176,14 +233,7 @@ constructor( return getPrivacyChipBoundingRectForInsets(insets, dotWidth, chipWidth, isRtl) } - /** - * Calculate the distance from the left, right and top edges of the screen to the status bar - * content area. This differs from the content area rects in that these values can be used - * directly as padding. - * - * @param rotation the target rotation for which to calculate insets - */ - fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets = + override fun getStatusBarContentInsetsForRotation(@Rotation rotation: Int): Insets = traceSection(tag = "StatusBarContentInsetsProvider.getStatusBarContentInsetsForRotation") { val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation() val displayCutout = sysUICutout?.cutout @@ -202,31 +252,17 @@ constructor( rotation, sysUICutout, getResourcesForRotation(rotation, context), - key + key, ) Insets.of(area.left, area.top, /* right= */ width - area.right, /* bottom= */ 0) } - /** - * Calculate the insets for the status bar content in the device's current rotation - * - * @see getStatusBarContentAreaForRotation - */ - fun getStatusBarContentInsetsForCurrentRotation(): Insets { + override fun getStatusBarContentInsetsForCurrentRotation(): Insets { return getStatusBarContentInsetsForRotation(getExactRotation(context)) } - /** - * Calculates the area of the status bar contents invariant of the current device rotation, in - * the target rotation's coordinates - * - * @param rotation the rotation for which the bounds are required. This is an absolute value - * (i.e., ROTATION_NONE will always return the same bounds regardless of the context from - * which this method is called) - */ - @JvmOverloads - fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect { + override fun getStatusBarContentAreaForRotation(@Rotation rotation: Int): Rect { val sysUICutout = sysUICutoutProvider.cutoutInfoForCurrentDisplayAndRotation() val displayCutout = sysUICutout?.cutout val key = getCacheKey(rotation, displayCutout) @@ -235,12 +271,11 @@ constructor( rotation, sysUICutout, getResourcesForRotation(rotation, context), - key + key, ) } - /** Get the status bar content area for the given rotation, in absolute bounds */ - fun getStatusBarContentAreaForCurrentRotation(): Rect { + override fun getStatusBarContentAreaForCurrentRotation(): Rect { val rotation = getExactRotation(context) return getStatusBarContentAreaForRotation(rotation) } @@ -249,7 +284,7 @@ constructor( @Rotation targetRotation: Int, sysUICutout: SysUICutoutInformation?, rotatedResources: Resources, - key: CacheKey + key: CacheKey, ): Rect { return getCalculatedAreaForRotation(sysUICutout, targetRotation, rotatedResources).also { insetsCache.put(key, it) @@ -259,7 +294,7 @@ constructor( private fun getCalculatedAreaForRotation( sysUICutout: SysUICutoutInformation?, @Rotation targetRotation: Int, - rotatedResources: Resources + rotatedResources: Resources, ): Rect { val currentRotation = getExactRotation(context) @@ -299,7 +334,7 @@ constructor( configurationController.isLayoutRtl, dotWidth, bottomAlignedMargin, - statusBarContentHeight + statusBarContentHeight, ) } @@ -349,7 +384,7 @@ constructor( return resources.getDimensionPixelSize(dimenRes) } - fun getStatusBarPaddingTop(@Rotation rotation: Int? = null): Int { + override fun getStatusBarPaddingTop(@Rotation rotation: Int?): Int { val res = rotation?.let { it -> getResourcesForRotation(it, context) } ?: context.resources return res.getDimensionPixelSize(R.dimen.status_bar_padding_top) } @@ -364,13 +399,13 @@ constructor( CacheKey( rotation = rotation, displaySize = Rect(context.resources.configuration.windowConfiguration.maxBounds), - displayCutout = displayCutout + displayCutout = displayCutout, ) private data class CacheKey( @Rotation val rotation: Int, val displaySize: Rect, - val displayCutout: DisplayCutout? + val displayCutout: DisplayCutout?, ) } @@ -395,21 +430,21 @@ fun getPrivacyChipBoundingRectForInsets( contentRect: Rect, dotWidth: Int, chipWidth: Int, - isRtl: Boolean + isRtl: Boolean, ): Rect { return if (isRtl) { Rect( contentRect.left - dotWidth, contentRect.top, contentRect.left + chipWidth, - contentRect.bottom + contentRect.bottom, ) } else { Rect( contentRect.right - chipWidth, contentRect.top, contentRect.right + dotWidth, - contentRect.bottom + contentRect.bottom, ) } } @@ -443,7 +478,7 @@ fun calculateInsetsForRotationWithRotatedResources( isRtl: Boolean, dotWidth: Int, bottomAlignedMargin: Int, - statusBarContentHeight: Int + statusBarContentHeight: Int, ): Rect { /* TODO: Check if this is ever used for devices with no rounded corners @@ -467,7 +502,7 @@ fun calculateInsetsForRotationWithRotatedResources( targetRotation, currentRotation, bottomAlignedMargin, - statusBarContentHeight + statusBarContentHeight, ) } @@ -503,7 +538,7 @@ private fun getStatusBarContentBounds( @Rotation targetRotation: Int, @Rotation currentRotation: Int, bottomAlignedMargin: Int, - statusBarContentHeight: Int + statusBarContentHeight: Int, ): Rect { val insetTop = getInsetTop(bottomAlignedMargin, statusBarContentHeight, sbHeight) @@ -597,7 +632,7 @@ private val DisplayCutout.boundingRectsLeftRightTop private fun getInsetTop( bottomAlignedMargin: Int, statusBarContentHeight: Int, - statusBarHeight: Int + statusBarHeight: Int, ): Int { val bottomAlignmentEnabled = bottomAlignedMargin >= 0 if (!bottomAlignmentEnabled) { @@ -610,7 +645,7 @@ private fun getInsetTop( private fun sbRect( @Rotation relativeRotation: Int, sbHeight: Int, - displaySize: Pair<Int, Int> + displaySize: Pair<Int, Int>, ): Rect { val w = displaySize.first val h = displaySize.second @@ -626,7 +661,7 @@ private fun shareShortEdge( sbRect: Rect, cutoutRect: Rect, currentWidth: Int, - currentHeight: Int + currentHeight: Int, ): Boolean { if (currentWidth < currentHeight) { // Check top/bottom edges by extending the width of the display cutout rect and checking diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java index 17bd53869ee5..74c6e72d3400 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java @@ -47,7 +47,6 @@ import androidx.annotation.VisibleForTesting; import com.android.internal.util.LatencyTracker; import com.android.internal.widget.LockPatternUtils; -import com.android.keyguard.AuthKeyguardMessageArea; import com.android.keyguard.KeyguardMessageAreaController; import com.android.keyguard.KeyguardSecurityModel; import com.android.keyguard.KeyguardUpdateMonitor; @@ -68,7 +67,6 @@ import com.android.systemui.bouncer.util.BouncerTestUtilsKt; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor; import com.android.systemui.dock.DockManager; import com.android.systemui.dreams.DreamOverlayStateController; import com.android.systemui.keyguard.DismissCallbackRegistry; @@ -77,9 +75,7 @@ import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInte import com.android.systemui.keyguard.domain.interactor.KeyguardDismissTransitionInteractor; import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor; import com.android.systemui.keyguard.shared.model.DismissAction; -import com.android.systemui.keyguard.shared.model.Edge; import com.android.systemui.keyguard.shared.model.KeyguardDone; -import com.android.systemui.keyguard.shared.model.KeyguardState; import com.android.systemui.keyguard.shared.model.TransitionStep; import com.android.systemui.navigationbar.NavigationModeController; import com.android.systemui.navigationbar.TaskbarDelegate; @@ -165,7 +161,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb private final DreamOverlayStateController mDreamOverlayStateController; @Nullable private final FoldAodAnimationController mFoldAodAnimationController; - KeyguardMessageAreaController<AuthKeyguardMessageArea> mKeyguardMessageAreaController; private final PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor; private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; private final AlternateBouncerInteractor mAlternateBouncerInteractor; @@ -463,11 +458,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb onPanelExpansionChanged(currentState); } mNotificationContainer = notificationContainer; - if (!DeviceEntryUdfpsRefactor.isEnabled()) { - mKeyguardMessageAreaController = mKeyguardMessageAreaFactory.create( - centralSurfaces.getKeyguardMessageArea()); - } - mCentralSurfacesRegistered = true; registerListeners(); @@ -518,24 +508,11 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mListenForCanShowAlternateBouncer.cancel(null); } mListenForCanShowAlternateBouncer = null; - if (!DeviceEntryUdfpsRefactor.isEnabled()) { - mListenForAlternateBouncerTransitionSteps = mJavaAdapter.alwaysCollectFlow( - mKeyguardTransitionInteractor - .transition(Edge.create(KeyguardState.ALTERNATE_BOUNCER)), - this::consumeFromAlternateBouncerTransitionSteps - ); - - mListenForKeyguardAuthenticatedBiometricsHandled = mJavaAdapter.alwaysCollectFlow( - mPrimaryBouncerInteractor.getKeyguardAuthenticatedBiometricsHandled(), - this::consumeKeyguardAuthenticatedBiometricsHandled - ); - } else { - // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot. - mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow( - mAlternateBouncerInteractor.getCanShowAlternateBouncer(), - this::consumeCanShowAlternateBouncer - ); - } + // Collector that keeps the AlternateBouncerInteractor#canShowAlternateBouncer flow hot. + mListenForCanShowAlternateBouncer = mJavaAdapter.alwaysCollectFlow( + mAlternateBouncerInteractor.getCanShowAlternateBouncer(), + this::consumeCanShowAlternateBouncer + ); if (KeyguardWmStateRefactor.isEnabled()) { // Show the keyguard views whenever we've told WM that the lockscreen is visible. @@ -792,21 +769,12 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb return; } - if (DeviceEntryUdfpsRefactor.isEnabled()) { - if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) { - Log.d(TAG, "showBouncer:alternateBouncer.forceShow()"); - mAlternateBouncerInteractor.forceShow(); - updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); - } else { - showPrimaryBouncer(scrimmed); - } - return; - } - - if (!mAlternateBouncerInteractor.show()) { - showPrimaryBouncer(scrimmed); - } else { + if (mAlternateBouncerInteractor.canShowAlternateBouncerForFingerprint()) { + Log.d(TAG, "showBouncer:alternateBouncer.forceShow()"); + mAlternateBouncerInteractor.forceShow(); updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); + } else { + showPrimaryBouncer(scrimmed); } } @@ -921,13 +889,9 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb mKeyguardGoneCancelAction = null; } - if (DeviceEntryUdfpsRefactor.isEnabled()) { - Log.d(TAG, "dismissWithAction:alternateBouncer.forceShow()"); - mAlternateBouncerInteractor.forceShow(); - updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); - } else { - updateAlternateBouncerShowing(mAlternateBouncerInteractor.show()); - } + Log.d(TAG, "dismissWithAction:alternateBouncer.forceShow()"); + mAlternateBouncerInteractor.forceShow(); + updateAlternateBouncerShowing(mAlternateBouncerInteractor.isVisibleState()); setKeyguardMessage(message, null, null); return; } @@ -1033,11 +997,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } final boolean isShowingAlternateBouncer = mAlternateBouncerInteractor.isVisibleState(); - if (mKeyguardMessageAreaController != null) { - DeviceEntryUdfpsRefactor.assertInLegacyMode(); - mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer); - mKeyguardMessageAreaController.setMessage(""); - } if (!SceneContainerFlag.isEnabled()) { mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer); } @@ -1646,12 +1605,7 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb /** Display security message to relevant KeyguardMessageArea. */ public void setKeyguardMessage(String message, ColorStateList colorState, BiometricSourceType biometricSourceType) { - if (mAlternateBouncerInteractor.isVisibleState()) { - if (mKeyguardMessageAreaController != null) { - DeviceEntryUdfpsRefactor.assertInLegacyMode(); - mKeyguardMessageAreaController.setMessage(message, biometricSourceType); - } - } else { + if (!mAlternateBouncerInteractor.isVisibleState()) { mPrimaryBouncerInteractor.showMessage(message, colorState); } } @@ -1778,66 +1732,6 @@ public class StatusBarKeyguardViewManager implements RemoteInputController.Callb } } - /** - * An opportunity for the AlternateBouncer to handle the touch instead of sending - * the touch to NPVC child views. - * @return true if the alternate bouncer should consime the touch and prevent it from - * going to its child views - */ - public boolean dispatchTouchEvent(MotionEvent event) { - if (shouldInterceptTouchEvent(event) - && !mUdfpsOverlayInteractor.isTouchWithinUdfpsArea(event)) { - onTouch(event); - } - return shouldInterceptTouchEvent(event); - } - - /** - * Whether the touch should be intercepted by the AlternateBouncer before going to the - * notification shade's child views. - */ - public boolean shouldInterceptTouchEvent(MotionEvent event) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - return false; - } - return mAlternateBouncerInteractor.isVisibleState(); - } - - /** - * For any touches on the NPVC, show the primary bouncer if the alternate bouncer is currently - * showing. - */ - public boolean onTouch(MotionEvent event) { - if (DeviceEntryUdfpsRefactor.isEnabled()) { - return false; - } - - boolean handleTouch = shouldInterceptTouchEvent(event); - if (handleTouch) { - final boolean actionDown = event.getActionMasked() == MotionEvent.ACTION_DOWN; - final boolean actionDownThenUp = mAlternateBouncerInteractor.getReceivedDownTouch() - && event.getActionMasked() == MotionEvent.ACTION_UP; - final boolean udfpsOverlayWillForwardEventsOutsideNotificationShade = - mKeyguardUpdateManager.isUdfpsEnrolled(); - final boolean actionOutsideShouldDismissAlternateBouncer = - event.getActionMasked() == MotionEvent.ACTION_OUTSIDE - && !udfpsOverlayWillForwardEventsOutsideNotificationShade; - if (actionDown) { - mAlternateBouncerInteractor.setReceivedDownTouch(true); - } else if ((actionDownThenUp || actionOutsideShouldDismissAlternateBouncer) - && mAlternateBouncerInteractor.hasAlternateBouncerShownWithMinTime()) { - showPrimaryBouncer(true); - } - } - - // Forward NPVC touches to callbacks in case they want to respond to touches - for (KeyguardViewManagerCallback callback: mCallbacks) { - callback.onTouch(event); - } - - return handleTouch; - } - /** Update keyguard position based on a tapped X coordinate. */ public void updateKeyguardPosition(float x) { mPrimaryBouncerInteractor.setKeyguardPosition(x); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt index 5f864e5dc53a..09e191dd1911 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.kt @@ -18,10 +18,12 @@ package com.android.systemui.statusbar.phone.dagger import android.view.Display import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Default import com.android.systemui.statusbar.CommandQueue import com.android.systemui.statusbar.core.CommandQueueInitializer import com.android.systemui.statusbar.core.MultiDisplayStatusBarInitializerStore +import com.android.systemui.statusbar.core.MultiDisplayStatusBarStarter import com.android.systemui.statusbar.core.SingleDisplayStatusBarInitializerStore import com.android.systemui.statusbar.core.StatusBarConnectedDisplays import com.android.systemui.statusbar.core.StatusBarInitializer @@ -29,7 +31,9 @@ import com.android.systemui.statusbar.core.StatusBarInitializerImpl import com.android.systemui.statusbar.core.StatusBarInitializerStore import com.android.systemui.statusbar.core.StatusBarOrchestrator import com.android.systemui.statusbar.core.StatusBarSimpleFragment +import com.android.systemui.statusbar.data.repository.StatusBarModeRepositoryStore import com.android.systemui.statusbar.phone.CentralSurfacesCommandQueueCallbacks +import com.android.systemui.statusbar.window.StatusBarWindowControllerStore import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStore import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStateRepositoryStoreImpl import dagger.Binds @@ -38,6 +42,7 @@ import dagger.Module import dagger.Provides import dagger.multibindings.ClassKey import dagger.multibindings.IntoMap +import kotlinx.coroutines.CoroutineScope /** Similar in purpose to [StatusBarModule], but scoped only to phones */ @Module @@ -58,24 +63,56 @@ interface StatusBarPhoneModule { implFactory: StatusBarInitializerImpl.Factory ): StatusBarInitializer.Factory - /** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */ - @Binds - @IntoMap - @ClassKey(StatusBarInitializerImpl::class) - fun bindStatusBarInitializer(@Default impl: StatusBarInitializerImpl): CoreStartable - @Binds fun statusBarInitializer(@Default impl: StatusBarInitializerImpl): StatusBarInitializer companion object { + /** Binds {@link StatusBarInitializer} as a {@link CoreStartable}. */ + @Provides + @SysUISingleton + @IntoMap + @ClassKey(StatusBarInitializer::class) + fun bindStatusBarInitializer( + @Default defaultInitializerLazy: Lazy<StatusBarInitializerImpl> + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + // Will be started through MultiDisplayStatusBarStarter + CoreStartable.NOP + } else { + defaultInitializerLazy.get() + } + } + // Dagger doesn't support providing AssistedInject types, without a qualifier. Using the // Default qualifier for this reason. @Default @Provides @SysUISingleton fun statusBarInitializerImpl( - implFactory: StatusBarInitializerImpl.Factory + implFactory: StatusBarInitializerImpl.Factory, + statusBarWindowControllerStore: StatusBarWindowControllerStore, ): StatusBarInitializerImpl { - return implFactory.create(displayId = Display.DEFAULT_DISPLAY) + return implFactory.create(statusBarWindowControllerStore.defaultDisplay) + } + + @Provides + @SysUISingleton + @Default // Dagger does not support providing @AssistedInject types without a qualifier + fun orchestrator( + @Background backgroundApplicationScope: CoroutineScope, + statusBarWindowStateRepositoryStore: StatusBarWindowStateRepositoryStore, + statusBarModeRepositoryStore: StatusBarModeRepositoryStore, + initializerStore: StatusBarInitializerStore, + statusBarWindowControllerStore: StatusBarWindowControllerStore, + statusBarOrchestratorFactory: StatusBarOrchestrator.Factory, + ): StatusBarOrchestrator { + return statusBarOrchestratorFactory.create( + Display.DEFAULT_DISPLAY, + backgroundApplicationScope, + statusBarWindowStateRepositoryStore.defaultDisplay, + statusBarModeRepositoryStore.defaultDisplay, + initializerStore.defaultDisplay, + statusBarWindowControllerStore.defaultDisplay, + ) } @Provides @@ -83,11 +120,29 @@ interface StatusBarPhoneModule { @IntoMap @ClassKey(StatusBarOrchestrator::class) fun orchestratorCoreStartable( - orchestratorLazy: Lazy<StatusBarOrchestrator> + @Default orchestratorLazy: Lazy<StatusBarOrchestrator> ): CoreStartable { - return if (StatusBarSimpleFragment.isEnabled) { + return if (StatusBarConnectedDisplays.isEnabled) { + // Will be started through MultiDisplayStatusBarStarter + CoreStartable.NOP + } else if (StatusBarSimpleFragment.isEnabled) { orchestratorLazy.get() } else { + // Will be started through CentralSurfacesImpl + CoreStartable.NOP + } + } + + @Provides + @SysUISingleton + @IntoMap + @ClassKey(MultiDisplayStatusBarStarter::class) + fun multiDisplayStarter( + multiDisplayStatusBarStarterLazy: Lazy<MultiDisplayStatusBarStarter> + ): CoreStartable { + return if (StatusBarConnectedDisplays.isEnabled) { + multiDisplayStatusBarStarterLazy.get() + } else { CoreStartable.NOP } } 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 c258095510c6..d868519ee9e2 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 @@ -29,6 +29,7 @@ import android.telephony.SubscriptionManager; import android.util.ArrayMap; import android.util.IndentingPrintWriter; import android.util.SparseArray; +import android.view.Display; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -55,7 +56,7 @@ import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameView; import com.android.systemui.statusbar.OperatorNameViewController; import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.chips.ron.shared.StatusBarRonChips; +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.core.StatusBarSimpleFragment; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationCallback; @@ -333,7 +334,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue @Override public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - mDumpManager.registerDumpable(getClass().getSimpleName(), this); + mDumpManager.registerDumpable(getDumpableName(), this); mStatusBarFragmentComponent = mStatusBarFragmentComponentFactory.create( (PhoneStatusBarView) getView()); mStatusBarFragmentComponent.init(); @@ -374,6 +375,14 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue mStatusBar, mCollapsedStatusBarViewModel, mStatusBarVisibilityChangeListener); } + private String getDumpableName() { + if (getContext().getDisplayId() == Display.DEFAULT_DISPLAY) { + return getClass().getSimpleName(); + } else { + return getClass().getSimpleName() + getContext().getDisplayId(); + } + } + @Override public void onCameraLaunchGestureDetected(int source) { mWaitingForWindowStateChangeAfterCameraLaunch = true; @@ -470,7 +479,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue startable.stop(); mStartableStates.put(startable, Startable.State.STOPPED); } - mDumpManager.unregisterDumpable(getClass().getSimpleName()); + mDumpManager.unregisterDumpable(getDumpableName()); if (mNicBindingDisposable != null) { mNicBindingDisposable.dispose(); mNicBindingDisposable = null; @@ -486,7 +495,12 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue NotificationIconContainer notificationIcons = notificationIconArea.requireViewById(R.id.notificationIcons); mNotificationIconAreaInner = notificationIcons; - mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons); + if (getContext().getDisplayId() == Display.DEFAULT_DISPLAY) { + //TODO(b/369337701): implement notification icons for all displays. + // Currently if we try to bind for all displays, there is a crash, because the same + // notification icon view can't have multiple parents. + mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons); + } if (!StatusBarSimpleFragment.isEnabled()) { updateNotificationIconAreaAndOngoingActivityChip(/* animate= */ false); @@ -642,7 +656,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } boolean showSecondaryOngoingActivityChip = Flags.statusBarScreenSharingChips() - && StatusBarRonChips.isEnabled() + && StatusBarNotifChips.isEnabled() && mHasSecondaryOngoingActivity; return new StatusBarVisibilityModel( @@ -684,7 +698,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue boolean showSecondaryOngoingActivityChip = // Secondary chips are only supported when RONs are enabled. - StatusBarRonChips.isEnabled() + StatusBarNotifChips.isEnabled() && visibilityModel.getShowSecondaryOngoingActivityChip() && !disableNotifications; if (showSecondaryOngoingActivityChip) { @@ -811,7 +825,7 @@ public class CollapsedStatusBarFragment extends Fragment implements CommandQueue } private void showSecondaryOngoingActivityChip(boolean animate) { - StatusBarRonChips.assertInNewMode(); + StatusBarNotifChips.assertInNewMode(); StatusBarSimpleFragment.assertInLegacyMode(); animateShow(mSecondaryOngoingActivityChip, animate); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt index bad6f80c3735..694a5e529ec4 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconsViewModel.kt @@ -16,8 +16,8 @@ package com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel -import com.android.app.tracing.coroutines.createCoroutineTracingContext import androidx.annotation.VisibleForTesting +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.flags.FeatureFlagsClassic @@ -29,8 +29,8 @@ import com.android.systemui.statusbar.pipeline.mobile.ui.VerboseMobileViewLogger import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants import javax.inject.Inject -import kotlinx.coroutines.CoroutineScope import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.cancel @@ -116,7 +116,7 @@ constructor( private fun createViewModel(subId: Int): Pair<MobileIconViewModel, CoroutineScope> { // Create a child scope so we can cancel it - val vmScope = scope.createChildScope(createCoroutineTracingContext("MobileIconViewModel")) + val vmScope = scope.createChildScope(newTracingContext("MobileIconViewModel")) val vm = MobileIconViewModel( subId, diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt index 9c168be0693f..3a07d9b6beaf 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ui/binder/CollapsedStatusBarViewBinder.kt @@ -27,6 +27,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips import com.android.systemui.statusbar.chips.ui.binder.OngoingActivityChipBinder import com.android.systemui.statusbar.chips.ui.model.OngoingActivityChipModel import com.android.systemui.statusbar.core.StatusBarSimpleFragment @@ -83,7 +84,7 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } - if (Flags.statusBarScreenSharingChips() && !Flags.statusBarRonChips()) { + if (Flags.statusBarScreenSharingChips() && !StatusBarNotifChips.isEnabled) { val primaryChipView: View = view.requireViewById(R.id.ongoing_activity_chip_primary) launch { @@ -119,7 +120,7 @@ class CollapsedStatusBarViewBinderImpl @Inject constructor() : CollapsedStatusBa } } - if (Flags.statusBarScreenSharingChips() && Flags.statusBarRonChips()) { + if (Flags.statusBarScreenSharingChips() && StatusBarNotifChips.isEnabled) { val primaryChipView: View = view.requireViewById(R.id.ongoing_activity_chip_primary) val secondaryChipView: View = diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt index 7d0dadcf8c6e..7a88dcd92b88 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowControllerStore.kt @@ -17,78 +17,40 @@ package com.android.systemui.statusbar.window import android.content.Context -import android.view.Display import android.view.WindowManager import com.android.app.viewcapture.ViewCaptureAwareWindowManager -import com.android.systemui.CoreStartable import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.display.data.repository.DisplayRepository import com.android.systemui.display.data.repository.DisplayWindowPropertiesRepository +import com.android.systemui.display.data.repository.PerDisplayStore +import com.android.systemui.display.data.repository.PerDisplayStoreImpl +import com.android.systemui.display.data.repository.SingleDisplayStore import com.android.systemui.statusbar.core.StatusBarConnectedDisplays -import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject -import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.launch /** Store that allows to retrieve per display instances of [StatusBarWindowController]. */ -interface StatusBarWindowControllerStore { - /** - * The instance for the default/main display of the device. For example, on a phone or a tablet, - * the default display is the internal/built-in display of the device. - * - * Note that the id of the default display is [Display.DEFAULT_DISPLAY]. - */ - val defaultDisplay: StatusBarWindowController - - /** - * Returns an instance for a specific display id. - * - * @throws IllegalArgumentException if [displayId] doesn't match the id of any existing - * displays. - */ - fun forDisplay(displayId: Int): StatusBarWindowController -} +interface StatusBarWindowControllerStore : PerDisplayStore<StatusBarWindowController> @SysUISingleton class MultiDisplayStatusBarWindowControllerStore @Inject constructor( - @Background private val backgroundApplicationScope: CoroutineScope, + @Background backgroundApplicationScope: CoroutineScope, private val controllerFactory: StatusBarWindowController.Factory, private val displayWindowPropertiesRepository: DisplayWindowPropertiesRepository, private val viewCaptureAwareWindowManagerFactory: ViewCaptureAwareWindowManager.Factory, - private val displayRepository: DisplayRepository, -) : StatusBarWindowControllerStore, CoreStartable { + displayRepository: DisplayRepository, +) : + StatusBarWindowControllerStore, + PerDisplayStoreImpl<StatusBarWindowController>(backgroundApplicationScope, displayRepository) { init { StatusBarConnectedDisplays.assertInNewMode() } - private val perDisplayControllers = ConcurrentHashMap<Int, StatusBarWindowController>() - - override fun start() { - backgroundApplicationScope.launch(CoroutineName("StatusBarWindowController#start")) { - displayRepository.displayRemovalEvent.collect { displayId -> - perDisplayControllers.remove(displayId) - } - } - } - - override val defaultDisplay: StatusBarWindowController - get() = forDisplay(Display.DEFAULT_DISPLAY) - - override fun forDisplay(displayId: Int): StatusBarWindowController { - if (displayRepository.getDisplay(displayId) == null) { - throw IllegalArgumentException("Display with id $displayId doesn't exist.") - } - return perDisplayControllers.computeIfAbsent(displayId) { - createControllerForDisplay(displayId) - } - } - - private fun createControllerForDisplay(displayId: Int): StatusBarWindowController { + override fun createInstanceForDisplay(displayId: Int): StatusBarWindowController { val statusBarDisplayContext = displayWindowPropertiesRepository.get( displayId = displayId, @@ -101,6 +63,8 @@ constructor( viewCaptureAwareWindowManager, ) } + + override val instanceClass = StatusBarWindowController::class.java } @SysUISingleton @@ -110,16 +74,13 @@ constructor( context: Context, viewCaptureAwareWindowManager: ViewCaptureAwareWindowManager, factory: StatusBarWindowControllerImpl.Factory, -) : StatusBarWindowControllerStore { +) : + StatusBarWindowControllerStore, + PerDisplayStore<StatusBarWindowController> by SingleDisplayStore( + factory.create(context, viewCaptureAwareWindowManager) + ) { init { StatusBarConnectedDisplays.assertInLegacyMode() } - - private val controller: StatusBarWindowController = - factory.create(context, viewCaptureAwareWindowManager) - - override val defaultDisplay = controller - - override fun forDisplay(displayId: Int) = controller } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt index 75c66f234bdc..90c005139c56 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/GestureTutorialScreen.kt @@ -67,7 +67,7 @@ class DistanceBasedGestureRecognizerProvider( val distanceThresholdPx = resources.getDimensionPixelSize( com.android.internal.R.dimen.system_gestures_distance_threshold - ) + ) * 5 return remember(distanceThresholdPx) { recognizerFactory(distanceThresholdPx, gestureStateChangedCallback) } @@ -77,7 +77,8 @@ class DistanceBasedGestureRecognizerProvider( fun GestureState.toTutorialActionState(): TutorialActionState { return when (this) { NotStarted -> TutorialActionState.NotStarted - is InProgress -> TutorialActionState.InProgress(progress) + // progress is disabled for now as views are not ready to handle varying progress + is InProgress -> TutorialActionState.InProgress(0f) Finished -> TutorialActionState.Finished } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt index 56e97a357d67..80f800390852 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/BackGestureRecognizer.kt @@ -16,10 +16,14 @@ package com.android.systemui.touchpad.tutorial.ui.gesture +import android.util.MathUtils import android.view.MotionEvent import kotlin.math.abs -/** Recognizes touchpad back gesture, that is three fingers swiping left or right */ +/** + * Recognizes touchpad back gesture, that is - using three fingers on touchpad - swiping left or + * right. + */ class BackGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer { private val distanceTracker = DistanceTracker() @@ -36,7 +40,7 @@ class BackGestureRecognizer(private val gestureDistanceThresholdPx: Int) : Gestu gestureStateChangedCallback, gestureState, isFinished = { abs(it.deltaX) >= gestureDistanceThresholdPx }, - progress = { 0f }, + progress = { MathUtils.saturate(abs(it.deltaX / gestureDistanceThresholdPx)) }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt index 3db9d7ccc8f7..2b84a4c50613 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/HomeGestureRecognizer.kt @@ -16,9 +16,10 @@ package com.android.systemui.touchpad.tutorial.ui.gesture +import android.util.MathUtils import android.view.MotionEvent -/** Recognizes touchpad home gesture, that is three fingers swiping up */ +/** Recognizes touchpad home gesture, that is - using three fingers on touchpad - swiping up. */ class HomeGestureRecognizer(private val gestureDistanceThresholdPx: Int) : GestureRecognizer { private val distanceTracker = DistanceTracker() @@ -35,7 +36,7 @@ class HomeGestureRecognizer(private val gestureDistanceThresholdPx: Int) : Gestu gestureStateChangedCallback, gestureState, isFinished = { -it.deltaY >= gestureDistanceThresholdPx }, - progress = { 0f }, + progress = { MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx) }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt index a194ad6a8016..69b7c5edd750 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/gesture/RecentAppsGestureRecognizer.kt @@ -16,13 +16,14 @@ package com.android.systemui.touchpad.tutorial.ui.gesture +import android.util.MathUtils import android.view.MotionEvent import kotlin.math.abs /** - * Recognizes apps gesture completion. That is - using three fingers on touchpad - swipe up over - * some distance threshold and then slow down gesture before fingers are lifted. Implementation is - * based on [com.android.quickstep.util.TriggerSwipeUpTouchTracker] + * Recognizes recent apps gesture, that is - using three fingers on touchpad - swipe up over some + * distance threshold and then slow down gesture before fingers are lifted. Implementation is based + * on [com.android.quickstep.util.TriggerSwipeUpTouchTracker] */ class RecentAppsGestureRecognizer( private val gestureDistanceThresholdPx: Int, @@ -49,7 +50,7 @@ class RecentAppsGestureRecognizer( -state.deltaY >= gestureDistanceThresholdPx && abs(velocityTracker.calculateVelocity().value) <= velocityThresholdPxPerMs }, - progress = { 0f }, + progress = { MathUtils.saturate(-it.deltaY / gestureDistanceThresholdPx) }, ) } } diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java index 213581787f73..d0817d736f07 100644 --- a/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java +++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerActivity.java @@ -61,7 +61,6 @@ public class TunerActivity extends Activity implements protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setTheme(androidx.appcompat.R.style.Theme_AppCompat_DayNight); getWindow().addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); requestWindowFeature(Window.FEATURE_NO_TITLE); diff --git a/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt index de9231856e2c..d87607dec8a1 100644 --- a/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt +++ b/packages/SystemUI/src/com/android/systemui/util/drawable/DrawableSize.kt @@ -11,6 +11,7 @@ import android.graphics.drawable.AnimatedStateListDrawable import android.graphics.drawable.AnimatedVectorDrawable import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable +import android.graphics.drawable.LayerDrawable import android.util.Log import androidx.annotation.Px import com.android.app.tracing.traceSection @@ -22,36 +23,35 @@ class DrawableSize { const val TAG = "SysUiDrawableSize" /** - * Downscales passed Drawable to set maximum width and height. This will only - * be done for Drawables that can be downscaled non-destructively - e.g. animated - * and stateful drawables will no be downscaled. + * Downscales passed Drawable to set maximum width and height. This will only be done for + * Drawables that can be downscaled non-destructively - e.g. animated drawables, stateful + * drawables, and drawables with mixed-type layers will not be downscaled. * - * Downscaling will keep the aspect ratio. - * This method will not touch drawables that already fit into size specification. + * Downscaling will keep the aspect ratio. This method will not touch drawables that already + * fit into size specification. * * @param resources Resources on which to base the density of resized drawable. * @param drawable Drawable to downscale. * @param maxWidth Maximum width of the downscaled drawable. * @param maxHeight Maximum height of the downscaled drawable. - * * @return returns downscaled drawable if it's possible to downscale it or original if it's - * not. + * not. */ @JvmStatic fun downscaleToSize( res: Resources, drawable: Drawable, @Px maxWidth: Int, - @Px maxHeight: Int + @Px maxHeight: Int, ): Drawable { traceSection("DrawableSize#downscaleToSize") { // Bitmap drawables can contain big bitmaps as their content while sneaking it past // us using density scaling. Inspect inside the Bitmap drawables for actual bitmap // size for those. - val originalWidth = (drawable as? BitmapDrawable)?.bitmap?.width - ?: drawable.intrinsicWidth - val originalHeight = (drawable as? BitmapDrawable)?.bitmap?.height - ?: drawable.intrinsicHeight + val originalWidth = + (drawable as? BitmapDrawable)?.bitmap?.width ?: drawable.intrinsicWidth + val originalHeight = + (drawable as? BitmapDrawable)?.bitmap?.height ?: drawable.intrinsicHeight // Don't touch drawable if we can't resolve sizes for whatever reason. if (originalWidth <= 0 || originalHeight <= 0) { @@ -61,14 +61,18 @@ class DrawableSize { // Do not touch drawables that are already within bounds. if (originalWidth < maxWidth && originalHeight < maxHeight) { if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Not resizing $originalWidth x $originalHeight" + " " + - "to $maxWidth x $maxHeight") + Log.d( + TAG, + "Not resizing $originalWidth x $originalHeight" + + " " + + "to $maxWidth x $maxHeight", + ) } return drawable } - if (!isSimpleBitmap(drawable)) { + if (isComplicatedBitmap(drawable)) { return drawable } @@ -80,19 +84,25 @@ class DrawableSize { val height = (originalHeight * scale).toInt() if (width <= 0 || height <= 0) { - Log.w(TAG, "Attempted to resize ${drawable.javaClass.simpleName} " + - "from $originalWidth x $originalHeight to invalid $width x $height.") + Log.w( + TAG, + "Attempted to resize ${drawable.javaClass.simpleName} " + + "from $originalWidth x $originalHeight to invalid $width x $height.", + ) return drawable } if (Log.isLoggable(TAG, Log.DEBUG)) { - Log.d(TAG, "Resizing large drawable (${drawable.javaClass.simpleName}) " + - "from $originalWidth x $originalHeight to $width x $height") + Log.d( + TAG, + "Resizing large drawable (${drawable.javaClass.simpleName}) " + + "from $originalWidth x $originalHeight to $width x $height", + ) } // We want to keep existing config if it's more efficient than 32-bit RGB. - val config = (drawable as? BitmapDrawable)?.bitmap?.config - ?: Bitmap.Config.ARGB_8888 + val config = + (drawable as? BitmapDrawable)?.bitmap?.config ?: Bitmap.Config.ARGB_8888 val scaledDrawableBitmap = Bitmap.createBitmap(width, height, config) val canvas = Canvas(scaledDrawableBitmap) @@ -105,8 +115,8 @@ class DrawableSize { } } - private fun isSimpleBitmap(drawable: Drawable): Boolean { - return !(drawable.isStateful || isAnimated(drawable)) + private fun isComplicatedBitmap(drawable: Drawable): Boolean { + return drawable.isStateful || isAnimated(drawable) || hasComplicatedLayers(drawable) } private fun isAnimated(drawable: Drawable): Boolean { @@ -119,5 +129,30 @@ class DrawableSize { drawable is AnimatedStateListDrawable || drawable is AnimatedVectorDrawable } + + private fun hasComplicatedLayers(drawable: Drawable): Boolean { + if (drawable !is LayerDrawable) { + return false + } + if (drawable.numberOfLayers == 1) { + return false + } + + val firstLayerType = drawable.getDrawable(0).javaClass + for (i in 1..<drawable.numberOfLayers) { + val layer = drawable.getDrawable(i) + if (layer.javaClass != firstLayerType) { + // If different layers have different drawable types, we shouldn't scale it down + // because we may lose the level information if one of the layers is a bitmap + // and another layer is a level-list. See b/244282477. + return true + } + if (isComplicatedBitmap(layer)) { + return true + } + } + + return false + } } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt index 2af84c7e46f0..579af73236f3 100644 --- a/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt +++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/GlobalCoroutinesModule.kt @@ -16,10 +16,9 @@ package com.android.systemui.util.kotlin -import com.android.app.tracing.coroutines.createCoroutineTracingContext +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main -import com.android.systemui.dagger.qualifiers.Tracing import dagger.Module import dagger.Provides import javax.inject.Singleton @@ -27,7 +26,6 @@ import kotlin.coroutines.CoroutineContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi /** Providers for various application-wide coroutines-related constructs. */ @Module @@ -35,16 +33,15 @@ class GlobalCoroutinesModule { @Provides @Singleton @Application - fun applicationScope( - @Main dispatcherContext: CoroutineContext, - ): CoroutineScope = CoroutineScope(dispatcherContext + createCoroutineTracingContext("ApplicationScope")) + fun applicationScope(@Main dispatcherContext: CoroutineContext): CoroutineScope = + CoroutineScope(dispatcherContext + newTracingContext("ApplicationScope")) @Provides @Singleton @Main @Deprecated( "Use @Main CoroutineContext instead", - ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext") + ReplaceWith("mainCoroutineContext()", "kotlin.coroutines.CoroutineContext"), ) fun mainDispatcher(): CoroutineDispatcher = Dispatchers.Main.immediate diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt index 4d9aaa6dc6b0..ea8709f7d65c 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/SettingsProxy.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.util.settings -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.annotation.UserIdInt import android.content.ContentResolver import android.database.ContentObserver @@ -24,6 +23,7 @@ import android.provider.Settings.SettingNotFoundException import androidx.annotation.AnyThread import androidx.annotation.WorkerThread import com.android.app.tracing.TraceUtils.trace +import com.android.systemui.coroutines.newTracingContext import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -94,7 +94,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(name: String, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-A")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-A")).launch { registerContentObserverSync(getUriFor(name), settingsObserver) } @@ -109,9 +109,9 @@ interface SettingsProxy { fun registerContentObserverAsync( name: String, settingsObserver: ContentObserver, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-B")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-B")).launch { registerContentObserverSync(getUriFor(name), settingsObserver) registered.run() } @@ -144,7 +144,7 @@ interface SettingsProxy { */ @AnyThread fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-C")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-C")).launch { registerContentObserverSync(uri, settingsObserver) } @@ -159,9 +159,9 @@ interface SettingsProxy { fun registerContentObserverAsync( uri: Uri, settingsObserver: ContentObserver, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-D")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-D")).launch { registerContentObserverSync(uri, settingsObserver) registered.run() } @@ -175,7 +175,7 @@ interface SettingsProxy { fun registerContentObserverSync( name: String, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) /** @@ -204,9 +204,9 @@ interface SettingsProxy { fun registerContentObserverAsync( name: String, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-E")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-E")).launch { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) } @@ -222,9 +222,9 @@ interface SettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-F")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-F")).launch { registerContentObserverSync(getUriFor(name), notifyForDescendants, settingsObserver) registered.run() } @@ -273,9 +273,9 @@ interface SettingsProxy { fun registerContentObserverAsync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-G")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-G")).launch { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) } @@ -291,9 +291,9 @@ interface SettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-H")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-H")).launch { registerContentObserverSync(uri, notifyForDescendants, settingsObserver) registered.run() } @@ -330,7 +330,9 @@ interface SettingsProxy { */ @AnyThread fun unregisterContentObserverAsync(settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("SettingsProxy-I")).launch { unregisterContentObserver(settingsObserver) } + CoroutineScope(backgroundDispatcher + newTracingContext("SettingsProxy-I")).launch { + unregisterContentObserver(settingsObserver) + } /** * Look up a name in the database. diff --git a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt index c820c07b61b1..c5deca214e28 100644 --- a/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/util/settings/UserSettingsProxy.kt @@ -15,7 +15,6 @@ */ package com.android.systemui.util.settings -import com.android.app.tracing.coroutines.createCoroutineTracingContext import android.annotation.UserIdInt import android.annotation.WorkerThread import android.content.ContentResolver @@ -24,6 +23,7 @@ import android.net.Uri import android.os.UserHandle import android.provider.Settings.SettingNotFoundException import com.android.app.tracing.TraceUtils.trace +import com.android.systemui.coroutines.newTracingContext import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloat import com.android.systemui.util.settings.SettingsProxy.Companion.parseFloatOrThrow import com.android.systemui.util.settings.SettingsProxy.Companion.parseLongOrThrow @@ -79,7 +79,7 @@ interface UserSettingsProxy : SettingsProxy { } override fun registerContentObserverAsync(uri: Uri, settingsObserver: ContentObserver) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-A")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-A")).launch { registerContentObserverForUserSync(uri, settingsObserver, userId) } @@ -88,7 +88,7 @@ interface UserSettingsProxy : SettingsProxy { override fun registerContentObserverSync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } @@ -111,9 +111,9 @@ interface UserSettingsProxy : SettingsProxy { override fun registerContentObserverAsync( uri: Uri, notifyForDescendants: Boolean, - settingsObserver: ContentObserver + settingsObserver: ContentObserver, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-B")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-B")).launch { registerContentObserverForUserSync(uri, notifyForDescendants, settingsObserver, userId) } @@ -156,9 +156,9 @@ interface UserSettingsProxy : SettingsProxy { fun registerContentObserverForUserAsync( name: String, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-C")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-C")).launch { registerContentObserverForUserSync(getUriFor(name), settingsObserver, userHandle) } @@ -197,9 +197,9 @@ interface UserSettingsProxy : SettingsProxy { fun registerContentObserverForUserAsync( uri: Uri, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-D")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-D")).launch { registerContentObserverForUserSync(uri, settingsObserver, userHandle) } @@ -214,9 +214,9 @@ interface UserSettingsProxy : SettingsProxy { uri: Uri, settingsObserver: ContentObserver, userHandle: Int, - @WorkerThread registered: Runnable + @WorkerThread registered: Runnable, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-E")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-E")).launch { registerContentObserverForUserSync(uri, settingsObserver, userHandle) registered.run() } @@ -273,14 +273,14 @@ interface UserSettingsProxy : SettingsProxy { name: String, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) { - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-F")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-F")).launch { registerContentObserverForUserSync( getUriFor(name), notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } } @@ -337,14 +337,14 @@ interface UserSettingsProxy : SettingsProxy { uri: Uri, notifyForDescendants: Boolean, settingsObserver: ContentObserver, - userHandle: Int + userHandle: Int, ) = - CoroutineScope(backgroundDispatcher + createCoroutineTracingContext("UserSettingsProxy-G")).launch { + CoroutineScope(backgroundDispatcher + newTracingContext("UserSettingsProxy-G")).launch { registerContentObserverForUserSync( uri, notifyForDescendants, settingsObserver, - userHandle + userHandle, ) } diff --git a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt index 2dd0bdab93d1..5e0af634c786 100644 --- a/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/volume/dialog/settings/domain/VolumeDialogSettingsButtonInteractor.kt @@ -17,7 +17,7 @@ package com.android.systemui.volume.dialog.settings.domain import android.app.ActivityManager -import com.android.app.tracing.coroutines.flow.map +import com.android.app.tracing.coroutines.flow.flowName import com.android.systemui.statusbar.policy.DeviceProvisionedController import com.android.systemui.volume.Events import com.android.systemui.volume.dialog.dagger.scope.VolumeDialog @@ -30,6 +30,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.stateIn @VolumeDialogScope @@ -49,6 +50,7 @@ constructor( deviceProvisionedController.isCurrentUserSetup() && model.lockTaskModeState == ActivityManager.LOCK_TASK_MODE_NONE } + .flowName("VDSBI#isVisible") .stateIn(coroutineScope, SharingStarted.Eagerly, false) fun onButtonClicked() { diff --git a/packages/SystemUI/tests/res/drawable/layer_drawable_all_same_type.xml b/packages/SystemUI/tests/res/drawable/layer_drawable_all_same_type.xml new file mode 100644 index 000000000000..d9ea0b91abdf --- /dev/null +++ b/packages/SystemUI/tests/res/drawable/layer_drawable_all_same_type.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + <item android:drawable="@drawable/ic_brightness"/> + <item android:drawable="@drawable/ic_brightness"/> +</layer-list> diff --git a/packages/SystemUI/tests/res/drawable/layer_drawable_different_types.xml b/packages/SystemUI/tests/res/drawable/layer_drawable_different_types.xml new file mode 100644 index 000000000000..796de8f15f47 --- /dev/null +++ b/packages/SystemUI/tests/res/drawable/layer_drawable_different_types.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?><!-- + ~ Copyright (C) 2024 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. + --> +<layer-list xmlns:android="http://schemas.android.com/apk/res/android" > + <!-- dessert_flan is a PNG while ic_brightness is a level-list. --> + <item android:drawable="@drawable/dessert_flan"/> + <item android:drawable="@drawable/ic_brightness"/> +</layer-list> diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java deleted file mode 100644 index c51aa04fc7b6..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerBaseTest.java +++ /dev/null @@ -1,245 +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.keyguard; - -import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; -import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1; -import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE; -import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED; - -import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.content.Context; -import android.content.res.Resources; -import android.graphics.Point; -import android.graphics.Rect; -import android.graphics.drawable.AnimatedStateListDrawable; -import android.util.Pair; -import android.view.View; -import android.view.WindowManager; -import android.view.accessibility.AccessibilityManager; -import android.widget.ImageView; - -import com.android.systemui.Flags; -import com.android.systemui.SysuiTestCase; -import com.android.systemui.biometrics.AuthController; -import com.android.systemui.biometrics.AuthRippleController; -import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor; -import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor; -import com.android.systemui.doze.util.BurnInHelperKt; -import com.android.systemui.dump.DumpManager; -import com.android.systemui.flags.FakeFeatureFlags; -import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory; -import com.android.systemui.kosmos.KosmosJavaAdapter; -import com.android.systemui.plugins.FalsingManager; -import com.android.systemui.plugins.statusbar.StatusBarStateController; -import com.android.systemui.res.R; -import com.android.systemui.scene.shared.flag.SceneContainerFlag; -import com.android.systemui.statusbar.StatusBarState; -import com.android.systemui.statusbar.VibratorHelper; -import com.android.systemui.statusbar.policy.ConfigurationController; -import com.android.systemui.statusbar.policy.KeyguardStateController; -import com.android.systemui.util.concurrency.FakeExecutor; -import com.android.systemui.util.time.FakeSystemClock; - -import org.junit.After; -import org.junit.Before; -import org.mockito.Answers; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.MockitoAnnotations; -import org.mockito.MockitoSession; -import org.mockito.quality.Strictness; - -public class LegacyLockIconViewControllerBaseTest extends SysuiTestCase { - protected static final String UNLOCKED_LABEL = "unlocked"; - protected static final String LOCKED_LABEL = "locked"; - protected static final int PADDING = 10; - - protected MockitoSession mStaticMockSession; - - protected final KosmosJavaAdapter mKosmos = new KosmosJavaAdapter(this); - protected @Mock DeviceEntryInteractor mDeviceEntryInteractor; - protected @Mock LockIconView mLockIconView; - protected @Mock ImageView mLockIcon; - protected @Mock AnimatedStateListDrawable mIconDrawable; - protected @Mock Context mContext; - protected @Mock Resources mResources; - protected @Mock(answer = Answers.RETURNS_DEEP_STUBS) WindowManager mWindowManager; - protected @Mock StatusBarStateController mStatusBarStateController; - protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor; - protected @Mock KeyguardViewController mKeyguardViewController; - protected @Mock KeyguardStateController mKeyguardStateController; - protected @Mock FalsingManager mFalsingManager; - protected @Mock AuthController mAuthController; - protected @Mock DumpManager mDumpManager; - protected @Mock AccessibilityManager mAccessibilityManager; - protected @Mock ConfigurationController mConfigurationController; - protected @Mock VibratorHelper mVibrator; - protected @Mock AuthRippleController mAuthRippleController; - protected FakeExecutor mDelayableExecutor = new FakeExecutor(new FakeSystemClock()); - protected FakeFeatureFlags mFeatureFlags; - - protected @Mock PrimaryBouncerInteractor mPrimaryBouncerInteractor; - - protected LegacyLockIconViewController mUnderTest; - - // Capture listeners so that they can be used to send events - @Captor protected ArgumentCaptor<View.OnAttachStateChangeListener> mAttachCaptor = - ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class); - - @Captor protected ArgumentCaptor<KeyguardStateController.Callback> mKeyguardStateCaptor = - ArgumentCaptor.forClass(KeyguardStateController.Callback.class); - protected KeyguardStateController.Callback mKeyguardStateCallback; - - @Captor protected ArgumentCaptor<StatusBarStateController.StateListener> mStatusBarStateCaptor = - ArgumentCaptor.forClass(StatusBarStateController.StateListener.class); - protected StatusBarStateController.StateListener mStatusBarStateListener; - - @Captor protected ArgumentCaptor<AuthController.Callback> mAuthControllerCallbackCaptor; - protected AuthController.Callback mAuthControllerCallback; - - @Captor protected ArgumentCaptor<KeyguardUpdateMonitorCallback> - mKeyguardUpdateMonitorCallbackCaptor = - ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback.class); - protected KeyguardUpdateMonitorCallback mKeyguardUpdateMonitorCallback; - - @Captor protected ArgumentCaptor<Point> mPointCaptor; - - @Before - public void setUp() throws Exception { - mStaticMockSession = mockitoSession() - .mockStatic(BurnInHelperKt.class) - .strictness(Strictness.LENIENT) - .startMocking(); - MockitoAnnotations.initMocks(this); - - setupLockIconViewMocks(); - when(mContext.getResources()).thenReturn(mResources); - when(mContext.getSystemService(WindowManager.class)).thenReturn(mWindowManager); - Rect windowBounds = new Rect(0, 0, 800, 1200); - when(mWindowManager.getCurrentWindowMetrics().getBounds()).thenReturn(windowBounds); - when(mResources.getString(R.string.accessibility_unlock_button)).thenReturn(UNLOCKED_LABEL); - when(mResources.getString(R.string.accessibility_lock_icon)).thenReturn(LOCKED_LABEL); - when(mResources.getDrawable(anyInt(), any())).thenReturn(mIconDrawable); - when(mResources.getDimensionPixelSize(R.dimen.lock_icon_padding)).thenReturn(PADDING); - when(mAuthController.getScaleFactor()).thenReturn(1f); - - when(mKeyguardStateController.isShowing()).thenReturn(true); - when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false); - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); - - if (!SceneContainerFlag.isEnabled()) { - mSetFlagsRule.disableFlags(Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR); - //TODO move this to use @DisableFlags annotation if needed - mSetFlagsRule.disableFlags(Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT); - } - - mFeatureFlags = new FakeFeatureFlags(); - mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, false); - mFeatureFlags.set(LOCKSCREEN_ENABLE_LANDSCAPE, false); - - mUnderTest = new LegacyLockIconViewController( - mStatusBarStateController, - mKeyguardUpdateMonitor, - mKeyguardViewController, - mKeyguardStateController, - mFalsingManager, - mAuthController, - mDumpManager, - mAccessibilityManager, - mConfigurationController, - mDelayableExecutor, - mVibrator, - mAuthRippleController, - mResources, - mKosmos.getKeyguardTransitionInteractor(), - KeyguardInteractorFactory.create(mFeatureFlags).getKeyguardInteractor(), - mFeatureFlags, - mPrimaryBouncerInteractor, - mContext, - () -> mDeviceEntryInteractor - ); - } - - @After - public void tearDown() { - mStaticMockSession.finishMocking(); - } - - protected Pair<Float, Point> setupUdfps() { - when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(true); - final Point udfpsLocation = new Point(50, 75); - final float radius = 33f; - when(mAuthController.getUdfpsLocation()).thenReturn(udfpsLocation); - when(mAuthController.getUdfpsRadius()).thenReturn(radius); - - return new Pair(radius, udfpsLocation); - } - - protected void setupShowLockIcon() { - when(mKeyguardStateController.isShowing()).thenReturn(true); - when(mKeyguardStateController.isKeyguardGoingAway()).thenReturn(false); - when(mStatusBarStateController.isDozing()).thenReturn(false); - when(mStatusBarStateController.getDozeAmount()).thenReturn(0f); - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.KEYGUARD); - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(false); - } - - protected void captureAuthControllerCallback() { - verify(mAuthController).addCallback(mAuthControllerCallbackCaptor.capture()); - mAuthControllerCallback = mAuthControllerCallbackCaptor.getValue(); - } - - protected void captureKeyguardStateCallback() { - verify(mKeyguardStateController).addCallback(mKeyguardStateCaptor.capture()); - mKeyguardStateCallback = mKeyguardStateCaptor.getValue(); - } - - protected void captureStatusBarStateListener() { - verify(mStatusBarStateController).addCallback(mStatusBarStateCaptor.capture()); - mStatusBarStateListener = mStatusBarStateCaptor.getValue(); - } - - protected void captureKeyguardUpdateMonitorCallback() { - verify(mKeyguardUpdateMonitor).registerCallback( - mKeyguardUpdateMonitorCallbackCaptor.capture()); - mKeyguardUpdateMonitorCallback = mKeyguardUpdateMonitorCallbackCaptor.getValue(); - } - - protected void setupLockIconViewMocks() { - when(mLockIconView.getResources()).thenReturn(mResources); - when(mLockIconView.getContext()).thenReturn(mContext); - when(mLockIconView.getLockIcon()).thenReturn(mLockIcon); - } - - protected void resetLockIconView() { - reset(mLockIconView); - setupLockIconViewMocks(); - } - - protected void init(boolean useDozeMigrationFlag) { - mFeatureFlags.set(DOZING_MIGRATION_1, useDozeMigrationFlag); - mUnderTest.setLockIconView(mLockIconView); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java deleted file mode 100644 index c1ba39e89cf9..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerTest.java +++ /dev/null @@ -1,414 +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.keyguard; - -import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT; - -import static com.android.keyguard.LockIconView.ICON_LOCK; -import static com.android.keyguard.LockIconView.ICON_UNLOCK; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.anyBoolean; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import android.graphics.Point; -import android.hardware.biometrics.BiometricSourceType; -import android.testing.TestableLooper; -import android.util.Pair; -import android.view.HapticFeedbackConstants; -import android.view.View; - -import androidx.test.ext.junit.runners.AndroidJUnit4; -import androidx.test.filters.SmallTest; - -import com.android.systemui.biometrics.UdfpsController; -import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams; -import com.android.systemui.doze.util.BurnInHelperKt; -import com.android.systemui.flags.EnableSceneContainer; -import com.android.systemui.statusbar.StatusBarState; - -import org.junit.Test; -import org.junit.runner.RunWith; - -@SmallTest -@RunWith(AndroidJUnit4.class) -@TestableLooper.RunWithLooper -public class LegacyLockIconViewControllerTest extends LegacyLockIconViewControllerBaseTest { - - @Override - public void setUp() throws Exception { - super.setUp(); - when(mLockIconView.isAttachedToWindow()).thenReturn(true); - } - - @Test - public void testUpdateFingerprintLocationOnInit() { - // GIVEN fp sensor location is available pre-attached - Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location - - // WHEN lock icon view controller is initialized and attached - init(/* useMigrationFlag= */false); - - // THEN lock icon view location is updated to the udfps location with UDFPS radius - verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), - eq(PADDING)); - } - - @Test - public void testUpdatePaddingBasedOnResolutionScale() { - // GIVEN fp sensor location is available pre-attached & scaled resolution factor is 5 - Pair<Float, Point> udfps = setupUdfps(); // first = radius, second = udfps location - when(mAuthController.getScaleFactor()).thenReturn(5f); - - // WHEN lock icon view controller is initialized and attached - init(/* useMigrationFlag= */false); - - // THEN lock icon view location is updated with the scaled radius - verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), - eq(PADDING * 5)); - } - - @Test - public void testUpdateLockIconLocationOnAuthenticatorsRegistered() { - // GIVEN fp sensor location is not available pre-init - when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); - when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); - init(/* useMigrationFlag= */false); - resetLockIconView(); // reset any method call counts for when we verify method calls later - - // GIVEN fp sensor location is available post-attached - captureAuthControllerCallback(); - Pair<Float, Point> udfps = setupUdfps(); - - // WHEN all authenticators are registered - mAuthControllerCallback.onAllAuthenticatorsRegistered(TYPE_FINGERPRINT); - mDelayableExecutor.runAllReady(); - - // THEN lock icon view location is updated with the same coordinates as auth controller vals - verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), - eq(PADDING)); - } - - @Test - public void testUpdateLockIconLocationOnUdfpsLocationChanged() { - // GIVEN fp sensor location is not available pre-init - when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); - when(mAuthController.getFingerprintSensorLocation()).thenReturn(null); - init(/* useMigrationFlag= */false); - resetLockIconView(); // reset any method call counts for when we verify method calls later - - // GIVEN fp sensor location is available post-attached - captureAuthControllerCallback(); - Pair<Float, Point> udfps = setupUdfps(); - - // WHEN udfps location changes - mAuthControllerCallback.onUdfpsLocationChanged(new UdfpsOverlayParams()); - mDelayableExecutor.runAllReady(); - - // THEN lock icon view location is updated with the same coordinates as auth controller vals - verify(mLockIconView).setCenterLocation(eq(udfps.second), eq(udfps.first), - eq(PADDING)); - } - - @Test - public void testLockIconViewBackgroundEnabledWhenUdfpsIsSupported() { - // GIVEN Udpfs sensor location is available - setupUdfps(); - - // WHEN the view is attached - init(/* useMigrationFlag= */false); - - // THEN the lock icon view background should be enabled - verify(mLockIconView).setUseBackground(true); - } - - @Test - public void testLockIconViewBackgroundDisabledWhenUdfpsIsNotSupported() { - // GIVEN Udfps sensor location is not supported - when(mKeyguardUpdateMonitor.isUdfpsSupported()).thenReturn(false); - - // WHEN the view is attached - init(/* useMigrationFlag= */false); - - // THEN the lock icon view background should be disabled - verify(mLockIconView).setUseBackground(false); - } - - @Test - public void testLockIconStartState() { - // GIVEN lock icon state - setupShowLockIcon(); - - // WHEN lock icon controller is initialized - init(/* useMigrationFlag= */false); - - // THEN the lock icon should show - verify(mLockIconView).updateIcon(ICON_LOCK, false); - } - - @Test - public void testLockIcon_updateToUnlock() { - // GIVEN starting state for the lock icon - setupShowLockIcon(); - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - reset(mLockIconView); - - // WHEN the unlocked state changes to canDismissLockScreen=true - when(mKeyguardStateController.canDismissLockScreen()).thenReturn(true); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the unlock should show - verify(mLockIconView).updateIcon(ICON_UNLOCK, false); - } - - @Test - public void testLockIcon_clearsIconWhenUnlocked() { - // GIVEN udfps not enrolled - setupUdfps(); - when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false); - - // GIVEN starting state for the lock icon - setupShowLockIcon(); - when(mStatusBarStateController.getState()).thenReturn(StatusBarState.SHADE); - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureStatusBarStateListener(); - reset(mLockIconView); - - // WHEN the dozing state changes - mStatusBarStateListener.onDozingChanged(false /* isDozing */); - - // THEN the icon is cleared - verify(mLockIconView).clearIcon(); - } - - @Test - public void testLockIcon_updateToAodLock_whenUdfpsEnrolled() { - // GIVEN udfps enrolled - setupUdfps(); - when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); - - // GIVEN starting state for the lock icon - setupShowLockIcon(); - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureStatusBarStateListener(); - reset(mLockIconView); - - // WHEN the dozing state changes - mStatusBarStateListener.onDozingChanged(true /* isDozing */); - - // THEN the AOD lock icon should show - verify(mLockIconView).updateIcon(ICON_LOCK, true); - } - - @Test - public void testBurnInOffsetsUpdated_onDozeAmountChanged() { - // GIVEN udfps enrolled - setupUdfps(); - when(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true); - - // GIVEN burn-in offset = 5 - int burnInOffset = 5; - when(BurnInHelperKt.getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset); - - // GIVEN starting state for the lock icon (keyguard) - setupShowLockIcon(); - init(/* useMigrationFlag= */false); - captureStatusBarStateListener(); - reset(mLockIconView); - - // WHEN dozing updates - mStatusBarStateListener.onDozingChanged(true /* isDozing */); - mStatusBarStateListener.onDozeAmountChanged(1f, 1f); - - // THEN the view's translation is updated to use the AoD burn-in offsets - verify(mLockIconView).setTranslationY(burnInOffset); - verify(mLockIconView).setTranslationX(burnInOffset); - reset(mLockIconView); - - // WHEN the device is no longer dozing - mStatusBarStateListener.onDozingChanged(false /* isDozing */); - mStatusBarStateListener.onDozeAmountChanged(0f, 0f); - - // THEN the view is updated to NO translation (no burn-in offsets anymore) - verify(mLockIconView).setTranslationY(0); - verify(mLockIconView).setTranslationX(0); - } - - @Test - public void lockIconShows_afterUnlockStateChanges() { - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - captureKeyguardUpdateMonitorCallback(); - - // GIVEN user has unlocked with a biometric auth (ie: face auth) - // and biometric running state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, - BiometricSourceType.FACE); - reset(mLockIconView); - - // WHEN the unlocked state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the lock icon is shown - verify(mLockIconView).setContentDescription(LOCKED_LABEL); - } - - @Test - public void lockIconAccessibility_notVisibleToUser() { - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - captureKeyguardUpdateMonitorCallback(); - - // GIVEN user has unlocked with a biometric auth (ie: face auth) - // and biometric running state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, - BiometricSourceType.FACE); - reset(mLockIconView); - when(mLockIconView.isVisibleToUser()).thenReturn(false); - - // WHEN the unlocked state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the lock icon is shown - verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - - @Test - public void lockIconAccessibility_bouncerAnimatingAway() { - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - captureKeyguardUpdateMonitorCallback(); - - // GIVEN user has unlocked with a biometric auth (ie: face auth) - // and biometric running state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, - BiometricSourceType.FACE); - reset(mLockIconView); - when(mLockIconView.isVisibleToUser()).thenReturn(true); - when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(true); - - // WHEN the unlocked state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the lock icon is shown - verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - - @Test - public void lockIconAccessibility_bouncerNotAnimatingAway_viewVisible() { - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */false); - captureKeyguardStateCallback(); - captureKeyguardUpdateMonitorCallback(); - - // GIVEN user has unlocked with a biometric auth (ie: face auth) - // and biometric running state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(true); - mKeyguardUpdateMonitorCallback.onBiometricRunningStateChanged(false, - BiometricSourceType.FACE); - reset(mLockIconView); - when(mLockIconView.isVisibleToUser()).thenReturn(true); - when(mPrimaryBouncerInteractor.isAnimatingAway()).thenReturn(false); - - // WHEN the unlocked state changes - when(mKeyguardUpdateMonitor.getUserUnlockedWithBiometric(anyInt())).thenReturn(false); - mKeyguardStateCallback.onUnlockedChanged(); - - // THEN the lock icon is shown - verify(mLockIconView).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - @Test - public void playHaptic_onTouchExploration_performHapticFeedback() { - // WHEN request to vibrate on touch exploration - mUnderTest.vibrateOnTouchExploration(); - - // THEN performHapticFeedback is used - verify(mVibrator).performHapticFeedback(any(), eq(HapticFeedbackConstants.CONTEXT_CLICK)); - } - - @Test - public void playHaptic_onLongPress_performHapticFeedback() { - // WHEN request to vibrate on long press - mUnderTest.vibrateOnLongPress(); - - // THEN uses perform haptic feedback - verify(mVibrator).performHapticFeedback(any(), eq(UdfpsController.LONG_PRESS)); - } - - @Test - public void longPress_showBouncer_sceneContainerNotEnabled() { - init(/* useMigrationFlag= */ false); - when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); - - // WHEN longPress - mUnderTest.onLongPress(); - - // THEN show primary bouncer via keyguard view controller, not scene container - verify(mKeyguardViewController).showPrimaryBouncer(anyBoolean()); - verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); - } - - @Test - @EnableSceneContainer - public void longPress_showBouncer() { - init(/* useMigrationFlag= */ false); - when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(false); - - // WHEN longPress - mUnderTest.onLongPress(); - - // THEN show primary bouncer - verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean()); - verify(mDeviceEntryInteractor).attemptDeviceEntry(); - } - - @Test - @EnableSceneContainer - public void longPress_falsingTriggered_doesNotShowBouncer() { - init(/* useMigrationFlag= */ false); - when(mFalsingManager.isFalseLongTap(anyInt())).thenReturn(true); - - // WHEN longPress - mUnderTest.onLongPress(); - - // THEN don't show primary bouncer - verify(mDeviceEntryInteractor, never()).attemptDeviceEntry(); - verify(mKeyguardViewController, never()).showPrimaryBouncer(anyBoolean()); - } -} diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt deleted file mode 100644 index 2fd3cb0f0592..000000000000 --- a/packages/SystemUI/tests/src/com/android/keyguard/LegacyLockIconViewControllerWithCoroutinesTest.kt +++ /dev/null @@ -1,149 +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.keyguard - -import android.view.View -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.keyguard.LockIconView.ICON_LOCK -import com.android.systemui.doze.util.getBurnInOffset -import com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED -import com.android.systemui.statusbar.StatusBarState -import com.android.systemui.util.mockito.whenever -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.runBlocking -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.anyBoolean -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.reset -import org.mockito.Mockito.verify - -@RunWith(AndroidJUnit4::class) -@SmallTest -class LegacyLockIconViewControllerWithCoroutinesTest : LegacyLockIconViewControllerBaseTest() { - - /** After migration, replaces LockIconViewControllerTest version */ - @Test - fun testLockIcon_clearsIconWhenUnlocked() = - runBlocking(IMMEDIATE) { - // GIVEN udfps not enrolled - setupUdfps() - whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(false) - - // GIVEN starting state for the lock icon - setupShowLockIcon() - whenever(mStatusBarStateController.state).thenReturn(StatusBarState.SHADE) - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */ true) - reset(mLockIconView) - - // WHEN the dozing state changes - mUnderTest.mIsDozingCallback.accept(false) - // THEN the icon is cleared - verify(mLockIconView).clearIcon() - } - - /** After migration, replaces LockIconViewControllerTest version */ - @Test - fun testLockIcon_updateToAodLock_whenUdfpsEnrolled() = - runBlocking(IMMEDIATE) { - // GIVEN udfps enrolled - setupUdfps() - whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true) - - // GIVEN starting state for the lock icon - setupShowLockIcon() - - // GIVEN lock icon controller is initialized and view is attached - init(/* useMigrationFlag= */ true) - reset(mLockIconView) - - // WHEN the dozing state changes - mUnderTest.mIsDozingCallback.accept(true) - - // THEN the AOD lock icon should show - verify(mLockIconView).updateIcon(ICON_LOCK, true) - } - - /** After migration, replaces LockIconViewControllerTest version */ - @Test - fun testBurnInOffsetsUpdated_onDozeAmountChanged() = - runBlocking(IMMEDIATE) { - // GIVEN udfps enrolled - setupUdfps() - whenever(mKeyguardUpdateMonitor.isUdfpsEnrolled()).thenReturn(true) - - // GIVEN burn-in offset = 5 - val burnInOffset = 5 - whenever(getBurnInOffset(anyInt(), anyBoolean())).thenReturn(burnInOffset) - - // GIVEN starting state for the lock icon (keyguard) - setupShowLockIcon() - init(/* useMigrationFlag= */ true) - reset(mLockIconView) - - // WHEN dozing updates - mUnderTest.mIsDozingCallback.accept(true) - mUnderTest.mDozeTransitionCallback.accept(1f) - - // THEN the view's translation is updated to use the AoD burn-in offsets - verify(mLockIconView).setTranslationY(burnInOffset.toFloat()) - verify(mLockIconView).setTranslationX(burnInOffset.toFloat()) - reset(mLockIconView) - - // WHEN the device is no longer dozing - mUnderTest.mIsDozingCallback.accept(false) - mUnderTest.mDozeTransitionCallback.accept(0f) - - // THEN the view is updated to NO translation (no burn-in offsets anymore) - verify(mLockIconView).setTranslationY(0f) - verify(mLockIconView).setTranslationX(0f) - } - - @Test - fun testHideLockIconView_onLockscreenHostedDreamStateChanged() = - runBlocking(IMMEDIATE) { - // GIVEN starting state for the lock icon (keyguard) and wallpaper dream enabled - mFeatureFlags.set(LOCKSCREEN_WALLPAPER_DREAM_ENABLED, true) - setupShowLockIcon() - init(/* useMigrationFlag= */ true) - reset(mLockIconView) - - // WHEN dream starts - mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept( - true /* isActiveDreamLockscreenHosted */ - ) - - // THEN the lock icon is hidden - verify(mLockIconView).visibility = View.INVISIBLE - reset(mLockIconView) - - // WHEN the device is no longer dreaming - mUnderTest.mIsActiveDreamLockscreenHostedCallback.accept( - false /* isActiveDreamLockscreenHosted */ - ) - - // THEN lock icon is visible - verify(mLockIconView).visibility = View.VISIBLE - } - - companion object { - private val IMMEDIATE = Dispatchers.Main.immediate - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt index 8b427fbc5fb8..071acfa44650 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TransitionAnimatorTest.kt @@ -156,7 +156,7 @@ class TransitionAnimatorTest(val useSpring: Boolean) : SysuiTestCase() { createEndState(transitionContainer), backgroundLayer, fadeWindowBackgroundLayer, - useSpring, + useSpring = useSpring, ) } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt deleted file mode 100644 index 6dc4b10a57da..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt +++ /dev/null @@ -1,351 +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.systemui.biometrics - -import android.graphics.Point -import android.hardware.biometrics.BiometricSourceType -import android.hardware.fingerprint.FingerprintSensorPropertiesInternal -import android.testing.TestableLooper.RunWithLooper -import android.util.DisplayMetrics -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession -import com.android.keyguard.KeyguardUpdateMonitor -import com.android.keyguard.KeyguardUpdateMonitorCallback -import com.android.keyguard.logging.KeyguardLogger -import com.android.systemui.Flags -import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository -import com.android.systemui.deviceentry.domain.interactor.AuthRippleInteractor -import com.android.systemui.keyguard.WakefulnessLifecycle -import com.android.systemui.log.logcatLogBuffer -import com.android.systemui.plugins.statusbar.StatusBarStateController -import com.android.systemui.statusbar.LightRevealScrim -import com.android.systemui.statusbar.NotificationShadeWindowController -import com.android.systemui.statusbar.commandline.CommandRegistry -import com.android.systemui.statusbar.phone.BiometricUnlockController -import com.android.systemui.statusbar.policy.ConfigurationController -import com.android.systemui.statusbar.policy.KeyguardStateController -import com.android.systemui.util.leak.RotationUtils -import com.android.systemui.util.mockito.any -import kotlinx.coroutines.ExperimentalCoroutinesApi -import org.junit.After -import org.junit.Assert.assertFalse -import org.junit.Assert.assertTrue -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.ArgumentCaptor -import org.mockito.ArgumentMatchers -import org.mockito.ArgumentMatchers.eq -import org.mockito.Captor -import org.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 -import org.mockito.MockitoSession -import org.mockito.quality.Strictness -import javax.inject.Provider - - -@ExperimentalCoroutinesApi -@SmallTest -@RunWith(AndroidJUnit4::class) -class AuthRippleControllerTest : SysuiTestCase() { - private lateinit var staticMockSession: MockitoSession - - private lateinit var controller: AuthRippleController - @Mock private lateinit var rippleView: AuthRippleView - @Mock private lateinit var commandRegistry: CommandRegistry - @Mock private lateinit var configurationController: ConfigurationController - @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor - @Mock private lateinit var authController: AuthController - @Mock private lateinit var authRippleInteractor: AuthRippleInteractor - @Mock private lateinit var keyguardStateController: KeyguardStateController - @Mock - private lateinit var wakefulnessLifecycle: WakefulnessLifecycle - @Mock - private lateinit var notificationShadeWindowController: NotificationShadeWindowController - @Mock - private lateinit var biometricUnlockController: BiometricUnlockController - @Mock - private lateinit var udfpsControllerProvider: Provider<UdfpsController> - @Mock - private lateinit var udfpsController: UdfpsController - @Mock - private lateinit var statusBarStateController: StatusBarStateController - @Mock - private lateinit var lightRevealScrim: LightRevealScrim - @Mock - private lateinit var fpSensorProp: FingerprintSensorPropertiesInternal - - private val facePropertyRepository = FakeFacePropertyRepository() - private val displayMetrics = DisplayMetrics() - - @Captor - private lateinit var biometricUnlockListener: - ArgumentCaptor<BiometricUnlockController.BiometricUnlockEventsListener> - - @Before - fun setUp() { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - MockitoAnnotations.initMocks(this) - staticMockSession = mockitoSession() - .mockStatic(RotationUtils::class.java) - .strictness(Strictness.LENIENT) - .startMocking() - - `when`(RotationUtils.getRotation(context)).thenReturn(RotationUtils.ROTATION_NONE) - `when`(authController.udfpsProps).thenReturn(listOf(fpSensorProp)) - `when`(udfpsControllerProvider.get()).thenReturn(udfpsController) - - controller = AuthRippleController( - context, - authController, - configurationController, - keyguardUpdateMonitor, - keyguardStateController, - wakefulnessLifecycle, - commandRegistry, - notificationShadeWindowController, - udfpsControllerProvider, - statusBarStateController, - displayMetrics, - KeyguardLogger(logcatLogBuffer(AuthRippleController.TAG)), - biometricUnlockController, - lightRevealScrim, - authRippleInteractor, - facePropertyRepository, - rippleView, - ) - controller.init() - } - - @After - fun tearDown() { - staticMockSession.finishMocking() - } - - @Test - fun testFingerprintTrigger_KeyguardShowing_Ripple() { - // GIVEN fp exists, keyguard is showing, unlocking with fp allowed - val fpsLocation = Point(5, 5) - `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) - controller.onViewAttached() - `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) - - // WHEN fingerprint authenticated - verify(biometricUnlockController).addListener(biometricUnlockListener.capture()) - biometricUnlockListener.value - .onBiometricUnlockedWithKeyguardDismissal(BiometricSourceType.FINGERPRINT) - - // THEN update sensor location and show ripple - verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) - verify(rippleView).startUnlockedRipple(any()) - } - - @Test - fun testFingerprintTrigger_KeyguardNotShowing_NoRipple() { - // GIVEN fp exists & unlocking with fp allowed - val fpsLocation = Point(5, 5) - `when`(authController.udfpsLocation).thenReturn(fpsLocation) - controller.onViewAttached() - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(true) - - // WHEN keyguard is NOT showing & fingerprint authenticated - `when`(keyguardStateController.isShowing).thenReturn(false) - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) - - // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test - fun testFingerprintTrigger_biometricUnlockNotAllowed_NoRipple() { - // GIVEN fp exists & keyguard is showing - val fpsLocation = Point(5, 5) - `when`(authController.udfpsLocation).thenReturn(fpsLocation) - controller.onViewAttached() - `when`(keyguardStateController.isShowing).thenReturn(true) - - // WHEN unlocking with fingerprint is NOT allowed & fingerprint authenticated - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FINGERPRINT))).thenReturn(false) - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) - - // THEN no ripple - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test - fun testNullFaceSensorLocationDoesNothing() { - facePropertyRepository.setSensorLocation(null) - controller.onViewAttached() - - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FACE /* type */, - false /* isStrongBiometric */) - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test - fun testNullFingerprintSensorLocationDoesNothing() { - `when`(authController.fingerprintSensorLocation).thenReturn(null) - controller.onViewAttached() - - val captor = ArgumentCaptor.forClass(KeyguardUpdateMonitorCallback::class.java) - verify(keyguardUpdateMonitor).registerCallback(captor.capture()) - - captor.value.onBiometricAuthenticated( - 0 /* userId */, - BiometricSourceType.FINGERPRINT /* type */, - false /* isStrongBiometric */) - verify(rippleView, never()).startUnlockedRipple(any()) - } - - @Test - fun registersAndDeregisters() { - controller.onViewAttached() - val captor = ArgumentCaptor - .forClass(KeyguardStateController.Callback::class.java) - verify(keyguardStateController).addCallback(captor.capture()) - val captor2 = ArgumentCaptor - .forClass(WakefulnessLifecycle.Observer::class.java) - verify(wakefulnessLifecycle).addObserver(captor2.capture()) - controller.onViewDetached() - verify(keyguardStateController).removeCallback(any()) - verify(wakefulnessLifecycle).removeObserver(any()) - } - - @Test - @RunWithLooper(setAsMainLooper = true) - fun testAnimatorRunWhenWakeAndUnlock_fingerprint() { - mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION) - val fpsLocation = Point(5, 5) - `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) - controller.onViewAttached() - `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - BiometricSourceType.FINGERPRINT)).thenReturn(true) - `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) - - controller.showUnlockRipple(BiometricSourceType.FINGERPRINT) - assertTrue("reveal didn't start on keyguardFadingAway", - controller.startLightRevealScrimOnKeyguardFadingAway) - `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) - controller.onKeyguardFadingAwayChanged() - assertFalse("reveal triggers multiple times", - controller.startLightRevealScrimOnKeyguardFadingAway) - } - - @Test - @RunWithLooper(setAsMainLooper = true) - fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() { - mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION) - val faceLocation = Point(5, 5) - facePropertyRepository.setSensorLocation(faceLocation) - controller.onViewAttached() - `when`(keyguardStateController.isShowing).thenReturn(true) - `when`(biometricUnlockController.isWakeAndUnlock).thenReturn(true) - `when`(authController.isUdfpsFingerDown).thenReturn(true) - `when`(keyguardUpdateMonitor.isUnlockingWithBiometricAllowed( - eq(BiometricSourceType.FACE))).thenReturn(true) - - controller.showUnlockRipple(BiometricSourceType.FACE) - assertTrue("reveal didn't start on keyguardFadingAway", - controller.startLightRevealScrimOnKeyguardFadingAway) - `when`(keyguardStateController.isKeyguardFadingAway).thenReturn(true) - controller.onKeyguardFadingAwayChanged() - assertFalse("reveal triggers multiple times", - controller.startLightRevealScrimOnKeyguardFadingAway) - } - - @Test - fun testUpdateRippleColor() { - controller.onViewAttached() - val captor = ArgumentCaptor - .forClass(ConfigurationController.ConfigurationListener::class.java) - verify(configurationController).addCallback(captor.capture()) - - reset(rippleView) - captor.value.onThemeChanged() - verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt()) - - reset(rippleView) - captor.value.onUiModeChanged() - verify(rippleView).setLockScreenColor(ArgumentMatchers.anyInt()) - } - - @Test - fun testUdfps_onFingerDown_runningForDeviceEntry_showDwellRipple() { - // GIVEN fingerprint detection is running on keyguard - `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(true) - - // GIVEN view is already attached - controller.onViewAttached() - val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) - verify(udfpsController).addCallback(captor.capture()) - - // GIVEN fp is updated to Point(5, 5) - val fpsLocation = Point(5, 5) - `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation) - - // WHEN finger is down - captor.value.onFingerDown() - - // THEN update sensor location and show ripple - verify(rippleView).setFingerprintSensorLocation(fpsLocation, 0f) - verify(rippleView).startDwellRipple(false) - } - - @Test - fun testUdfps_onFingerDown_notDeviceEntry_doesNotShowDwellRipple() { - // GIVEN fingerprint detection is NOT running on keyguard - `when`(keyguardUpdateMonitor.isFingerprintDetectionRunning).thenReturn(false) - - // GIVEN view is already attached - controller.onViewAttached() - val captor = ArgumentCaptor.forClass(UdfpsController.Callback::class.java) - verify(udfpsController).addCallback(captor.capture()) - - // WHEN finger is down - captor.value.onFingerDown() - - // THEN doesn't show dwell ripple - verify(rippleView, never()).startDwellRipple(false) - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt index e2a6a5508992..4baca713e19f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt @@ -16,21 +16,14 @@ package com.android.systemui.biometrics -import android.graphics.Rect import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD -import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER -import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS -import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING import android.hardware.biometrics.BiometricRequestConstants.RequestReason import android.hardware.fingerprint.IUdfpsOverlayControllerCallback import android.testing.TestableLooper.RunWithLooper import android.view.LayoutInflater import android.view.MotionEvent -import android.view.Surface -import android.view.Surface.Rotation import android.view.View -import android.view.ViewGroup import android.view.WindowManager import android.view.accessibility.AccessibilityManager import androidx.test.ext.junit.runners.AndroidJUnit4 @@ -38,7 +31,6 @@ import androidx.test.filters.SmallTest import com.android.app.viewcapture.ViewCapture import com.android.app.viewcapture.ViewCaptureAwareWindowManager import com.android.keyguard.KeyguardUpdateMonitor -import com.android.systemui.Flags import com.android.systemui.SysuiTestCase import com.android.systemui.animation.ActivityTransitionAnimator import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor @@ -61,9 +53,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.power.shared.model.WakefulnessState -import com.android.systemui.res.R import com.android.systemui.shade.domain.interactor.ShadeInteractor -import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController @@ -86,7 +76,6 @@ import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.eq import org.mockito.Captor import org.mockito.Mock -import org.mockito.Mockito.mock import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever @@ -118,7 +107,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor @Mock private lateinit var dialogManager: SystemUIDialogManager @Mock private lateinit var dumpManager: DumpManager - @Mock private lateinit var transitionController: LockscreenShadeTransitionController @Mock private lateinit var configurationController: ConfigurationController @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock @@ -126,8 +114,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { @Mock private lateinit var udfpsDisplayMode: UdfpsDisplayModeProvider @Mock private lateinit var controllerCallback: IUdfpsOverlayControllerCallback @Mock private lateinit var udfpsController: UdfpsController - @Mock private lateinit var udfpsView: UdfpsView - @Mock private lateinit var mUdfpsKeyguardViewLegacy: UdfpsKeyguardViewLegacy @Mock private lateinit var mActivityTransitionAnimator: ActivityTransitionAnimator @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor @Mock private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor @@ -147,7 +133,7 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { private lateinit var powerInteractor: PowerInteractor private lateinit var testScope: TestScope - private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true } + private val onTouch = { _: View, _: MotionEvent -> true } private var overlayParams: UdfpsOverlayParams = UdfpsOverlayParams() private lateinit var controllerOverlay: UdfpsControllerOverlay @@ -158,53 +144,37 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { powerInteractor = kosmos.powerInteractor keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor - whenever(inflater.inflate(R.layout.udfps_view, null, false)).thenReturn(udfpsView) - whenever(inflater.inflate(R.layout.udfps_bp_view, null)) - .thenReturn(mock(UdfpsBpView::class.java)) - whenever(inflater.inflate(R.layout.udfps_keyguard_view_legacy, null)) - .thenReturn(mUdfpsKeyguardViewLegacy) - whenever(inflater.inflate(R.layout.udfps_fpm_empty_view, null)) - .thenReturn(mock(UdfpsFpmEmptyView::class.java)) } private suspend fun withReasonSuspend( @RequestReason reason: Int, isDebuggable: Boolean = false, - enableDeviceEntryUdfpsRefactor: Boolean = false, block: suspend () -> Unit, ) { - withReason( - reason, - isDebuggable, - enableDeviceEntryUdfpsRefactor, - ) + withReason(reason, isDebuggable) block() } private fun withReason( @RequestReason reason: Int, isDebuggable: Boolean = false, - enableDeviceEntryUdfpsRefactor: Boolean = false, block: () -> Unit = {}, ) { - if (enableDeviceEntryUdfpsRefactor) { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - } else { - mSetFlagsRule.disableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - } controllerOverlay = UdfpsControllerOverlay( context, inflater, - ViewCaptureAwareWindowManager(windowManager, lazyViewCapture, - isViewCaptureEnabled = false), + ViewCaptureAwareWindowManager( + windowManager, + lazyViewCapture, + isViewCaptureEnabled = false, + ), accessibilityManager, statusBarStateController, statusBarKeyguardViewManager, keyguardUpdateMonitor, dialogManager, dumpManager, - transitionController, configurationController, keyguardStateController, unlockedScreenOffAnimationController, @@ -230,117 +200,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { block() } - @Test fun showUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { showUdfpsOverlay() } - - @Test - fun showUdfpsOverlay_keyguard() = - withReason(REASON_AUTH_KEYGUARD) { - showUdfpsOverlay() - verify(mUdfpsKeyguardViewLegacy).updateSensorLocation(eq(overlayParams.sensorBounds)) - } - - @Test fun showUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { showUdfpsOverlay() } - - private fun withRotation(@Rotation rotation: Int, block: () -> Unit) { - // Sensor that's in the top left corner of the display in natural orientation. - val sensorBounds = Rect(0, 0, SENSOR_WIDTH, SENSOR_HEIGHT) - val overlayBounds = Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT) - overlayParams = - UdfpsOverlayParams( - sensorBounds, - overlayBounds, - DISPLAY_WIDTH, - DISPLAY_HEIGHT, - scaleFactor = 1f, - rotation - ) - block() - } - - @Test - fun showUdfpsOverlay_withRotation0() = - withRotation(Surface.ROTATION_0) { - withReason(REASON_AUTH_BP) { - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // ROTATION_0 is the native orientation. Sensor should stay in the top left corner. - val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(DISPLAY_WIDTH) - assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT) - } - } - - @Test - fun showUdfpsOverlay_withRotation180() = - withRotation(Surface.ROTATION_180) { - withReason(REASON_AUTH_BP) { - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // ROTATION_180 is not supported. Sensor should stay in the top left corner. - val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(DISPLAY_WIDTH) - assertThat(lp.height).isEqualTo(DISPLAY_HEIGHT) - } - } - - @Test - fun showUdfpsOverlay_withRotation90() = - withRotation(Surface.ROTATION_90) { - withReason(REASON_AUTH_BP) { - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // Sensor should be in the bottom left corner in ROTATION_90. - val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT) - assertThat(lp.height).isEqualTo(DISPLAY_WIDTH) - } - } - - @Test - fun showUdfpsOverlay_withRotation270() = - withRotation(Surface.ROTATION_270) { - withReason(REASON_AUTH_BP) { - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // Sensor should be in the top right corner in ROTATION_270. - val lp = layoutParamsCaptor.value - assertThat(lp.x).isEqualTo(0) - assertThat(lp.y).isEqualTo(0) - assertThat(lp.width).isEqualTo(DISPLAY_HEIGHT) - assertThat(lp.height).isEqualTo(DISPLAY_WIDTH) - } - } - - @Test - fun showUdfpsOverlay_awake() = - testScope.runTest { - withReason(REASON_AUTH_KEYGUARD) { - powerRepository.updateWakefulness( - rawState = WakefulnessState.AWAKE, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - runCurrent() - controllerOverlay.show(udfpsController, overlayParams) - runCurrent() - verify(windowManager).addView(any(), any()) - } - } - @Test fun showUdfpsOverlay_whileGoingToSleep() = testScope.runTest { @@ -412,91 +271,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } @Test - fun showUdfpsOverlay_afterFinishedTransitioningToAOD() = - testScope.runTest { - withReasonSuspend(REASON_AUTH_KEYGUARD) { - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.OFF, - to = KeyguardState.GONE, - testScope = this, - ) - powerRepository.updateWakefulness( - rawState = WakefulnessState.STARTING_TO_SLEEP, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - runCurrent() - - // WHEN a request comes to show the view - controllerOverlay.show(udfpsController, overlayParams) - runCurrent() - - // THEN the view does not get added immediately - verify(windowManager, never()).addView(any(), any()) - - // WHEN the device finishes transitioning to AOD - keyguardTransitionRepository.sendTransitionSteps( - from = KeyguardState.GONE, - to = KeyguardState.AOD, - testScope = this, - ) - runCurrent() - - // THEN the view gets added - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - } - } - - private fun showUdfpsOverlay() { - val didShow = controllerOverlay.show(udfpsController, overlayParams) - - verify(windowManager).addView(eq(controllerOverlay.getTouchOverlay()), any()) - verify(udfpsView).setUdfpsDisplayModeProvider(eq(udfpsDisplayMode)) - verify(udfpsView).animationViewController = any() - verify(udfpsView).addView(any()) - - assertThat(didShow).isTrue() - assertThat(controllerOverlay.isShowing).isTrue() - assertThat(controllerOverlay.isHiding).isFalse() - assertThat(controllerOverlay.getTouchOverlay()).isNotNull() - } - - @Test fun hideUdfpsOverlay_bp() = withReason(REASON_AUTH_BP) { hideUdfpsOverlay() } - - @Test fun hideUdfpsOverlay_keyguard() = withReason(REASON_AUTH_KEYGUARD) { hideUdfpsOverlay() } - - @Test fun hideUdfpsOverlay_settings() = withReason(REASON_AUTH_SETTINGS) { hideUdfpsOverlay() } - - @Test fun hideUdfpsOverlay_other() = withReason(REASON_AUTH_OTHER) { hideUdfpsOverlay() } - - private fun hideUdfpsOverlay() { - val didShow = controllerOverlay.show(udfpsController, overlayParams) - val view = controllerOverlay.getTouchOverlay() - view?.let { whenever(view.parent).thenReturn(mock(ViewGroup::class.java)) } - val didHide = controllerOverlay.hide() - - verify(windowManager).removeView(eq(view)) - - assertThat(didShow).isTrue() - assertThat(didHide).isTrue() - assertThat(controllerOverlay.getTouchOverlay()).isNull() - assertThat(controllerOverlay.animationViewController).isNull() - assertThat(controllerOverlay.isShowing).isFalse() - assertThat(controllerOverlay.isHiding).isTrue() - } - - @Test fun canNotHide() = withReason(REASON_AUTH_BP) { assertThat(controllerOverlay.hide()).isFalse() } @Test - fun canNotReshow() = - withReason(REASON_AUTH_BP) { - assertThat(controllerOverlay.show(udfpsController, overlayParams)).isTrue() - assertThat(controllerOverlay.show(udfpsController, overlayParams)).isFalse() - } - - @Test fun cancels() = withReason(REASON_AUTH_BP) { controllerOverlay.cancel() @@ -504,16 +281,6 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } @Test - fun unconfigureDisplayOnHide() = - withReason(REASON_AUTH_BP) { - whenever(udfpsView.isDisplayConfigured).thenReturn(true) - - controllerOverlay.show(udfpsController, overlayParams) - controllerOverlay.hide() - verify(udfpsView).unconfigureDisplay() - } - - @Test fun matchesRequestIds() = withReason(REASON_AUTH_BP) { assertThat(controllerOverlay.matchesRequestId(REQUEST_ID)).isTrue() @@ -521,29 +288,9 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { } @Test - fun smallOverlayOnEnrollmentWithA11y() = - withRotation(Surface.ROTATION_0) { - withReason(REASON_ENROLL_ENROLLING) { - // When a11y enabled during enrollment - whenever(accessibilityManager.isTouchExplorationEnabled).thenReturn(true) - - controllerOverlay.show(udfpsController, overlayParams) - verify(windowManager) - .addView(eq(controllerOverlay.getTouchOverlay()), layoutParamsCaptor.capture()) - - // Layout params should use sensor bounds - val lp = layoutParamsCaptor.value - assertThat(lp.width).isEqualTo(overlayParams.sensorBounds.width()) - assertThat(lp.height).isEqualTo(overlayParams.sensorBounds.height()) - } - } - - @Test fun addViewPending_layoutIsNotUpdated() = testScope.runTest { withReasonSuspend(REASON_AUTH_KEYGUARD) { - mSetFlagsRule.enableFlags(Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - // GIVEN going to sleep keyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OFF, @@ -574,26 +321,4 @@ class UdfpsControllerOverlayTest : SysuiTestCase() { controllerOverlay.hide() } } - - @Test - fun updateOverlayParams_viewLayoutUpdated() = - testScope.runTest { - withReasonSuspend(REASON_AUTH_KEYGUARD) { - powerRepository.updateWakefulness( - rawState = WakefulnessState.AWAKE, - lastWakeReason = WakeSleepReason.POWER_BUTTON, - lastSleepReason = WakeSleepReason.OTHER, - ) - runCurrent() - controllerOverlay.show(udfpsController, overlayParams) - runCurrent() - verify(windowManager).addView(any(), any()) - - // WHEN updateOverlayParams gets called - controllerOverlay.updateOverlayParams(overlayParams) - - // THEN the view layout is updated - verify(windowManager).updateViewLayout(any(), any()) - } - } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt deleted file mode 100644 index 9fbe09619ff1..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt +++ /dev/null @@ -1,109 +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.systemui.biometrics - -import android.hardware.biometrics.SensorLocationInternal -import android.testing.TestableLooper -import android.testing.ViewUtils -import android.view.LayoutInflater -import android.view.Surface -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.res.R -import com.android.systemui.SysuiTestCase -import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams -import com.android.systemui.util.mockito.any -import com.android.systemui.util.mockito.mock -import com.android.systemui.util.mockito.withArgCaptor -import com.google.common.truth.Truth.assertThat -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.Mockito.never -import org.mockito.Mockito.nullable -import org.mockito.Mockito.verify -import org.mockito.junit.MockitoJUnit - -private const val SENSOR_X = 50 -private const val SENSOR_Y = 250 -private const val SENSOR_RADIUS = 10 - -@SmallTest -@RunWith(AndroidJUnit4::class) -@TestableLooper.RunWithLooper -class UdfpsViewTest : SysuiTestCase() { - - @JvmField @Rule - var rule = MockitoJUnit.rule() - - @Mock - lateinit var hbmProvider: UdfpsDisplayModeProvider - @Mock - lateinit var animationViewController: UdfpsAnimationViewController<UdfpsAnimationView> - - private lateinit var view: UdfpsView - - @Before - fun setup() { - context.setTheme(androidx.appcompat.R.style.Theme_AppCompat) - view = LayoutInflater.from(context).inflate(R.layout.udfps_view, null) as UdfpsView - view.animationViewController = animationViewController - val sensorBounds = SensorLocationInternal("", SENSOR_X, SENSOR_Y, SENSOR_RADIUS).rect - view.overlayParams = UdfpsOverlayParams(sensorBounds, sensorBounds, 1920, - 1080, 1f, Surface.ROTATION_0) - view.setUdfpsDisplayModeProvider(hbmProvider) - ViewUtils.attachView(view) - } - - @After - fun cleanup() { - ViewUtils.detachView(view) - } - - // TODO: Add test to verify view is size of screen - - @Test - fun startAndStopIllumination() { - val onDone: Runnable = mock() - view.configureDisplay(onDone) - - val illuminator = withArgCaptor<Runnable> { - verify(hbmProvider).enable(capture()) - } - - assertThat(view.isDisplayConfigured).isTrue() - verify(animationViewController).onDisplayConfiguring() - verify(animationViewController, never()).onDisplayUnconfigured() - verify(onDone, never()).run() - - // fake illumination event - illuminator.run() - waitForLooper() - verify(onDone).run() - verify(hbmProvider, never()).disable(any()) - - view.unconfigureDisplay() - assertThat(view.isDisplayConfigured).isFalse() - verify(animationViewController).onDisplayUnconfigured() - verify(hbmProvider).disable(nullable(Runnable::class.java)) - } - - private fun waitForLooper() = TestableLooper.get(this).processAllMessages() -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java index b87647ef9839..eb72f29046aa 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/doze/DozeMachineTest.java @@ -48,11 +48,11 @@ import static org.mockito.Mockito.when; import android.app.ActivityManager; import android.content.res.Configuration; import android.hardware.display.AmbientDisplayConfiguration; -import android.testing.AndroidTestingRunner; -import android.testing.UiThreadTest; +import androidx.test.annotation.UiThreadTest; import android.view.Display; import androidx.annotation.NonNull; +import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; @@ -69,7 +69,7 @@ import org.mockito.Mock; import org.mockito.MockitoAnnotations; @SmallTest -@RunWith(AndroidTestingRunner.class) +@RunWith(AndroidJUnit4.class) @UiThreadTest public class DozeMachineTest extends SysuiTestCase { diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt index ae2a9add4fb7..6fce108f35a1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest_311121830.kt @@ -20,10 +20,10 @@ import android.animation.AnimatorTestRule import android.content.Context import android.service.quicksettings.Tile import android.testing.AndroidTestingRunner -import android.testing.UiThreadTest import android.view.ContextThemeWrapper import android.view.View import android.widget.ImageView +import androidx.test.annotation.UiThreadTest import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.plugins.qs.QSTile diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java index 643debfbc810..7b24233d8603 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java @@ -17,13 +17,13 @@ import android.os.Handler; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.testing.TestableLooper; -import android.testing.UiThreadTest; import android.view.View; import android.view.Window; import android.widget.LinearLayout; import android.widget.Switch; import android.widget.TextView; +import androidx.test.annotation.UiThreadTest; import androidx.recyclerview.widget.RecyclerView; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt deleted file mode 100644 index 0d1d37af7e5b..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/core/MultiDisplayStatusBarInitializerStoreTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2024 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.core - -import android.platform.test.annotations.EnableFlags -import android.view.Display -import androidx.test.ext.junit.runners.AndroidJUnit4 -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.display.data.repository.displayRepository -import com.android.systemui.kosmos.testDispatcher -import com.android.systemui.kosmos.testScope -import com.android.systemui.kosmos.unconfinedTestDispatcher -import com.android.systemui.testKosmos -import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.test.runTest -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith - -@RunWith(AndroidJUnit4::class) -@SmallTest -@EnableFlags(StatusBarConnectedDisplays.FLAG_NAME) -class MultiDisplayStatusBarInitializerStoreTest : SysuiTestCase() { - - private val kosmos = - testKosmos().also { - // Using unconfinedTestDispatcher to avoid having to call `runCurrent` in the tests. - it.testDispatcher = it.unconfinedTestDispatcher - } - private val testScope = kosmos.testScope - private val fakeDisplayRepository = kosmos.displayRepository - private val store = kosmos.multiDisplayStatusBarInitializerStore - - @Before - fun start() { - store.start() - } - - @Before - fun addDisplays() = runBlocking { - fakeDisplayRepository.addDisplay(DEFAULT_DISPLAY_ID) - fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID) - } - - @Test - fun forDisplay_defaultDisplay_multipleCalls_returnsSameInstance() = - testScope.runTest { - val controller = store.defaultDisplay - - assertThat(store.defaultDisplay).isSameInstanceAs(controller) - } - - @Test - fun forDisplay_nonDefaultDisplay_multipleCalls_returnsSameInstance() = - testScope.runTest { - val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) - - assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isSameInstanceAs(controller) - } - - @Test - fun forDisplay_nonDefaultDisplay_afterDisplayRemoved_returnsNewInstance() = - testScope.runTest { - val controller = store.forDisplay(NON_DEFAULT_DISPLAY_ID) - - fakeDisplayRepository.removeDisplay(NON_DEFAULT_DISPLAY_ID) - fakeDisplayRepository.addDisplay(NON_DEFAULT_DISPLAY_ID) - - assertThat(store.forDisplay(NON_DEFAULT_DISPLAY_ID)).isNotSameInstanceAs(controller) - } - - @Test(expected = IllegalArgumentException::class) - fun forDisplay_nonExistingDisplayId_throws() = - testScope.runTest { store.forDisplay(NON_EXISTING_DISPLAY_ID) } - - companion object { - private const val DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY - private const val NON_DEFAULT_DISPLAY_ID = Display.DEFAULT_DISPLAY + 1 - private const val NON_EXISTING_DISPLAY_ID = Display.DEFAULT_DISPLAY + 2 - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java index 2d8e69280d30..c8ef663a4f81 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/PropertyAnimatorTest.java @@ -31,12 +31,12 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.testing.AndroidTestingRunner; -import android.testing.UiThreadTest; import android.util.FloatProperty; import android.util.Property; import android.view.View; import android.view.animation.Interpolator; +import androidx.test.annotation.UiThreadTest; import androidx.test.filters.SmallTest; import com.android.app.animation.Interpolators; @@ -270,4 +270,4 @@ public class PropertyAnimatorTest extends SysuiTestCase { PropertyAnimator.startAnimation(mView, mProperty, 200f, mAnimationProperties); assertTrue(PropertyAnimator.isAnimating(mView, mProperty)); } -}
\ No newline at end of file +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java index ed7383cacd29..e3e2491e5d52 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowTest.java @@ -612,6 +612,8 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { Assert.assertNull(child.getParent()); Assert.assertNull(child.getNotificationParent()); Assert.assertFalse(child.keepInParentForDismissAnimation()); + verify(mNotificationTestHelper.getMockLogger()) + .logCancelAppearDrawing(child.getEntry(), false); verifyNoMoreInteractions(mNotificationTestHelper.getMockLogger()); } @@ -1013,7 +1015,7 @@ public class ExpandableNotificationRowTest extends SysuiTestCase { assertThat(row.isHeadsUpAnimatingAway()).isTrue(); // on disappear animation ends - row.onAppearAnimationFinished(/* wasAppearing = */ false); + row.onAppearAnimationFinished(/* wasAppearing = */ false, /* cancelled = */ false); assertThat(row.isHeadsUpAnimatingAway()).isFalse(); } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java index 40b8cdafa813..8f64287ebd0f 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java @@ -21,7 +21,6 @@ import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.provider.Settings.Global.HEADS_UP_NOTIFICATIONS_ENABLED; import static android.provider.Settings.Global.HEADS_UP_ON; -import static com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR; import static com.android.systemui.Flags.FLAG_KEYBOARD_SHORTCUT_HELPER_REWRITE; import static com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION; import static com.android.systemui.flags.Flags.SHORTCUT_LIST_SEARCH_LAYOUT; @@ -513,10 +512,10 @@ public class CentralSurfacesImplTest extends SysuiTestCase { mLightBarController, mAutoHideController, new StatusBarInitializerImpl( - mContext.getDisplayId(), - mStatusBarWindowControllerStore, + mStatusBarWindowController, mCollapsedStatusBarFragmentProvider, - emptySet()), + emptySet() + ), mStatusBarWindowControllerStore, mStatusBarWindowStateController, new FakeStatusBarModeRepository(), @@ -859,34 +858,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testSetDozingNotUnlocking_transitionToAuthScrimmed_cancelKeyguardFadingAway() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - when(mKeyguardStateController.isKeyguardFadingAway()).thenReturn(true); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); - verify(mStatusBarKeyguardViewManager).onKeyguardFadedAway(); - } - - @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testOccludingQSNotExpanded_flagOff_transitionToAuthScrimmed() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN device occluded and panel is NOT expanded - mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE - when(mKeyguardStateController.isOccluded()).thenReturn(true); - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(false); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED)); - } - - @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testNotOccluding_QSNotExpanded_flagOn_doesNotTransitionScrimState() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); @@ -902,7 +873,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - @EnableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) public void testNotOccluding_QSExpanded_flagOn_doesTransitionScrimStateToKeyguard() { when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); @@ -918,21 +888,6 @@ public class CentralSurfacesImplTest extends SysuiTestCase { } @Test - @DisableFlags(FLAG_DEVICE_ENTRY_UDFPS_REFACTOR) - public void testOccludingQSExpanded_transitionToAuthScrimmedShade() { - when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true); - - // GIVEN device occluded and qs IS expanded - mCentralSurfaces.setBarStateForTest(SHADE); // occluding on LS has StatusBarState = SHADE - when(mKeyguardStateController.isOccluded()).thenReturn(true); - when(mNotificationPanelViewController.isPanelExpanded()).thenReturn(true); - - mCentralSurfaces.updateScrimController(); - - verify(mScrimController).legacyTransitionTo(eq(ScrimState.AUTH_SCRIMMED_SHADE)); - } - - @Test public void testEnteringGlanceableHub_updatesScrim() { // Transition to the glanceable hub. mKosmos.getCommunalRepository() diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java index e804b33db1f7..eecf36e9343b 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/ScrimControllerTest.java @@ -309,8 +309,6 @@ public class ScrimControllerTest extends SysuiTestCase { // Attach behind scrim so flows that are collecting on it start running. ViewUtils.attachView(mScrimBehind); - mScrimController.setHasBackdrop(false); - mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false); mTestScope.getTestScheduler().runCurrent(); @@ -483,47 +481,6 @@ public class ScrimControllerTest extends SysuiTestCase { } @Test - public void transitionToAod_withAodWallpaperAndLockScreenWallpaper() { - mScrimController.setHasBackdrop(true); - mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); - mTestScope.getTestScheduler().runCurrent(); - - mScrimController.legacyTransitionTo(ScrimState.AOD); - finishAnimationsImmediately(); - - assertScrimAlpha(Map.of( - mScrimInFront, TRANSPARENT, - mScrimBehind, TRANSPARENT)); - assertEquals(1f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f); - - assertScrimTinted(Map.of( - mScrimInFront, true, - mScrimBehind, true - )); - } - - @Test - public void setHasBackdrop_withAodWallpaperAndAlbumArt() { - mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(true); - mTestScope.getTestScheduler().runCurrent(); - - mScrimController.legacyTransitionTo(ScrimState.AOD); - finishAnimationsImmediately(); - mScrimController.setHasBackdrop(true); - finishAnimationsImmediately(); - - assertScrimAlpha(Map.of( - mScrimInFront, TRANSPARENT, - mScrimBehind, TRANSPARENT)); - assertEquals(1f, mScrimController.getState().getMaxLightRevealScrimAlpha(), 0f); - - assertScrimTinted(Map.of( - mScrimInFront, true, - mScrimBehind, true - )); - } - - @Test public void transitionToAod_withFrontAlphaUpdates() { // Assert that setting the AOD front scrim alpha doesn't take effect in a non-AOD state. mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); @@ -1356,7 +1313,6 @@ public class ScrimControllerTest extends SysuiTestCase { mScrimController.setScrimVisibleListener(visible -> mScrimVisibility = visible); mScrimController.attachViews(mScrimBehind, mNotificationsScrim, mScrimInFront); mScrimController.setAnimatorListener(mAnimatorListener); - mScrimController.setHasBackdrop(false); mWallpaperRepository.getWallpaperSupportsAmbientMode().setValue(false); mTestScope.getTestScheduler().runCurrent(); mScrimController.legacyTransitionTo(ScrimState.KEYGUARD); 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 e57e8d108529..15ef917343ee 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 @@ -16,7 +16,6 @@ package com.android.systemui.statusbar.phone.fragment; import static android.view.Display.DEFAULT_DISPLAY; -import static com.android.systemui.Flags.FLAG_STATUS_BAR_RON_CHIPS; import static com.android.systemui.Flags.FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS; import static com.android.systemui.Flags.FLAG_STATUS_BAR_SIMPLE_FRAGMENT; import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED; @@ -61,6 +60,7 @@ import com.android.systemui.shade.ShadeExpansionStateManager; import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor; import com.android.systemui.statusbar.CommandQueue; import com.android.systemui.statusbar.OperatorNameViewController; +import com.android.systemui.statusbar.chips.notification.shared.StatusBarNotifChips; import com.android.systemui.statusbar.disableflags.DisableFlagsLogger; import com.android.systemui.statusbar.events.SystemStatusAnimationScheduler; import com.android.systemui.statusbar.notification.icon.ui.viewbinder.NotificationIconContainerStatusBarViewBinder; @@ -633,7 +633,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags({ FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, - FLAG_STATUS_BAR_RON_CHIPS, + StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) public void hasPrimaryOngoingActivity_viewsUnchangedWhenSimpleFragmentFlagOn() { resumeAndGetFragment(); @@ -660,8 +660,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasSecondaryOngoingActivity_butRonsFlagOff_secondaryChipHidden() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasSecondaryOngoingActivity_butNotifsFlagOff_secondaryChipHidden() { resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -673,7 +673,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void hasSecondaryOngoingActivity_flagOn_secondaryChipShownAndNotificationIconsHidden() { resumeAndGetFragment(); @@ -689,8 +689,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasOngoingActivityButNotificationIconsDisabled_chipHidden_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -705,9 +705,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_ronsFlagOn() { + public void hasOngoingActivitiesButNotificationIconsDisabled_chipsHidden_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -724,8 +724,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasOngoingActivityButAlsoHun_chipHidden_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasOngoingActivityButAlsoHun_chipHidden_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -740,9 +740,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void hasOngoingActivitiesButAlsoHun_chipsHidden_ronsFlagOn() { + public void hasOngoingActivitiesButAlsoHun_chipsHidden_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); mCollapsedStatusBarViewBinder.getListener().onOngoingActivityStatusChanged( @@ -759,8 +759,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void primaryOngoingActivityEnded_chipHidden_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void primaryOngoingActivityEnded_chipHidden_notifsFlagOff() { resumeAndGetFragment(); // Ongoing activity started @@ -781,9 +781,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void primaryOngoingActivityEnded_chipHidden_ronsFlagOn() { + public void primaryOngoingActivityEnded_chipHidden_notifsFlagOn() { resumeAndGetFragment(); // Ongoing activity started @@ -804,7 +804,7 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) public void secondaryOngoingActivityEnded_chipHidden() { resumeAndGetFragment(); @@ -828,8 +828,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating fragment.enableAnimationsForTesting(); @@ -846,9 +846,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void hasOngoingActivity_hidesNotifsWithoutAnimation_ronsFlagOn() { + public void hasOngoingActivity_hidesNotifsWithoutAnimation_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // Enable animations for testing so that we can verify we still aren't animating fragment.enableAnimationsForTesting(); @@ -866,8 +866,8 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { @Test @EnableFlags(FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS) - @DisableFlags({FLAG_STATUS_BAR_RON_CHIPS, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) - public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOff() { + @DisableFlags({StatusBarNotifChips.FLAG_NAME, FLAG_STATUS_BAR_SIMPLE_FRAGMENT}) + public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOff() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN there *is* an ongoing call via old callback @@ -898,9 +898,9 @@ public class CollapsedStatusBarFragmentTest extends SysuiBaseFragmentTest { } @Test - @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, FLAG_STATUS_BAR_RON_CHIPS}) + @EnableFlags({FLAG_STATUS_BAR_SCREEN_SHARING_CHIPS, StatusBarNotifChips.FLAG_NAME}) @DisableFlags(FLAG_STATUS_BAR_SIMPLE_FRAGMENT) - public void screenSharingChipsEnabled_ignoresOngoingCallController_ronsFlagOn() { + public void screenSharingChipsEnabled_ignoresOngoingCallController_notifsFlagOn() { CollapsedStatusBarFragment fragment = resumeAndGetFragment(); // WHEN there *is* an ongoing call via old callback 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 5996ef13a463..1ceb20adeebd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java @@ -198,6 +198,8 @@ import com.android.wm.shell.taskview.TaskView; import com.android.wm.shell.taskview.TaskViewTransitions; import com.android.wm.shell.transition.Transitions; +import kotlin.Lazy; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -216,7 +218,6 @@ import java.util.List; import java.util.Optional; import java.util.concurrent.Executor; -import kotlin.Lazy; import platform.test.runner.parameterized.ParameterizedAndroidJunit4; import platform.test.runner.parameterized.Parameters; @@ -2481,7 +2482,7 @@ public class BubblesTest extends SysuiTestCase { mEntryListener.onEntryAdded(mRow); - verify(mBubbleLogger).log(argThat(b -> b.getKey().equals(mRow.getKey())), + verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()), eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED)); } @@ -2498,10 +2499,25 @@ public class BubblesTest extends SysuiTestCase { NotificationEntryHelper.modifyRanking(mRow).setTextChanged(true).build(); mEntryListener.onEntryUpdated(mRow, /* fromSystem= */ true); - verify(mBubbleLogger).log(argThat(b -> b.getKey().equals(mRow.getKey())), + verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()), eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_UPDATED)); } + @EnableFlags(FLAG_ENABLE_BUBBLE_BAR) + @Test + public void testEventLogging_bubbleBar_dragBubbleToDismiss() { + mBubbleProperties.mIsBubbleBarEnabled = true; + mPositioner.setIsLargeScreen(true); + FakeBubbleStateListener bubbleStateListener = new FakeBubbleStateListener(); + mBubbleController.registerBubbleStateListener(bubbleStateListener); + + mEntryListener.onEntryAdded(mRow); + mBubbleController.dragBubbleToDismiss(mRow.getKey(), 1L); + + verify(mBubbleLogger).log(eqBubbleWithKey(mRow.getKey()), + eq(BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE)); + } + /** Creates a bubble using the userId and package. */ private Bubble createBubble(int userId, String pkg) { final UserHandle userHandle = new UserHandle(userId); @@ -2687,6 +2703,10 @@ public class BubblesTest extends SysuiTestCase { assertThat(mSysUiStateBubblesManageMenuExpanded).isEqualTo(manageMenuExpanded); } + private Bubble eqBubbleWithKey(String key) { + return argThat(b -> b.getKey().equals(key)); + } + private static class FakeBubbleStateListener implements Bubbles.BubbleStateListener { int mStateChangeCalls = 0; diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt index baaf60447cf9..703e2d1db200 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt @@ -49,8 +49,6 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos private val _isAlternateBouncerVisible = MutableStateFlow(false) override val alternateBouncerVisible = _isAlternateBouncerVisible.asStateFlow() override var lastAlternateBouncerVisibleTime: Long = 0L - private val _isAlternateBouncerUIAvailable = MutableStateFlow<Boolean>(false) - override val alternateBouncerUIAvailable = _isAlternateBouncerUIAvailable.asStateFlow() override val lastShownSecurityMode: MutableStateFlow<KeyguardSecurityModel.SecurityMode> = MutableStateFlow(KeyguardSecurityModel.SecurityMode.Invalid) override var bouncerDismissActionModel: BouncerDismissActionModel? = null @@ -63,10 +61,6 @@ class FakeKeyguardBouncerRepository @Inject constructor() : KeyguardBouncerRepos _isAlternateBouncerVisible.value = isVisible } - override fun setAlternateBouncerUIAvailable(isAvailable: Boolean) { - _isAlternateBouncerUIAvailable.value = isAvailable - } - override fun setPrimaryShow(isShowing: Boolean) { _primaryBouncerShow.value = isShowing } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt index 63323b239a78..8bbb8a0d320e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/AlternateBouncerInteractorKosmos.kt @@ -16,33 +16,23 @@ package com.android.systemui.bouncer.domain.interactor -import com.android.keyguard.keyguardUpdateMonitor import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository import com.android.systemui.bouncer.data.repository.keyguardBouncerRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryBiometricsAllowedInteractor -import com.android.systemui.deviceentry.shared.DeviceEntryUdfpsRefactor -import com.android.systemui.keyguard.data.repository.biometricSettingsRepository import com.android.systemui.keyguard.data.repository.deviceEntryFaceAuthRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.statusbar.statusBarStateController import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.statusbar.policy.keyguardStateController -import com.android.systemui.util.mockito.whenever import com.android.systemui.util.time.systemClock val Kosmos.alternateBouncerInteractor: AlternateBouncerInteractor by Kosmos.Fixture { AlternateBouncerInteractor( - statusBarStateController = statusBarStateController, - keyguardStateController = keyguardStateController, bouncerRepository = keyguardBouncerRepository, fingerprintPropertyRepository = fingerprintPropertyRepository, - biometricSettingsRepository = biometricSettingsRepository, systemClock = systemClock, - keyguardUpdateMonitor = keyguardUpdateMonitor, deviceEntryBiometricsAllowedInteractor = { deviceEntryBiometricsAllowedInteractor }, keyguardInteractor = { keyguardInteractor }, keyguardTransitionInteractor = { keyguardTransitionInteractor }, @@ -54,21 +44,9 @@ val Kosmos.alternateBouncerInteractor: AlternateBouncerInteractor by fun Kosmos.givenCanShowAlternateBouncer() { this.givenAlternateBouncerSupported() this.keyguardBouncerRepository.setPrimaryShow(false) - this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) - this.biometricSettingsRepository.setIsFingerprintAuthCurrentlyAllowed(true) this.deviceEntryFaceAuthRepository.setLockedOut(false) - whenever(this.keyguardUpdateMonitor.isFingerprintLockedOut).thenReturn(false) - whenever(this.keyguardStateController.isUnlocked).thenReturn(false) } fun Kosmos.givenAlternateBouncerSupported() { - if (DeviceEntryUdfpsRefactor.isEnabled) { - this.fingerprintPropertyRepository.supportsUdfps() - } else { - this.keyguardBouncerRepository.setAlternateBouncerUIAvailable(true) - } -} - -fun Kosmos.givenCannotShowAlternateBouncer() { - this.biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(false) + this.fingerprintPropertyRepository.supportsUdfps() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt index 3087d01a2479..77ec83871016 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/domain/interactor/BouncerActionButtonInteractorKosmos.kt @@ -23,6 +23,7 @@ import com.android.internal.logging.metricsLogger import com.android.internal.util.emergencyAffordanceManager import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.bouncer.data.repository.emergencyServicesRepository +import com.android.systemui.haptics.msdl.bouncerHapticPlayer import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testDispatcher @@ -52,5 +53,6 @@ val Kosmos.bouncerActionButtonInteractor by Fixture { metricsLogger = metricsLogger, dozeLogger = mock(), sceneInteractor = { sceneInteractor }, + bouncerHapticPlayer, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt new file mode 100644 index 000000000000..e3797260ed6d --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/PerDisplayStoreKosmos.kt @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2024 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.display.data.repository + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.applicationCoroutineScope +import kotlinx.coroutines.CoroutineScope + +class FakePerDisplayStore( + backgroundApplicationScope: CoroutineScope, + displayRepository: DisplayRepository, +) : PerDisplayStoreImpl<TestPerDisplayInstance>(backgroundApplicationScope, displayRepository) { + + val removalActions = mutableListOf<TestPerDisplayInstance>() + + override fun createInstanceForDisplay(displayId: Int): TestPerDisplayInstance { + return TestPerDisplayInstance(displayId) + } + + override val instanceClass = TestPerDisplayInstance::class.java + + override suspend fun onDisplayRemovalAction(instance: TestPerDisplayInstance) { + removalActions += instance + } +} + +data class TestPerDisplayInstance(val displayId: Int) + +val Kosmos.fakePerDisplayStore by + Kosmos.Fixture { + FakePerDisplayStore( + backgroundApplicationScope = applicationCoroutineScope, + displayRepository = displayRepository, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt index c0152b26d7a3..41402badf141 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/flags/EnableSceneContainer.kt @@ -17,7 +17,6 @@ package com.android.systemui.flags import android.platform.test.annotations.EnableFlags -import com.android.systemui.Flags.FLAG_DEVICE_ENTRY_UDFPS_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR import com.android.systemui.Flags.FLAG_KEYGUARD_WM_STATE_REFACTOR import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT @@ -36,7 +35,6 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER FLAG_NOTIFICATION_AVALANCHE_THROTTLE_HUN, FLAG_PREDICTIVE_BACK_SYSUI, FLAG_SCENE_CONTAINER, - FLAG_DEVICE_ENTRY_UDFPS_REFACTOR, ) @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorKosmos.kt index 4ccee6f52fa2..2aa27444a058 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorKosmos.kt @@ -22,18 +22,16 @@ import com.android.systemui.bouncer.domain.interactor.alternateBouncerInteractor import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.kosmos.Kosmos -import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor val Kosmos.deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor by Kosmos.Fixture { DeviceEntrySideFpsOverlayInteractor( - applicationScope = testScope.backgroundScope, context = applicationContext, deviceEntryFingerprintAuthRepository = deviceEntryFingerprintAuthRepository, sceneInteractor = sceneInteractor, primaryBouncerInteractor = primaryBouncerInteractor, alternateBouncerInteractor = alternateBouncerInteractor, - keyguardUpdateMonitor = keyguardUpdateMonitor + keyguardUpdateMonitor = keyguardUpdateMonitor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/hearingdevices/HearingDevicesTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/hearingdevices/HearingDevicesTileKosmos.kt new file mode 100644 index 000000000000..e16756befb03 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/hearingdevices/HearingDevicesTileKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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.hearingdevices + +import com.android.systemui.accessibility.qs.QSAccessibilityModule +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger + +val Kosmos.qsHearingDevicesTileConfig by + Kosmos.Fixture { QSAccessibilityModule.provideHearingDevicesTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt index c0d65a076ca0..2316a2fdcd2b 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ron/demo/ui/viewmodel/DemoRonChipViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/demo/ui/viewmodel/DemoNotifChipViewModelKosmos.kt @@ -14,16 +14,16 @@ * limitations under the License. */ -package com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel +package com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel import android.content.packageManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.statusbar.commandline.commandRegistry import com.android.systemui.util.time.fakeSystemClock -val Kosmos.demoRonChipViewModel: DemoRonChipViewModel by +val Kosmos.demoNotifChipViewModel: DemoNotifChipViewModel by Kosmos.Fixture { - DemoRonChipViewModel( + DemoNotifChipViewModel( commandRegistry = commandRegistry, packageManager = packageManager, systemClock = fakeSystemClock, diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt new file mode 100644 index 000000000000..af24c371d62b --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/notification/ui/viewmodel/NotifChipsViewModelKosmos.kt @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2024 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.chips.notification.ui.viewmodel + +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor + +val Kosmos.notifChipsViewModel: NotifChipsViewModel by + Kosmos.Fixture { NotifChipsViewModel(activeNotificationsInteractor) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt index 5382c1c4b8d0..0300bf4636ea 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/chips/ui/viewmodel/OngoingActivityChipsViewModelKosmos.kt @@ -20,7 +20,8 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.chips.call.ui.viewmodel.callChipViewModel import com.android.systemui.statusbar.chips.casttootherdevice.ui.viewmodel.castToOtherDeviceChipViewModel -import com.android.systemui.statusbar.chips.ron.demo.ui.viewmodel.demoRonChipViewModel +import com.android.systemui.statusbar.chips.notification.demo.ui.viewmodel.demoNotifChipViewModel +import com.android.systemui.statusbar.chips.notification.ui.viewmodel.notifChipsViewModel import com.android.systemui.statusbar.chips.screenrecord.ui.viewmodel.screenRecordChipViewModel import com.android.systemui.statusbar.chips.sharetoapp.ui.viewmodel.shareToAppChipViewModel import com.android.systemui.statusbar.chips.statusBarChipsLogger @@ -33,7 +34,8 @@ val Kosmos.ongoingActivityChipsViewModel: OngoingActivityChipsViewModel by shareToAppChipViewModel = shareToAppChipViewModel, castToOtherDeviceChipViewModel = castToOtherDeviceChipViewModel, callChipViewModel = callChipViewModel, - demoRonChipViewModel = demoRonChipViewModel, + notifChipsViewModel = notifChipsViewModel, + demoNotifChipViewModel = demoNotifChipViewModel, logger = statusBarChipsLogger, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt index edd660490e4d..9fa3abfdc95c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializer.kt @@ -19,11 +19,18 @@ package com.android.systemui.statusbar.core import com.android.systemui.statusbar.core.StatusBarInitializer.OnStatusBarViewUpdatedListener import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions import com.android.systemui.statusbar.phone.PhoneStatusBarViewController +import org.mockito.kotlin.mock -class FakeStatusBarInitializer( - private val statusBarViewController: PhoneStatusBarViewController, - private val statusBarTransitions: PhoneStatusBarTransitions, -) : StatusBarInitializer { +class FakeStatusBarInitializer : StatusBarInitializer { + + val statusBarViewController = mock<PhoneStatusBarViewController>() + val statusBarTransitions = mock<PhoneStatusBarTransitions>() + + var startedByCoreStartable: Boolean = false + private set + + var initializedByCentralSurfaces: Boolean = false + private set override var statusBarViewUpdatedListener: OnStatusBarViewUpdatedListener? = null set(value) { @@ -31,5 +38,11 @@ class FakeStatusBarInitializer( value?.onStatusBarViewUpdated(statusBarViewController, statusBarTransitions) } - override fun initializeStatusBar() {} + override fun initializeStatusBar() { + initializedByCentralSurfaces = true + } + + override fun start() { + startedByCoreStartable = true + } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt index 73ed228f5aaa..8c218be6c982 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerFactory.kt @@ -16,14 +16,11 @@ package com.android.systemui.statusbar.core -import com.android.systemui.statusbar.phone.PhoneStatusBarTransitions -import com.android.systemui.statusbar.phone.PhoneStatusBarViewController +import com.android.systemui.statusbar.window.StatusBarWindowController -class FakeStatusBarInitializerFactory( - private val statusBarViewController: PhoneStatusBarViewController, - private val statusBarTransitions: PhoneStatusBarTransitions, -) : StatusBarInitializer.Factory { +class FakeStatusBarInitializerFactory() : StatusBarInitializer.Factory { - override fun create(displayId: Int): StatusBarInitializer = - FakeStatusBarInitializer(statusBarViewController, statusBarTransitions) + override fun create( + statusBarWindowController: StatusBarWindowController + ): StatusBarInitializer = FakeStatusBarInitializer() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerStore.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerStore.kt new file mode 100644 index 000000000000..0c2cba981824 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarInitializerStore.kt @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2024 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.core + +import android.view.Display + +class FakeStatusBarInitializerStore : StatusBarInitializerStore { + + private val initializers = mutableMapOf<Int, FakeStatusBarInitializer>() + + override val defaultDisplay: FakeStatusBarInitializer + get() = forDisplay(Display.DEFAULT_DISPLAY) + + override fun forDisplay(displayId: Int): FakeStatusBarInitializer { + return initializers.computeIfAbsent(displayId) { FakeStatusBarInitializer() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt new file mode 100644 index 000000000000..9197dcdc3f68 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/FakeStatusBarOrchestratorFactory.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2024 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.core + +import com.android.systemui.statusbar.data.repository.StatusBarModePerDisplayRepository +import com.android.systemui.statusbar.window.StatusBarWindowController +import com.android.systemui.statusbar.window.data.repository.StatusBarWindowStatePerDisplayRepository +import kotlinx.coroutines.CoroutineScope +import org.mockito.kotlin.mock + +class FakeStatusBarOrchestratorFactory : StatusBarOrchestrator.Factory { + + private val createdOrchestrators = mutableMapOf<Int, StatusBarOrchestrator>() + + fun createdOrchestratorForDisplay(displayId: Int): StatusBarOrchestrator? = + createdOrchestrators[displayId] + + override fun create( + displayId: Int, + displayScope: CoroutineScope, + statusBarWindowStateRepository: StatusBarWindowStatePerDisplayRepository, + statusBarModeRepository: StatusBarModePerDisplayRepository, + statusBarInitializer: StatusBarInitializer, + statusBarWindowController: StatusBarWindowController, + ): StatusBarOrchestrator = + mock<StatusBarOrchestrator>().also { createdOrchestrators[displayId] = it } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt index 7ad715ba5a89..303529b7f7b0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarInitializerKosmos.kt @@ -19,20 +19,13 @@ package com.android.systemui.statusbar.core import com.android.systemui.display.data.repository.displayRepository import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope -import com.android.systemui.statusbar.phone.phoneStatusBarTransitions -import com.android.systemui.statusbar.phone.phoneStatusBarViewController +import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore -val Kosmos.fakeStatusBarInitializer by - Kosmos.Fixture { - FakeStatusBarInitializer(phoneStatusBarViewController, phoneStatusBarTransitions) - } +val Kosmos.fakeStatusBarInitializer by Kosmos.Fixture { FakeStatusBarInitializer() } var Kosmos.statusBarInitializer by Kosmos.Fixture { fakeStatusBarInitializer } -val Kosmos.fakeStatusBarInitializerFactory by - Kosmos.Fixture { - FakeStatusBarInitializerFactory(phoneStatusBarViewController, phoneStatusBarTransitions) - } +val Kosmos.fakeStatusBarInitializerFactory by Kosmos.Fixture { FakeStatusBarInitializerFactory() } var Kosmos.statusBarInitializerFactory: StatusBarInitializer.Factory by Kosmos.Fixture { fakeStatusBarInitializerFactory } @@ -41,7 +34,13 @@ val Kosmos.multiDisplayStatusBarInitializerStore by Kosmos.Fixture { MultiDisplayStatusBarInitializerStore( applicationCoroutineScope, - fakeStatusBarInitializerFactory, displayRepository, + fakeStatusBarInitializerFactory, + fakeStatusBarWindowControllerStore, ) } + +val Kosmos.fakeStatusBarInitializerStore by Kosmos.Fixture { FakeStatusBarInitializerStore() } + +var Kosmos.statusBarInitializerStore: StatusBarInitializerStore by + Kosmos.Fixture { fakeStatusBarInitializerStore } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt index 54de293b8911..87f7142b8817 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/core/StatusBarOrchestratorKosmos.kt @@ -16,7 +16,11 @@ package com.android.systemui.statusbar.core +import android.content.testableContext import com.android.systemui.bouncer.domain.interactor.primaryBouncerInteractor +import com.android.systemui.display.data.repository.displayRepository +import com.android.systemui.display.data.repository.displayScopeRepository +import com.android.systemui.dump.dumpManager import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.mockDemoModeController @@ -24,20 +28,25 @@ import com.android.systemui.plugins.mockPluginDependencyProvider import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.shade.mockNotificationShadeWindowViewController import com.android.systemui.shade.mockShadeSurface -import com.android.systemui.statusbar.data.repository.fakeStatusBarModeRepository +import com.android.systemui.statusbar.data.repository.fakeStatusBarModePerDisplayRepository +import com.android.systemui.statusbar.data.repository.statusBarModeRepository import com.android.systemui.statusbar.mockNotificationRemoteInputManager import com.android.systemui.statusbar.phone.mockAutoHideController +import com.android.systemui.statusbar.window.data.repository.fakeStatusBarWindowStatePerDisplayRepository import com.android.systemui.statusbar.window.data.repository.statusBarWindowStateRepositoryStore -import com.android.systemui.statusbar.window.fakeStatusBarWindowControllerStore +import com.android.systemui.statusbar.window.fakeStatusBarWindowController +import com.android.systemui.statusbar.window.statusBarWindowControllerStore import com.android.wm.shell.bubbles.bubblesOptional val Kosmos.statusBarOrchestrator by Kosmos.Fixture { StatusBarOrchestrator( + testableContext.displayId, applicationCoroutineScope, + fakeStatusBarWindowStatePerDisplayRepository, + fakeStatusBarModePerDisplayRepository, fakeStatusBarInitializer, - fakeStatusBarModeRepository, - fakeStatusBarWindowControllerStore, + fakeStatusBarWindowController, mockDemoModeController, mockPluginDependencyProvider, mockAutoHideController, @@ -45,8 +54,28 @@ val Kosmos.statusBarOrchestrator by { mockNotificationShadeWindowViewController }, mockShadeSurface, bubblesOptional, - statusBarWindowStateRepositoryStore, + dumpManager, powerInteractor, primaryBouncerInteractor, ) } + +val Kosmos.fakeStatusBarOrchestratorFactory by Kosmos.Fixture { FakeStatusBarOrchestratorFactory() } + +var Kosmos.statusBarOrchestratorFactory: StatusBarOrchestrator.Factory by + Kosmos.Fixture { fakeStatusBarOrchestratorFactory } + +val Kosmos.multiDisplayStatusBarStarter by + Kosmos.Fixture { + MultiDisplayStatusBarStarter( + applicationCoroutineScope, + displayScopeRepository, + statusBarOrchestratorFactory, + statusBarWindowStateRepositoryStore, + statusBarModeRepository, + displayRepository, + statusBarInitializerStore, + statusBarWindowControllerStore, + statusBarInitializerStore, + ) + } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt index 60690838fcdf..285cebb96cae 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/FakeStatusBarModeRepository.kt @@ -20,7 +20,6 @@ import android.view.Display import com.android.systemui.dagger.SysUISingleton import com.android.systemui.statusbar.data.model.StatusBarAppearance import com.android.systemui.statusbar.data.model.StatusBarMode -import com.google.common.truth.Truth.assertThat import dagger.Binds import dagger.Module import javax.inject.Inject @@ -37,7 +36,6 @@ class FakeStatusBarModeRepository @Inject constructor() : StatusBarModeRepositor FakeStatusBarModePerDisplayRepository() override fun forDisplay(displayId: Int): FakeStatusBarModePerDisplayRepository { - assertThat(displayId).isEqualTo(DISPLAY_ID) return defaultDisplay } } @@ -51,6 +49,7 @@ class FakeStatusBarModePerDisplayRepository : StatusBarModePerDisplayRepository override fun showTransient() { isTransientShown.value = true } + override fun clearTransient() { isTransientShown.value = false } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt index 0f2b477db67a..12db2f74197d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/data/repository/StatusBarModeRepositoryKosmos.kt @@ -18,6 +18,9 @@ package com.android.systemui.statusbar.data.repository import com.android.systemui.kosmos.Kosmos +val Kosmos.fakeStatusBarModePerDisplayRepository by + Kosmos.Fixture { FakeStatusBarModePerDisplayRepository() } + val Kosmos.statusBarModeRepository: StatusBarModeRepositoryStore by Kosmos.Fixture { fakeStatusBarModeRepository } val Kosmos.fakeStatusBarModeRepository by Kosmos.Fixture { FakeStatusBarModeRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt index fc4f05df26ed..7f4c670b05aa 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt @@ -69,6 +69,9 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag +import com.android.systemui.statusbar.notification.row.icon.AppIconProviderImpl +import com.android.systemui.statusbar.notification.row.icon.NotificationIconStyleProviderImpl +import com.android.systemui.statusbar.notification.row.icon.NotificationRowIconViewInflaterFactory import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger import com.android.systemui.statusbar.phone.KeyguardBypassController @@ -99,7 +102,7 @@ import org.mockito.Mockito class ExpandableNotificationRowBuilder( private val context: Context, dependency: TestableDependency, - private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic() + private val featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic(), ) { private val mMockLogger: ExpandableNotificationRowLogger @@ -161,21 +164,21 @@ class ExpandableNotificationRowBuilder( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_SHOW_IN_HEADS_UP, "true", - true + true, ) setProperty( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_ENABLED, "true", - true + true, ) setProperty( DeviceConfig.NAMESPACE_SYSTEMUI, SystemUiDeviceConfigFlags.SSIN_REQUIRES_TARGETING_P, "false", - true + true, ) - } + }, ) val remoteViewsFactories = getNotifRemoteViewsFactoryContainer(featureFlags) val remoteInputManager = Mockito.mock(NotificationRemoteInputManager::class.java, STUB_ONLY) @@ -192,21 +195,21 @@ class ExpandableNotificationRowBuilder( Mockito.mock(KeyguardDismissUtil::class.java, STUB_ONLY), remoteInputManager = remoteInputManager, smartReplyController = mSmartReplyController, - context = context + context = context, ), smartActionsInflater = SmartActionInflaterImpl( constants = mSmartReplyConstants, activityStarter = Mockito.mock(ActivityStarter::class.java, STUB_ONLY), smartReplyController = mSmartReplyController, - headsUpManager = mHeadsUpManager - ) + headsUpManager = mHeadsUpManager, + ), ) val notifLayoutInflaterFactoryProvider = object : NotifLayoutInflaterFactory.Provider { override fun provide( row: ExpandableNotificationRow, - layoutType: Int + layoutType: Int, ): NotifLayoutInflaterFactory = NotifLayoutInflaterFactory(row, layoutType, remoteViewsFactories) } @@ -270,14 +273,14 @@ class ExpandableNotificationRowBuilder( whenever( mOnUserInteractionCallback.registerFutureDismissal( ArgumentMatchers.any(), - ArgumentMatchers.anyInt() + ArgumentMatchers.anyInt(), ) ) .thenReturn(mFutureDismissalRunnable) } private fun getNotifRemoteViewsFactoryContainer( - featureFlags: FeatureFlags, + featureFlags: FeatureFlags ): NotifRemoteViewsFactoryContainer { return NotifRemoteViewsFactoryContainerImpl( featureFlags, @@ -285,6 +288,10 @@ class ExpandableNotificationRowBuilder( BigPictureLayoutInflaterFactory(), NotificationOptimizedLinearLayoutFactory(), { Mockito.mock(NotificationViewFlipperFactory::class.java) }, + NotificationRowIconViewInflaterFactory( + AppIconProviderImpl(context), + NotificationIconStyleProviderImpl(), + ), ) } @@ -293,7 +300,7 @@ class ExpandableNotificationRowBuilder( NotificationChannel( notification.channelId, notification.channelId, - NotificationManager.IMPORTANCE_DEFAULT + NotificationManager.IMPORTANCE_DEFAULT, ) channel.isBlockable = true val entry = @@ -321,7 +328,7 @@ class ExpandableNotificationRowBuilder( private fun generateRow( entry: NotificationEntry, - @InflationFlag extraInflationFlags: Int + @InflationFlag extraInflationFlags: Int, ): ExpandableNotificationRow { // NOTE: This flag is read when the ExpandableNotificationRow is inflated, so it needs to be // set, but we do not want to override an existing value that is needed by a specific test. @@ -329,7 +336,7 @@ class ExpandableNotificationRowBuilder( val rowInflaterTask = RowInflaterTask( mFakeSystemClock, - Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY) + Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY), ) val row = rowInflaterTask.inflateSynchronously(context, null, entry) @@ -364,7 +371,7 @@ class ExpandableNotificationRowBuilder( mSmartReplyController, featureFlags, Mockito.mock(IStatusBarService::class.java, STUB_ONLY), - Mockito.mock(UiEventLogger::class.java, STUB_ONLY) + Mockito.mock(UiEventLogger::class.java, STUB_ONLY), ) row.setAboveShelfChangedListener { aboveShelf: Boolean -> } mBindStage.getStageParams(entry).requireContentViews(extraInflationFlags) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt new file mode 100644 index 000000000000..08c6bbab6dd6 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/AppIconProviderKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 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.row.icon + +import android.content.applicationContext +import com.android.systemui.kosmos.Kosmos + +val Kosmos.appIconProvider by Kosmos.Fixture { AppIconProviderImpl(applicationContext) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt new file mode 100644 index 000000000000..611c90a6f4e8 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/icon/NotificationIconStyleProviderKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2024 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.row.icon + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.notificationIconStyleProvider by Kosmos.Fixture { NotificationIconStyleProviderImpl() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/BluetoothControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/BluetoothControllerKosmos.kt new file mode 100644 index 000000000000..14f4d75d5647 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/BluetoothControllerKosmos.kt @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2024 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.policy + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.fakeBluetoothController by Kosmos.Fixture { FakeBluetoothController() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeBluetoothController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeBluetoothController.kt new file mode 100644 index 000000000000..4876cd8a6086 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/FakeBluetoothController.kt @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2024 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.policy + +import android.bluetooth.BluetoothAdapter +import com.android.internal.annotations.VisibleForTesting +import com.android.settingslib.bluetooth.CachedBluetoothDevice +import com.android.systemui.statusbar.policy.BluetoothController.Callback +import java.io.PrintWriter +import java.util.Collections +import java.util.concurrent.Executor + +class FakeBluetoothController : BluetoothController { + + private var callbacks = mutableListOf<Callback>() + private var enabled = false + + override fun addCallback(listener: Callback) { + callbacks += listener + listener.onBluetoothStateChange(isBluetoothEnabled) + } + + override fun removeCallback(listener: Callback) { + callbacks -= listener + } + + override fun dump(pw: PrintWriter, args: Array<out String>) {} + + override fun isBluetoothSupported(): Boolean = false + + override fun isBluetoothEnabled(): Boolean = enabled + + override fun getBluetoothState(): Int = 0 + + override fun isBluetoothConnected(): Boolean = false + + override fun isBluetoothConnecting(): Boolean = false + + override fun isBluetoothAudioProfileOnly(): Boolean = false + + override fun isBluetoothAudioActive(): Boolean = false + + override fun getConnectedDeviceName(): String? = null + + override fun setBluetoothEnabled(enabled: Boolean) { + this.enabled = enabled + callbacks.forEach { it.onBluetoothStateChange(enabled) } + } + + override fun canConfigBluetooth(): Boolean = false + + override fun getConnectedDevices(): MutableList<CachedBluetoothDevice> = Collections.emptyList() + + override fun addOnMetadataChangedListener( + device: CachedBluetoothDevice?, + executor: Executor?, + listener: BluetoothAdapter.OnMetadataChangedListener?, + ) {} + + override fun removeOnMetadataChangedListener( + device: CachedBluetoothDevice?, + listener: BluetoothAdapter.OnMetadataChangedListener?, + ) {} + + /** Trigger the [Callback.onBluetoothDevicesChanged] method for all registered callbacks. */ + @VisibleForTesting + fun onBluetoothDevicesChanged() { + callbacks.forEach { it.onBluetoothDevicesChanged() } + } +} diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt index 2205a3b6e084..cbaf2bd65083 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/window/data/repository/StatusBarWindowStateRepositoryStoreKosmos.kt @@ -21,6 +21,9 @@ import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.settings.displayTracker import com.android.systemui.statusbar.commandQueue +val Kosmos.fakeStatusBarWindowStatePerDisplayRepository by + Kosmos.Fixture { FakeStatusBarWindowStatePerDisplayRepository() } + val Kosmos.fakeStatusBarWindowStateRepositoryStore by Kosmos.Fixture { FakeStatusBarWindowStateRepositoryStore() } diff --git a/packages/Vcn/OWNERS b/packages/Vcn/OWNERS new file mode 100644 index 000000000000..ff2146e76b47 --- /dev/null +++ b/packages/Vcn/OWNERS @@ -0,0 +1,6 @@ +evitayan@google.com +nharold@google.com +benedictwong@google.com #{LAST_RESORT_SUGGESTION} +yangji@google.com #{LAST_RESORT_SUGGESTION} + +include platform/packages/modules/common:/MODULES_OWNERS
\ No newline at end of file diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp index bfa801f30955..8e884bc3484b 100644 --- a/ravenwood/Android.bp +++ b/ravenwood/Android.bp @@ -372,6 +372,7 @@ java_library { android_ravenwood_libgroup { name: "ravenwood-runtime", data: [ + ":system-build.prop", ":framework-res", ":ravenwood-empty-res", ":framework-platform-compat-config", diff --git a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java index 5894476b9201..afa7a6c51abb 100644 --- a/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java +++ b/ravenwood/junit-impl-src/android/platform/test/ravenwood/RavenwoodRuntimeEnvironmentController.java @@ -20,12 +20,14 @@ import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_INST_R import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_RESOURCE_APK; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERBOSE_LOGGING; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_VERSION_JAVA_SYSPROP; +import static com.android.ravenwood.common.RavenwoodCommonUtils.getRavenwoodRuntimePath; import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import android.annotation.Nullable; import android.app.ActivityManager; import android.app.Instrumentation; import android.app.ResourcesManager; @@ -81,6 +83,8 @@ public class RavenwoodRuntimeEnvironmentController { private static final String MAIN_THREAD_NAME = "RavenwoodMain"; private static final String RAVENWOOD_NATIVE_SYSPROP_NAME = "ravenwood_sysprop"; private static final String RAVENWOOD_NATIVE_RUNTIME_NAME = "ravenwood_runtime"; + private static final String RAVENWOOD_BUILD_PROP = + getRavenwoodRuntimePath() + "ravenwood-data/build.prop"; /** * When enabled, attempt to dump all thread stacks just before we hit the @@ -158,7 +162,8 @@ public class RavenwoodRuntimeEnvironmentController { System.load(RavenwoodCommonUtils.getJniLibraryPath(RAVENWOOD_NATIVE_RUNTIME_NAME)); // Do the basic set up for the android sysprops. - setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES); + RavenwoodSystemProperties.initialize(RAVENWOOD_BUILD_PROP); + setSystemProperties(null); // Make sure libandroid_runtime is loaded. RavenwoodNativeLoader.loadFrameworkNativeCode(); @@ -220,14 +225,9 @@ public class RavenwoodRuntimeEnvironmentController { ActivityManager.init$ravenwood(config.mCurrentUser); - final HandlerThread main; - if (config.mProvideMainThread) { - main = new HandlerThread(MAIN_THREAD_NAME); - main.start(); - Looper.setMainLooperForTest(main.getLooper()); - } else { - main = null; - } + final var main = new HandlerThread(MAIN_THREAD_NAME); + main.start(); + Looper.setMainLooperForTest(main.getLooper()); final boolean isSelfInstrumenting = Objects.equals(config.mTestPackageName, config.mTargetPackageName); @@ -319,17 +319,15 @@ public class RavenwoodRuntimeEnvironmentController { } sMockUiAutomation.dropShellPermissionIdentity(); - if (config.mProvideMainThread) { - Looper.getMainLooper().quit(); - Looper.clearMainLooperForTest(); - } + Looper.getMainLooper().quit(); + Looper.clearMainLooperForTest(); ActivityManager.reset$ravenwood(); LocalServices.removeAllServicesForTest(); ServiceManager.reset$ravenwood(); - setSystemProperties(RavenwoodSystemProperties.DEFAULT_VALUES); + setSystemProperties(null); if (sOriginalIdentityToken != -1) { Binder.restoreCallingIdentity(sOriginalIdentityToken); } @@ -388,9 +386,10 @@ public class RavenwoodRuntimeEnvironmentController { /** * Set the current configuration to the actual SystemProperties. */ - private static void setSystemProperties(RavenwoodSystemProperties systemProperties) { + private static void setSystemProperties(@Nullable RavenwoodSystemProperties systemProperties) { SystemProperties.clearChangeCallbacksForTest(); RavenwoodRuntimeNative.clearSystemProperties(); + if (systemProperties == null) systemProperties = new RavenwoodSystemProperties(); sProps = new RavenwoodSystemProperties(systemProperties, true); for (var entry : systemProperties.getValues().entrySet()) { RavenwoodRuntimeNative.setSystemProperty(entry.getKey(), entry.getValue()); diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java index 446f819ad41b..1f6e11dd5cf2 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodConfig.java @@ -152,7 +152,10 @@ public final class RavenwoodConfig { /** * Configure a "main" thread to be available for the duration of the test, as defined * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. + * + * @deprecated */ + @Deprecated public Builder setProvideMainThread(boolean provideMainThread) { mConfig.mProvideMainThread = provideMainThread; return this; diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 4196d8e22610..93a6806ed1f4 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -139,7 +139,10 @@ public final class RavenwoodRule implements TestRule { /** * Configure a "main" thread to be available for the duration of the test, as defined * by {@code Looper.getMainLooper()}. Has no effect on non-Ravenwood environments. + * + * @deprecated */ + @Deprecated public Builder setProvideMainThread(boolean provideMainThread) { mBuilder.setProvideMainThread(provideMainThread); return this; diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java index f1e1ef672871..ced151927fdc 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodSystemProperties.java @@ -18,12 +18,94 @@ package android.platform.test.ravenwood; import static com.android.ravenwood.common.RavenwoodCommonUtils.RAVENWOOD_SYSPROP; +import com.android.ravenwood.common.RavenwoodCommonUtils; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; public class RavenwoodSystemProperties { + private static final String TAG = "RavenwoodSystemProperties"; + + private static final Map<String, String> sDefaultValues = new HashMap<>(); + + private static final String[] PARTITIONS = { + "bootimage", + "odm", + "product", + "system", + "system_ext", + "vendor", + "vendor_dlkm", + }; + + /** + * More info about property file loading: system/core/init/property_service.cpp + * In the following logic, the only partition we would need to consider is "system", + * since we only read from system-build.prop + */ + static void initialize(String propFile) { + // Load all properties from build.prop + try { + Files.readAllLines(Path.of(propFile)).stream() + .map(String::trim) + .filter(s -> !s.startsWith("#")) + .map(s -> s.split("\\s*=\\s*", 2)) + .filter(a -> a.length == 2) + .forEach(a -> sDefaultValues.put(a[0], a[1])); + } catch (IOException e) { + throw new RuntimeException(e); + } + + // If ro.product.${name} is not set, derive from ro.product.${partition}.${name} + // If ro.product.cpu.abilist* is not set, derive from ro.${partition}.product.cpu.abilist* + for (var entry : Set.copyOf(sDefaultValues.entrySet())) { + final String key; + if (entry.getKey().startsWith("ro.product.system.")) { + var name = entry.getKey().substring(18); + key = "ro.product." + name; + + } else if (entry.getKey().startsWith("ro.system.product.cpu.abilist")) { + var name = entry.getKey().substring(22); + key = "ro.product.cpu." + name; + } else { + continue; + } + if (!sDefaultValues.containsKey(key)) { + sDefaultValues.put(key, entry.getValue()); + } + } + + // Some other custom values + sDefaultValues.put("ro.board.first_api_level", "1"); + sDefaultValues.put("ro.product.first_api_level", "1"); + sDefaultValues.put("ro.soc.manufacturer", "Android"); + sDefaultValues.put("ro.soc.model", "Ravenwood"); + sDefaultValues.put(RAVENWOOD_SYSPROP, "1"); + + // Log all values + sDefaultValues.forEach((key, value) -> RavenwoodCommonUtils.log(TAG, key + "=" + value)); + + // Copy ro.product.* and ro.build.* to all partitions, just in case + // We don't want to log these because these are just a lot of duplicate values + for (var entry : Set.copyOf(sDefaultValues.entrySet())) { + var key = entry.getKey(); + if (key.startsWith("ro.product.") || key.startsWith("ro.build.")) { + var name = key.substring(3); + for (String partition : PARTITIONS) { + var newKey = "ro." + partition + "." + name; + if (!sDefaultValues.containsKey(newKey)) { + sDefaultValues.put(newKey, entry.getValue()); + } + } + } + } + } + private volatile boolean mIsImmutable; private final Map<String, String> mValues = new HashMap<>(); @@ -35,47 +117,15 @@ public class RavenwoodSystemProperties { private final Set<String> mKeyWritable = new HashSet<>(); public RavenwoodSystemProperties() { - // TODO: load these values from build.prop generated files - setValueForPartitions("product.brand", "Android"); - setValueForPartitions("product.device", "Ravenwood"); - setValueForPartitions("product.manufacturer", "Android"); - setValueForPartitions("product.model", "Ravenwood"); - setValueForPartitions("product.name", "Ravenwood"); - - setValueForPartitions("product.cpu.abilist", "x86_64"); - setValueForPartitions("product.cpu.abilist32", ""); - setValueForPartitions("product.cpu.abilist64", "x86_64"); - - setValueForPartitions("build.date", "Thu Jan 01 00:00:00 GMT 2024"); - setValueForPartitions("build.date.utc", "1704092400"); - setValueForPartitions("build.id", "MAIN"); - setValueForPartitions("build.tags", "dev-keys"); - setValueForPartitions("build.type", "userdebug"); - setValueForPartitions("build.version.all_codenames", "REL"); - setValueForPartitions("build.version.codename", "REL"); - setValueForPartitions("build.version.incremental", "userdebug.ravenwood.20240101"); - setValueForPartitions("build.version.known_codenames", "REL"); - setValueForPartitions("build.version.release", "14"); - setValueForPartitions("build.version.release_or_codename", "VanillaIceCream"); - setValueForPartitions("build.version.sdk", "34"); - - setValue("ro.board.first_api_level", "1"); - setValue("ro.product.first_api_level", "1"); - - setValue("ro.soc.manufacturer", "Android"); - setValue("ro.soc.model", "Ravenwood"); - - setValue("ro.debuggable", "1"); - - setValue(RAVENWOOD_SYSPROP, "1"); + mValues.putAll(sDefaultValues); } /** Copy constructor */ public RavenwoodSystemProperties(RavenwoodSystemProperties source, boolean immutable) { - this.mKeyReadable.addAll(source.mKeyReadable); - this.mKeyWritable.addAll(source.mKeyWritable); - this.mValues.putAll(source.mValues); - this.mIsImmutable = immutable; + mKeyReadable.addAll(source.mKeyReadable); + mKeyWritable.addAll(source.mKeyWritable); + mValues.putAll(source.mValues); + mIsImmutable = immutable; } public Map<String, String> getValues() { @@ -123,36 +173,12 @@ public class RavenwoodSystemProperties { return mKeyWritable.contains(key); } - private static final String[] PARTITIONS = { - "bootimage", - "odm", - "product", - "system", - "system_ext", - "vendor", - "vendor_dlkm", - }; - private void ensureNotImmutable() { if (mIsImmutable) { throw new RuntimeException("Unable to update immutable instance"); } } - /** - * Set the given property for all possible partitions where it could be defined. For - * example, the value of {@code ro.build.type} is typically also mirrored under - * {@code ro.system.build.type}, etc. - */ - private void setValueForPartitions(String key, String value) { - ensureNotImmutable(); - - setValue("ro." + key, value); - for (String partition : PARTITIONS) { - setValue("ro." + partition + "." + key, value); - } - } - public void setValue(String key, Object value) { ensureNotImmutable(); @@ -195,11 +221,4 @@ public class RavenwoodSystemProperties { return key; } } - - /** - * Return an immutable, default instance. - */ - // Create a default instance, and make an immutable copy of it. - public static final RavenwoodSystemProperties DEFAULT_VALUES = - new RavenwoodSystemProperties(new RavenwoodSystemProperties(), true); } diff --git a/ravenwood/tests/bivalenttest/Android.bp b/ravenwood/tests/bivalenttest/Android.bp index ac499b966afe..d7f4b3e2955d 100644 --- a/ravenwood/tests/bivalenttest/Android.bp +++ b/ravenwood/tests/bivalenttest/Android.bp @@ -54,34 +54,36 @@ android_ravenwood_test { auto_gen_config: true, } -// TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture +android_test { + name: "RavenwoodBivalentTest_device", -// android_test { -// name: "RavenwoodBivalentTest_device", -// -// srcs: [ -// "test/**/*.java", -// ], -// static_libs: [ -// "junit", -// "truth", -// -// "androidx.annotation_annotation", -// "androidx.test.ext.junit", -// "androidx.test.rules", -// -// "junit-params", -// "platform-parametric-runner-lib", -// -// "ravenwood-junit", -// ], -// jni_libs: [ -// "libravenwoodbivalenttest_jni", -// ], -// test_suites: [ -// "device-tests", -// ], -// optimize: { -// enabled: false, -// }, -// } + srcs: [ + "test/**/*.java", + ], + // TODO(b/371215487): migrate bivalenttest.ravenizer tests to another architecture + exclude_srcs: [ + "test/**/ravenizer/*.java", + ], + static_libs: [ + "junit", + "truth", + + "androidx.annotation_annotation", + "androidx.test.ext.junit", + "androidx.test.rules", + + "junit-params", + "platform-parametric-runner-lib", + + "ravenwood-junit", + ], + jni_libs: [ + "libravenwoodbivalenttest_jni", + ], + test_suites: [ + "device-tests", + ], + optimize: { + enabled: false, + }, +} diff --git a/ravenwood/minimum-test/Android.bp b/ravenwood/tests/minimum-test/Android.bp index e4ed3d5b0b73..e4ed3d5b0b73 100644 --- a/ravenwood/minimum-test/Android.bp +++ b/ravenwood/tests/minimum-test/Android.bp diff --git a/ravenwood/minimum-test/README.md b/ravenwood/tests/minimum-test/README.md index 6b0abe968053..6b0abe968053 100644 --- a/ravenwood/minimum-test/README.md +++ b/ravenwood/tests/minimum-test/README.md diff --git a/ravenwood/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java b/ravenwood/tests/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java index 30abaa2e7d38..30abaa2e7d38 100644 --- a/ravenwood/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java +++ b/ravenwood/tests/minimum-test/test/com/android/ravenwoodtest/RavenwoodMinimumTest.java diff --git a/ravenwood/mockito/Android.bp b/ravenwood/tests/mockito/Android.bp index d91537bbc334..d91537bbc334 100644 --- a/ravenwood/mockito/Android.bp +++ b/ravenwood/tests/mockito/Android.bp diff --git a/ravenwood/mockito/AndroidManifest.xml b/ravenwood/tests/mockito/AndroidManifest.xml index 15f0a2934b5f..15f0a2934b5f 100644 --- a/ravenwood/mockito/AndroidManifest.xml +++ b/ravenwood/tests/mockito/AndroidManifest.xml diff --git a/ravenwood/mockito/AndroidTest.xml b/ravenwood/tests/mockito/AndroidTest.xml index 5ba9b1ff2cd8..5ba9b1ff2cd8 100644 --- a/ravenwood/mockito/AndroidTest.xml +++ b/ravenwood/tests/mockito/AndroidTest.xml diff --git a/ravenwood/mockito/README.md b/ravenwood/tests/mockito/README.md index 4ceb795fe14a..4ceb795fe14a 100644 --- a/ravenwood/mockito/README.md +++ b/ravenwood/tests/mockito/README.md diff --git a/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java b/ravenwood/tests/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java index d566977bd15c..d566977bd15c 100644 --- a/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java +++ b/ravenwood/tests/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoDeviceOnlyTest.java diff --git a/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java b/ravenwood/tests/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java index aa2b7611da37..aa2b7611da37 100644 --- a/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java +++ b/ravenwood/tests/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoRavenwoodOnlyTest.java diff --git a/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java b/ravenwood/tests/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java index fcc6c9cc447d..fcc6c9cc447d 100644 --- a/ravenwood/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java +++ b/ravenwood/tests/mockito/test/com/android/ravenwoodtest/mockito/RavenwoodMockitoTest.java diff --git a/ravenwood/resapk_test/Android.bp b/ravenwood/tests/resapk_test/Android.bp index c14576550f78..c14576550f78 100644 --- a/ravenwood/resapk_test/Android.bp +++ b/ravenwood/tests/resapk_test/Android.bp diff --git a/ravenwood/resapk_test/apk/Android.bp b/ravenwood/tests/resapk_test/apk/Android.bp index 10ed5e2f8410..10ed5e2f8410 100644 --- a/ravenwood/resapk_test/apk/Android.bp +++ b/ravenwood/tests/resapk_test/apk/Android.bp diff --git a/ravenwood/resapk_test/apk/AndroidManifest.xml b/ravenwood/tests/resapk_test/apk/AndroidManifest.xml index f34d8b2f4e81..f34d8b2f4e81 100644 --- a/ravenwood/resapk_test/apk/AndroidManifest.xml +++ b/ravenwood/tests/resapk_test/apk/AndroidManifest.xml diff --git a/ravenwood/resapk_test/apk/res/values/strings.xml b/ravenwood/tests/resapk_test/apk/res/values/strings.xml index 23d4c0f21007..23d4c0f21007 100644 --- a/ravenwood/resapk_test/apk/res/values/strings.xml +++ b/ravenwood/tests/resapk_test/apk/res/values/strings.xml diff --git a/ravenwood/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java b/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java index e547114bbe40..e547114bbe40 100644 --- a/ravenwood/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java +++ b/ravenwood/tests/resapk_test/test/com/android/ravenwoodtest/resapk_test/RavenwoodResApkTest.java diff --git a/ravenwood/runtime-test/Android.bp b/ravenwood/tests/runtime-test/Android.bp index 410292001670..410292001670 100644 --- a/ravenwood/runtime-test/Android.bp +++ b/ravenwood/tests/runtime-test/Android.bp diff --git a/ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsConstantsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsConstantsTest.java index 633ed4e9d10a..633ed4e9d10a 100644 --- a/ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsConstantsTest.java +++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsConstantsTest.java diff --git a/ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java index c2230c739ccf..c2230c739ccf 100644 --- a/ravenwood/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java +++ b/ravenwood/tests/runtime-test/test/com/android/ravenwoodtest/runtimetest/OsTest.java diff --git a/ravenwood/services-test/Android.bp b/ravenwood/tests/services-test/Android.bp index 39858f05e80d..39858f05e80d 100644 --- a/ravenwood/services-test/Android.bp +++ b/ravenwood/tests/services-test/Android.bp diff --git a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java index f833782bc8bb..f833782bc8bb 100644 --- a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java +++ b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesDependenciesTest.java diff --git a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java index b3d3963270ee..b3d3963270ee 100644 --- a/ravenwood/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java +++ b/ravenwood/tests/services-test/test/com/android/ravenwoodtest/servicestest/RavenwoodServicesTest.java diff --git a/ravenwood/tools/hoststubgen/.gitignore b/ravenwood/tools/hoststubgen/.gitignore new file mode 100644 index 000000000000..82158c9b25e3 --- /dev/null +++ b/ravenwood/tools/hoststubgen/.gitignore @@ -0,0 +1,4 @@ +framework-all-stub-out +out/ +*-out/ +*.log diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/ravenwood/tools/hoststubgen/Android.bp index a5ff4964b0a4..a5ff4964b0a4 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/ravenwood/tools/hoststubgen/Android.bp diff --git a/tools/hoststubgen/README.md b/ravenwood/tools/hoststubgen/README.md index 1a895dc7dfce..615e7671bea1 100644 --- a/tools/hoststubgen/README.md +++ b/ravenwood/tools/hoststubgen/README.md @@ -11,7 +11,7 @@ used at runtime. - HostStubGen itself is design to be agnostic to Android. It doesn't use any Android APIs (hidden or not). But it may use Android specific knowledge -- e.g. as of now, -AndroidHeuristicsFilter has hardcoded heuristics to detect AIDL generated classes. +AndroidHeuristicsFilter has hardcoded heuristics to detect AIDL generated classes. - `test-tiny-framework/` contains basic tests that are agnostic to Android. @@ -20,19 +20,16 @@ AndroidHeuristicsFilter has hardcoded heuristics to detect AIDL generated classe ## Directories and files -- `hoststubgen/` - Contains source code of the "hoststubgen" tool and relevant code +- `src/` - - `src/` + HostStubGen tool source code. - HostStubGen tool source code. +- `annotations-src/` See `Android.bp`. +- `helper-framework-buildtime-src/` See `Android.bp`. +- `helper-framework-runtime-src/` See `Android.bp`. +- `helper-runtime-src/` See `Android.bp`. - - `annotations-src/` See `Android.bp`. - - `helper-framework-buildtime-src/` See `Android.bp`. - - `helper-framework-runtime-src/` See `Android.bp`. - - `helper-runtime-src/` See `Android.bp`. - - - `test-tiny-framework/` See `README.md` in it. +- `test-tiny-framework/` See `README.md` in it. - `scripts` - `dump-jar.sh` @@ -78,4 +75,4 @@ $ ./scripts/run-all-tests.sh - At some point, we can move or delete all Android specific code to `frameworks/base/ravenwood`. - `helper-framework-*-src` should be moved to `frameworks/base/ravenwood` - - `test-framework` should be deleted.
\ No newline at end of file + - `test-framework` should be deleted. diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestClassLoadHook.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestClassLoadHook.java index a774336a897c..a774336a897c 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestClassLoadHook.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestClassLoadHook.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestIgnore.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestIgnore.java index 501fd652145e..501fd652145e 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestIgnore.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestIgnore.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestKeep.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestKeep.java index 06ad1c266a14..06ad1c266a14 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestKeep.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestKeep.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirect.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirect.java index bc9471b84b97..bc9471b84b97 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirect.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirect.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirectionClass.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirectionClass.java index 28ad236a66f3..28ad236a66f3 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirectionClass.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRedirectionClass.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRemove.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRemove.java index 46e5078fb05d..46e5078fb05d 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRemove.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestRemove.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStaticInitializerKeep.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStaticInitializerKeep.java index eec72269e0d3..eec72269e0d3 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStaticInitializerKeep.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestStaticInitializerKeep.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestSubstitute.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestSubstitute.java index 510a67e0aaed..510a67e0aaed 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestSubstitute.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestSubstitute.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestThrow.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestThrow.java index cd1bef4be505..cd1bef4be505 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestThrow.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestThrow.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassKeep.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassKeep.java index 3d1ddea2cbb7..3d1ddea2cbb7 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassKeep.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/HostSideTestWholeClassKeep.java diff --git a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/tests/HostSideTestSuppress.java b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/tests/HostSideTestSuppress.java index b10f0ff1a4b1..b10f0ff1a4b1 100644 --- a/tools/hoststubgen/hoststubgen/annotations-src/android/hosttest/annotation/tests/HostSideTestSuppress.java +++ b/ravenwood/tools/hoststubgen/annotations-src/android/hosttest/annotation/tests/HostSideTestSuppress.java diff --git a/tools/hoststubgen/common.sh b/ravenwood/tools/hoststubgen/common.sh index b49ee39a3142..b49ee39a3142 100644 --- a/tools/hoststubgen/common.sh +++ b/ravenwood/tools/hoststubgen/common.sh diff --git a/tools/hoststubgen/hoststubgen/framework-policy-override.txt b/ravenwood/tools/hoststubgen/framework-policy-override.txt index af3789e270a4..af3789e270a4 100644 --- a/tools/hoststubgen/hoststubgen/framework-policy-override.txt +++ b/ravenwood/tools/hoststubgen/framework-policy-override.txt diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsIgnore.java b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsIgnore.java index b01710347537..b01710347537 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsIgnore.java +++ b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsIgnore.java diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsKeep.java b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsKeep.java index 18ef1bab203e..18ef1bab203e 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsKeep.java +++ b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsKeep.java diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsSubstitute.java b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsSubstitute.java index 99e38c0b1725..99e38c0b1725 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsSubstitute.java +++ b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsSubstitute.java diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsThrow.java b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsThrow.java index 4933cf8784d9..4933cf8784d9 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsThrow.java +++ b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostStubGenProcessedAsThrow.java diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestException.java b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestException.java index c54c2c111229..c54c2c111229 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestException.java +++ b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestException.java diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestSuite.java b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestSuite.java index 29f7be008eef..29f7be008eef 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestSuite.java +++ b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestSuite.java diff --git a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java index 78fd8f7f960a..78fd8f7f960a 100644 --- a/tools/hoststubgen/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java +++ b/ravenwood/tools/hoststubgen/helper-runtime-src/com/android/hoststubgen/hosthelper/HostTestUtils.java diff --git a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt index 001943c18d6b..001943c18d6b 100644 --- a/tools/hoststubgen/hoststubgen/hoststubgen-standard-options.txt +++ b/ravenwood/tools/hoststubgen/hoststubgen-standard-options.txt diff --git a/tools/hoststubgen/hoststubgen/invoketest/Android.bp b/ravenwood/tools/hoststubgen/invoketest/Android.bp index 7e90e421a1f9..7e90e421a1f9 100644 --- a/tools/hoststubgen/hoststubgen/invoketest/Android.bp +++ b/ravenwood/tools/hoststubgen/invoketest/Android.bp diff --git a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh b/ravenwood/tools/hoststubgen/invoketest/hoststubgen-invoke-test.sh index 084448d0a797..084448d0a797 100755 --- a/tools/hoststubgen/hoststubgen/invoketest/hoststubgen-invoke-test.sh +++ b/ravenwood/tools/hoststubgen/invoketest/hoststubgen-invoke-test.sh diff --git a/tools/hoststubgen/hoststubgen/jarjar-rules.txt b/ravenwood/tools/hoststubgen/jarjar-rules.txt index b1f2fc21c8e9..b1f2fc21c8e9 100644 --- a/tools/hoststubgen/hoststubgen/jarjar-rules.txt +++ b/ravenwood/tools/hoststubgen/jarjar-rules.txt diff --git a/tools/hoststubgen/scripts/Android.bp b/ravenwood/tools/hoststubgen/scripts/Android.bp index b1ba07ec540d..b1ba07ec540d 100644 --- a/tools/hoststubgen/scripts/Android.bp +++ b/ravenwood/tools/hoststubgen/scripts/Android.bp diff --git a/tools/hoststubgen/scripts/build-framework-hostside-jars-without-genrules.sh b/ravenwood/tools/hoststubgen/scripts/build-framework-hostside-jars-without-genrules.sh index c3605a9ffaa5..c3605a9ffaa5 100755 --- a/tools/hoststubgen/scripts/build-framework-hostside-jars-without-genrules.sh +++ b/ravenwood/tools/hoststubgen/scripts/build-framework-hostside-jars-without-genrules.sh diff --git a/tools/hoststubgen/scripts/dump-jar b/ravenwood/tools/hoststubgen/scripts/dump-jar index 87652451359d..87652451359d 100755 --- a/tools/hoststubgen/scripts/dump-jar +++ b/ravenwood/tools/hoststubgen/scripts/dump-jar diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Exceptions.kt index f59e143c1e4e..f59e143c1e4e 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Exceptions.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 6d8d7b768b91..6d8d7b768b91 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt index a218c5599553..a218c5599553 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt index 4bcee409aaec..4bcee409aaec 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt index 85064661cd2b..85064661cd2b 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 55e853e3e2fb..55e853e3e2fb 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt index 9045db210495..9045db210495 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/HostStubGenStats.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Utils.kt index 10179eefcb95..10179eefcb95 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/Utils.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt index a02082d12934..a02082d12934 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt index e2647eb13ed3..e2647eb13ed3 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt index 5e4e70f0cbaa..5e4e70f0cbaa 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/dumper/ApiDumper.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt index 6b360b79c327..6b360b79c327 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AndroidHeuristicsFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt index 36adf0626415..36adf0626415 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/AnnotationBasedFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt index f8bb526d0a86..f8bb526d0a86 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ClassWidePolicyPropagatingFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt index be3c59c80152..be3c59c80152 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ConstantFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt index d771003a955d..d771003a955d 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DefaultHookInjectingFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt index b8b0d8a31268..b8b0d8a31268 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt index 2f2f81b05ad1..2f2f81b05ad1 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt index b10165b835f2..b10165b835f2 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicyWithReason.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt index c5a2f9ff5e96..c5a2f9ff5e96 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt index 474da6dfa1b9..474da6dfa1b9 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt index 59fa464a7212..59fa464a7212 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/InMemoryOutputFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt index 00e7d77fa6e7..00e7d77fa6e7 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/KeepNativeFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt index f99ce906240a..f99ce906240a 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt index c67e6714d4c2..c67e6714d4c2 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/PackageFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt index 18a1e16bcf3a..18a1e16bcf3a 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SanitizationFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt index fd7474b55fa6..fd7474b55fa6 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/SubclassFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt index caf80ebec0c9..caf80ebec0c9 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt index d45f41407a52..d45f41407a52 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt index a78c6552b8d0..a78c6552b8d0 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt index d6aa7617fd59..d6aa7617fd59 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/ClassFilter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt index 1b3d79cddb8e..1b3d79cddb8e 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/utils/Trie.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt index 261ef59c45c7..261ef59c45c7 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BaseAdapter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt index 55d0c0e555f1..55d0c0e555f1 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/BodyReplacingMethodVisitor.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt index dc4f26bdda34..dc4f26bdda34 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/Helper.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt index 567a69e43b58..567a69e43b58 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt index e90ecd7ef678..e90ecd7ef678 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt +++ b/ravenwood/tools/hoststubgen/src/com/android/hoststubgen/visitors/PackageRedirectRemapper.kt diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp b/ravenwood/tools/hoststubgen/test-tiny-framework/Android.bp index ba2c869adfe8..1570549ec27d 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/Android.bp +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/Android.bp @@ -16,7 +16,7 @@ java_library { static_libs: [ "hoststubgen-annotations", ], - visibility: ["//frameworks/base/tools/hoststubgen:__subpackages__"], + visibility: ["//frameworks/base/ravenwood/tools/hoststubgen:__subpackages__"], } // Create stub/impl jars from "hoststubgen-test-tiny-framework", using the following 3 rules. diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/AndroidTest-host.xml b/ravenwood/tools/hoststubgen/test-tiny-framework/AndroidTest-host.xml index 84aad69c33bc..84aad69c33bc 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/AndroidTest-host.xml +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/AndroidTest-host.xml diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md b/ravenwood/tools/hoststubgen/test-tiny-framework/README.md index 344b4e953b23..344b4e953b23 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/README.md +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/README.md diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt index de4cb0c536c1..de4cb0c536c1 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/annotation-allowed-classes-tiny-framework.txt diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh index 3726ca972564..3726ca972564 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/diff-and-update-golden.sh +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/diff-and-update-golden.sh diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index 103e152c7e39..103e152c7e39 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt index eeec554e954c..eeec554e954c 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-dump.txt diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt index 0f8af92dc486..0f8af92dc486 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-dump.txt diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt index 3c138d21b75d..3c138d21b75d 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh b/ravenwood/tools/hoststubgen/test-tiny-framework/run-test-manually.sh index 80ebf3adab3d..80ebf3adab3d 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/run-test-manually.sh +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/run-test-manually.sh diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py index cee29dcd1d59..cee29dcd1d59 100755 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework-dump-test.py diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java index 0a07c2b91fc3..0a07c2b91fc3 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/IPretendingAidl.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java index b1bedf4b6853..b1bedf4b6853 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/R.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java index 3415deb957ed..3415deb957ed 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotations.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java index f734790c8dd9..f734790c8dd9 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassLoadHook.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java index e83163edb5e5..e83163edb5e5 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotations.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.java index 3df21d9a5647..3df21d9a5647 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerDefault.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.java index cc665de9cd01..cc665de9cd01 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWithInitializerStub.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java index f833ad814513..f833ad814513 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumComplex.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java index c023169b5601..c023169b5601 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkEnumSimple.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java index f7cae7d255fe..f7cae7d255fe 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkExceptionTester.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java index ec1efba99c77..ec1efba99c77 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.java index 1ca653ec7da6..1ca653ec7da6 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java index 57c69a336654..57c69a336654 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java index 04a551c8c46e..04a551c8c46e 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java index c7a29a1cc0f9..c7a29a1cc0f9 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java index c1ea2ee59fbb..c1ea2ee59fbb 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNestedClasses.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.java index 941fcff31d8e..941fcff31d8e 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkPackageRedirect.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java index 707bc0ebb4db..707bc0ebb4db 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkRenamedClassCaller.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java index 8319ced6109a..8319ced6109a 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkToBeRenamed.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java index 6a52e4401b45..6a52e4401b45 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/A.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java index 1374a288f7aa..1374a288f7aa 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/B.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java index 361a7fd04842..361a7fd04842 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/A.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java index 716595a44243..716595a44243 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/packagetest/sub/B.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C1.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C1.java index 03c9e2a7b5bb..03c9e2a7b5bb 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C1.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C1.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C2.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C2.java index 3ca8f1f172bd..3ca8f1f172bd 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C2.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C2.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C3.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C3.java index a6c14f09b680..a6c14f09b680 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C3.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/C3.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/CA.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/CA.java index 2e353709fd1e..2e353709fd1e 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/CA.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/CA.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/CB.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/CB.java index fe4cee64b2f9..fe4cee64b2f9 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/CB.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/CB.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.java index 12012fcdf6ca..12012fcdf6ca 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C1.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.java index 8d48ee6f6858..8d48ee6f6858 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C2.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.java index 6748430a8e6f..6748430a8e6f 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_C3.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA.java index 58aa5c3b74eb..58aa5c3b74eb 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CA.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB.java index c1c3d624b126..c1c3d624b126 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA.java index 398b56975f1c..398b56975f1c 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_CB_IA.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.java index 44cbd8f9bffa..44cbd8f9bffa 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.java index 42355a34b65c..42355a34b65c 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I1_IA.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.java index 09c80992e450..09c80992e450 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I2.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.java index 0806a478d756..0806a478d756 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA.java index eaa8528a3f31..eaa8528a3f31 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_I3_IA.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA.java index 778c5aaf27f0..778c5aaf27f0 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1.java index 493f7c83c0f0..493f7c83c0f0 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I1.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3.java index 2aa1de18b7f4..2aa1de18b7f4 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IA_I3.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB.java index d9eae0934c42..d9eae0934c42 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA.java index 9ee42836ac9a..9ee42836ac9a 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_IB_IA.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_None.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_None.java index 50ec2cbc9c6e..50ec2cbc9c6e 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_None.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/Class_None.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I1.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I1.java index 3f3659644a80..3f3659644a80 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I1.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I1.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I2.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I2.java index 960060c8a036..960060c8a036 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I2.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I2.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I3.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I3.java index c678eaa789b0..c678eaa789b0 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I3.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/I3.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/IA.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/IA.java index 1cff484c3cd8..1cff484c3cd8 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/IA.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/IA.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/IB.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/IB.java index 84e7173c71b8..84e7173c71b8 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/IB.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/subclasstest/IB.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/supported/UnsupportedClass.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/supported/UnsupportedClass.java index fa5866451e83..fa5866451e83 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/supported/UnsupportedClass.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/supported/UnsupportedClass.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/unsupported/UnsupportedClass.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/unsupported/UnsupportedClass.java index 92f41ac63cdb..92f41ac63cdb 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/unsupported/UnsupportedClass.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-framework/src/com/unsupported/UnsupportedClass.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java index 1ae049371229..1ae049371229 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkAnnotationsTest.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index 68673dc2a5b8..68673dc2a5b8 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java index 1816b383f6f7..1816b383f6f7 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java +++ b/ravenwood/tools/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassWideAnnotationsTest.java diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt b/ravenwood/tools/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt index 5b2795c4cff2..5b2795c4cff2 100644 --- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt +++ b/ravenwood/tools/hoststubgen/test/com/android/hoststubgen/asm/AsmUtilsTest.kt diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt b/ravenwood/tools/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt index d4e75d43a54a..d4e75d43a54a 100644 --- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt +++ b/ravenwood/tools/hoststubgen/test/com/android/hoststubgen/utils/ClassFilterTest.kt diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt b/ravenwood/tools/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt index 081d03909926..081d03909926 100644 --- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt +++ b/ravenwood/tools/hoststubgen/test/com/android/hoststubgen/utils/TrieTest.kt diff --git a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt b/ravenwood/tools/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt index 75e2536a98fa..75e2536a98fa 100644 --- a/tools/hoststubgen/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt +++ b/ravenwood/tools/hoststubgen/test/com/android/hoststubgen/visitors/HelperTest.kt diff --git a/services/Android.bp b/services/Android.bp index 653cd3c3b680..f04c692c12d0 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -195,7 +195,17 @@ soong_config_module_type { module_type: "java_library", config_namespace: "system_services", bool_variables: ["without_vibrator"], - properties: ["vintf_fragments"], + properties: ["vintf_fragment_modules"], +} + +vintf_fragment { + name: "manifest_services.xml", + src: "manifest_services.xml", +} + +vintf_fragment { + name: "manifest_services_android.frameworks.vibrator.xml", + src: "manifest_services_android.frameworks.vibrator.xml", } system_java_library { @@ -264,11 +274,11 @@ system_java_library { soong_config_variables: { without_vibrator: { - vintf_fragments: [ + vintf_fragment_modules: [ "manifest_services.xml", ], conditions_default: { - vintf_fragments: [ + vintf_fragment_modules: [ "manifest_services.xml", "manifest_services_android.frameworks.vibrator.xml", ], diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig index 034127c0420e..7057cc361a1a 100644 --- a/services/accessibility/accessibility.aconfig +++ b/services/accessibility/accessibility.aconfig @@ -45,6 +45,16 @@ flag { } flag { + name: "clear_shortcuts_when_activity_updates_to_service" + namespace: "accessibility" + description: "When an a11y activity is updated to an a11y service, clears the associated shortcuts so that we don't skip the AccessibilityServiceWarning." + bug: "358092445" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "compute_window_changes_on_a11y_v2" namespace: "accessibility" description: "Computes accessibility window changes in accessibility instead of wm package." @@ -114,10 +124,13 @@ flag { } flag { - name: "enable_magnification_follows_mouse" + name: "enable_magnification_follows_mouse_bugfix" namespace: "accessibility" description: "Whether to enable mouse following for fullscreen magnification" bug: "354696546" + metadata { + purpose: PURPOSE_BUGFIX + } } flag { diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java index 1451dfaa7964..ec8908bc7c91 100644 --- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -2513,6 +2513,19 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub private boolean readInstalledAccessibilityShortcutLocked(AccessibilityUserState userState, List<AccessibilityShortcutInfo> parsedAccessibilityShortcutInfos) { if (!parsedAccessibilityShortcutInfos.equals(userState.mInstalledShortcuts)) { + if (Flags.clearShortcutsWhenActivityUpdatesToService()) { + List<String> componentNames = userState.mInstalledShortcuts.stream() + .filter(a11yActivity -> + !parsedAccessibilityShortcutInfos.contains(a11yActivity)) + .map(a11yActivity -> a11yActivity.getComponentName().flattenToString()) + .toList(); + if (!componentNames.isEmpty()) { + enableShortcutsForTargets( + /* enable= */ false, UserShortcutType.ALL, + componentNames, userState.mUserId); + } + } + userState.mInstalledShortcuts.clear(); userState.mInstalledShortcuts.addAll(parsedAccessibilityShortcutInfos); userState.updateTileServiceMapForAccessibilityActivityLocked(); 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 a19fdddea49c..963334b07ea6 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java @@ -345,7 +345,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH @Override void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (Flags.enableMagnificationFollowsMouse()) { + if (Flags.enableMagnificationFollowsMouseBugfix()) { if (mFullScreenMagnificationController.isActivated(mDisplayId)) { // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are // over, rather than only interacting with the current display. @@ -1206,7 +1206,7 @@ public class FullScreenMagnificationGestureHandler extends MagnificationGestureH protected void cacheDelayedMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) { - if (Flags.enableMagnificationFollowsMouse() + if (Flags.enableMagnificationFollowsMouseBugfix() && !event.isFromSource(SOURCE_TOUCHSCREEN)) { // Only touch events need to be cached and sent later. return; diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java index 446123f07f64..fa86ba39bb1a 100644 --- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java +++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java @@ -146,7 +146,8 @@ public abstract class MagnificationGestureHandler extends BaseEventStreamTransfo } break; case SOURCE_MOUSE: case SOURCE_STYLUS: { - if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) { + if (magnificationShortcutExists() + && Flags.enableMagnificationFollowsMouseBugfix()) { handleMouseOrStylusEvent(event, rawEvent, policyFlags); } } diff --git a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java index 5d574083b326..89f14b09d397 100644 --- a/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java +++ b/services/appfunctions/java/com/android/server/appfunctions/AppFunctionManagerServiceImpl.java @@ -68,7 +68,10 @@ import com.android.server.SystemService.TargetUser; import java.io.FileDescriptor; import java.io.PrintWriter; +import java.util.Collections; +import java.util.Map; import java.util.Objects; +import java.util.WeakHashMap; import java.util.concurrent.CompletionException; import java.util.concurrent.Executor; @@ -81,7 +84,8 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { private final ServiceHelper mInternalServiceHelper; private final ServiceConfig mServiceConfig; private final Context mContext; - private final Object mLock = new Object(); + private final Map<String, Object> mLocks = new WeakHashMap<>(); + public AppFunctionManagerServiceImpl(@NonNull Context context) { this( @@ -225,12 +229,6 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { .thenAccept( canExecute -> { if (!canExecute) { - safeExecuteAppFunctionCallback.onResult( - ExecuteAppFunctionResponse.newFailure( - ExecuteAppFunctionResponse.RESULT_DENIED, - "Caller does not have permission to execute the" - + " appfunction", - /* extras= */ null)); throw new SecurityException( "Caller does not have permission to execute the" + " appfunction"); @@ -322,9 +320,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { THREAD_POOL_EXECUTOR.execute( () -> { try { - // TODO(357551503): Instead of holding a global lock, hold a per-package - // lock. - synchronized (mLock) { + synchronized (getLockForPackage(callingPackage)) { setAppFunctionEnabledInternalLocked( callingPackage, functionIdentifier, userHandle, enabledState); } @@ -352,7 +348,7 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { * process. */ @WorkerThread - @GuardedBy("mLock") + @GuardedBy("getLockForPackage(callingPackage)") private void setAppFunctionEnabledInternalLocked( @NonNull String callingPackage, @NonNull String functionIdentifier, @@ -547,6 +543,26 @@ public class AppFunctionManagerServiceImpl extends IAppFunctionManager.Stub { }); } } + /** + * Retrieves the lock object associated with the given package name. + * + * This method returns the lock object from the {@code mLocks} map if it exists. + * If no lock is found for the given package name, a new lock object is created, + * stored in the map, and returned. + */ + @VisibleForTesting + @NonNull + Object getLockForPackage(String callingPackage) { + // Synchronized the access to mLocks to prevent race condition. + synchronized (mLocks) { + // By using a WeakHashMap, we allow the garbage collector to reclaim memory by removing + // entries associated with unused callingPackage keys. Therefore, we remove the null + // values before getting/computing a new value. The goal is to not let the size of this + // map grow without an upper bound. + mLocks.values().removeAll(Collections.singleton(null)); // Remove null values + return mLocks.computeIfAbsent(callingPackage, k -> new Object()); + } + } private static class AppFunctionMetadataObserver implements ObserverCallback { @Nullable private final MetadataSyncAdapter mPerUserMetadataSyncAdapter; diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig index ba8448548365..1803424ae7d7 100644 --- a/services/autofill/bugfixes.aconfig +++ b/services/autofill/bugfixes.aconfig @@ -49,3 +49,10 @@ flag { description: "Include the session id into the FillEventHistory events as part of ClientState" bug: "333927465" } + +flag { + name: "highlight_autofill_single_field" + namespace: "autofill" + description: "Highlight single field after autofill selection" + bug: "41496744" +} diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java index 2fa0e0d0d946..8f12b1db8f29 100644 --- a/services/autofill/java/com/android/server/autofill/Session.java +++ b/services/autofill/java/com/android/server/autofill/Session.java @@ -39,6 +39,7 @@ import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG; import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED; import static android.service.autofill.FillRequest.FLAG_VIEW_REQUESTS_CREDMAN_SERVICE; import static android.service.autofill.FillRequest.INVALID_REQUEST_ID; +import static android.service.autofill.Flags.highlightAutofillSingleField; import static android.view.autofill.AutofillManager.ACTION_RESPONSE_EXPIRED; import static android.view.autofill.AutofillManager.ACTION_START_SESSION; import static android.view.autofill.AutofillManager.ACTION_VALUE_CHANGED; @@ -49,7 +50,6 @@ import static android.view.autofill.AutofillManager.COMMIT_REASON_UNKNOWN; import static android.view.autofill.AutofillManager.EXTRA_AUTOFILL_REQUEST_ID; import static android.view.autofill.AutofillManager.FLAG_SMART_SUGGESTION_SYSTEM; import static android.view.autofill.AutofillManager.getSmartSuggestionModeToString; - import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_EXPLICITLY_REQUESTED; import static com.android.server.autofill.FillRequestEventLogger.TRIGGER_REASON_NORMAL_TRIGGER; @@ -104,7 +104,6 @@ import static com.android.server.wm.ActivityTaskManagerInternal.ASSIST_KEY_STRUC import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import android.app.Activity; import android.app.ActivityTaskManager; import android.app.IAssistDataReceiver; import android.app.PendingIntent; @@ -143,7 +142,6 @@ import android.os.TransactionTooLargeException; import android.service.assist.classification.FieldClassificationRequest; import android.service.assist.classification.FieldClassificationResponse; import android.service.autofill.AutofillFieldClassificationService.Scores; -import android.service.autofill.AutofillService; import android.service.autofill.CompositeUserData; import android.service.autofill.ConvertCredentialResponse; import android.service.autofill.Dataset; @@ -186,7 +184,6 @@ import android.view.autofill.IAutoFillManagerClient; import android.view.autofill.IAutofillWindowPresenter; import android.view.inputmethod.InlineSuggestionsRequest; import android.widget.RemoteViews; - import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.logging.MetricsLogger; @@ -198,7 +195,6 @@ import com.android.server.autofill.ui.InlineFillUi; import com.android.server.autofill.ui.PendingUi; import com.android.server.inputmethod.InputMethodManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; - import java.io.PrintWriter; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -222,18 +218,21 @@ import java.util.function.Function; /** * A session for a given activity. * - * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track - * of the current {@link ViewState} to display the appropriate UI. + * <p>This class manages the multiple {@link ViewState}s for each view it has, and keeps track of + * the current {@link ViewState} to display the appropriate UI. * - * <p>Although the autofill requests and callbacks are stateless from the service's point of - * view, we need to keep state in the framework side for cases such as authentication. For - * example, when service return a {@link FillResponse} that contains all the fields needed - * to fill the activity but it requires authentication first, that response need to be held - * until the user authenticates or it times out. + * <p>Although the autofill requests and callbacks are stateless from the service's point of view, + * we need to keep state in the framework side for cases such as authentication. For example, when + * service return a {@link FillResponse} that contains all the fields needed to fill the activity + * but it requires authentication first, that response need to be held until the user authenticates + * or it times out. */ -final class Session implements RemoteFillService.FillServiceCallbacks, ViewState.Listener, - AutoFillUI.AutoFillUiCallback, ValueFinder, - RemoteFieldClassificationService.FieldClassificationServiceCallbacks { +final class Session + implements RemoteFillService.FillServiceCallbacks, + ViewState.Listener, + AutoFillUI.AutoFillUiCallback, + ValueFinder, + RemoteFieldClassificationService.FieldClassificationServiceCallbacks { private static final String TAG = "AutofillSession"; // This should never be true in production. This is only for local debugging. @@ -257,6 +256,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private final AutofillManagerServiceImpl mService; private final Handler mHandler; private final AutoFillUI mUi; + /** * Context associated with the session, it has the same {@link Context#getDisplayId() displayId} * of the activity being autofilled. @@ -286,14 +286,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** Session is destroyed and removed from the manager service. */ public static final int STATE_REMOVED = 3; - @IntDef(prefix = { "STATE_" }, value = { - STATE_UNKNOWN, - STATE_ACTIVE, - STATE_FINISHED, - STATE_REMOVED - }) + @IntDef( + prefix = {"STATE_"}, + value = {STATE_UNKNOWN, STATE_ACTIVE, STATE_FINISHED, STATE_REMOVED}) @Retention(RetentionPolicy.SOURCE) - @interface SessionState{} + @interface SessionState {} @GuardedBy("mLock") private final SessionFlags mSessionFlags; @@ -318,7 +315,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public final int mFlags; @GuardedBy("mLock") - @NonNull private IBinder mActivityToken; + @NonNull + private IBinder mActivityToken; /** The app activity that's being autofilled */ @NonNull private final ComponentName mComponentName; @@ -341,11 +339,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * autofill. */ @GuardedBy("mLock") - @Nullable private Pair<Integer, InlineSuggestionsRequest> mLastInlineSuggestionsRequest; + @Nullable + private Pair<Integer, InlineSuggestionsRequest> mLastInlineSuggestionsRequest; - /** - * Id of the View currently being displayed. - */ + /** Id of the View currently being displayed. */ @GuardedBy("mLock") private @Nullable AutofillId mCurrentViewId; @@ -363,8 +360,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * * <p>Only {@code null} when the session is for augmented autofill only. */ - @Nullable - private final RemoteFillService mRemoteFillService; + @Nullable private final RemoteFillService mRemoteFillService; /** * With the credman integration, Autofill Framework handles two types of autofill flows - @@ -379,8 +375,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * service the session was initially created with, the secondary provider handler will contain * the remaining autofill service. */ - @Nullable - private final SecondaryProviderHandler mSecondaryProviderHandler; + @Nullable private final SecondaryProviderHandler mSecondaryProviderHandler; @GuardedBy("mLock") private SparseArray<FillResponse> mResponses; @@ -395,9 +390,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private ArrayList<FillContext> mContexts; - /** - * Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. - */ + /** Whether the client has an {@link android.view.autofill.AutofillManager.AutofillCallback}. */ private boolean mHasCallback; /** Whether the session has credential manager provider as the primary provider. */ @@ -426,32 +419,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private PendingUi mPendingSaveUi; - /** - * List of dataset ids selected by the user. - */ + /** List of dataset ids selected by the user. */ @GuardedBy("mLock") private ArrayList<String> mSelectedDatasetIds; - /** - * When the session started (using elapsed time since boot). - */ + /** When the session started (using elapsed time since boot). */ private final long mStartTime; - /** - * Count of FillRequests in the session. - */ + /** Count of FillRequests in the session. */ private int mRequestCount; /** - * Starting timestamp of latency logger. - * This is set when Session created or when the view is reset. + * Starting timestamp of latency logger. This is set when Session created or when the view is + * reset. */ @GuardedBy("mLock") private long mLatencyBaseTime; - /** - * When the UI was shown for the first time (using elapsed time since boot). - */ + /** When the UI was shown for the first time (using elapsed time since boot). */ @GuardedBy("mLock") private long mUiShownTime; @@ -475,15 +460,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private final LocalLog mWtfHistory; - /** - * Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id. - */ + /** Map of {@link MetricsEvent#AUTOFILL_REQUEST} metrics, keyed by fill request id. */ @GuardedBy("mLock") private final SparseArray<LogMaker> mRequestLogs = new SparseArray<>(1); - /** - * Destroys the augmented Autofill UI. - */ + /** Destroys the augmented Autofill UI. */ // TODO(b/123099468): this runnable is called when the Autofill session is destroyed, the // main reason being the cases where user tap HOME. // Right now it's completely destroying the UI, but we need to decide whether / how to @@ -494,13 +475,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Nullable private Runnable mAugmentedAutofillDestroyer; - /** - * List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics. - */ + /** List of {@link MetricsEvent#AUTOFILL_AUGMENTED_REQUEST} metrics. */ @GuardedBy("mLock") private ArrayList<LogMaker> mAugmentedRequestsLogs; - /** * List of autofill ids of autofillable fields present in the AssistStructure that can be used * to trigger new augmented autofill requests (because the "standard" service was not interested @@ -509,23 +487,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private ArrayList<AutofillId> mAugmentedAutofillableIds; - @NonNull - final AutofillInlineSessionController mInlineSessionController; + @NonNull final AutofillInlineSessionController mInlineSessionController; - /** - * Receiver of assist data from the app's {@link Activity}. - */ + /** Receiver of assist data from the app's {@link Activity}. */ private final AssistDataReceiverImpl mAssistReceiver = new AssistDataReceiverImpl(); - /** - * Receiver of assist data for pcc purpose - */ + /** Receiver of assist data for pcc purpose */ private final PccAssistDataReceiverImpl mPccAssistReceiver = new PccAssistDataReceiverImpl(); private final ClassificationState mClassificationState = new ClassificationState(); - @Nullable - private final ComponentName mCredentialAutofillService; + @Nullable private final ComponentName mCredentialAutofillService; // TODO(b/216576510): Share one BroadcastReceiver between all Sessions instead of creating a // new one per Session. @@ -547,7 +519,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.v(TAG, "mDelayedFillBroadcastReceiver delayed fill action received"); synchronized (mLock) { int requestId = intent.getIntExtra(EXTRA_REQUEST_ID, 0); - FillResponse response = intent.getParcelableExtra(EXTRA_FILL_RESPONSE, android.service.autofill.FillResponse.class); + FillResponse response = + intent.getParcelableExtra( + EXTRA_FILL_RESPONSE, + android.service.autofill.FillResponse.class); mFillRequestEventLogger.maybeSetRequestTriggerReason( TRIGGER_REASON_RETRIGGER); mAssistReceiver.processDelayedFillLocked(requestId, response); @@ -575,28 +550,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private SessionCommittedEventLogger mSessionCommittedEventLogger; - /** - * Fill dialog request would likely be sent slightly later. - */ + /** Fill dialog request would likely be sent slightly later. */ @NonNull @GuardedBy("mLock") private boolean mPreviouslyFillDialogPotentiallyStarted; /** - * Keeps track of if the user entered view, this is used to - * distinguish Fill Request that did not have user interaction - * with ones that did. + * Keeps track of if the user entered view, this is used to distinguish Fill Request that did + * not have user interaction with ones that did. * - * This is set to true when entering view - after FillDialog FillRequest - * or on plain user tap. + * <p>This is set to true when entering view - after FillDialog FillRequest or on plain user + * tap. */ @NonNull @GuardedBy("mLock") private boolean mLogViewEntered; /** - * Keeps the fill dialog trigger ids of the last response. This invalidates - * the trigger ids of the previous response. + * Keeps the fill dialog trigger ids of the last response. This invalidates the trigger ids of + * the previous response. */ @Nullable @GuardedBy("mLock") @@ -662,9 +634,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return false; } - /** - * Collection of flags/booleans that helps determine Session behaviors. - */ + /** Collection of flags/booleans that helps determine Session behaviors. */ private final class SessionFlags { /** Whether autofill is disabled by the service */ private boolean mAutofillDisabled; @@ -695,31 +665,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final class AssistDataReceiverImpl extends IAssistDataReceiver.Stub { @GuardedBy("mLock") private boolean mWaitForInlineRequest; + @GuardedBy("mLock") private InlineSuggestionsRequest mPendingInlineSuggestionsRequest; + @GuardedBy("mLock") private FillRequest mPendingFillRequest; + @GuardedBy("mLock") private FillRequest mLastFillRequest; - @Nullable Consumer<InlineSuggestionsRequest> newAutofillRequestLocked(ViewState viewState, - boolean isInlineRequest) { + @Nullable + Consumer<InlineSuggestionsRequest> newAutofillRequestLocked( + ViewState viewState, boolean isInlineRequest) { mPendingFillRequest = null; mWaitForInlineRequest = isInlineRequest; mPendingInlineSuggestionsRequest = null; if (isInlineRequest) { WeakReference<AssistDataReceiverImpl> assistDataReceiverWeakReference = - new WeakReference<AssistDataReceiverImpl>(this); + new WeakReference<AssistDataReceiverImpl>(this); WeakReference<ViewState> viewStateWeakReference = - new WeakReference<ViewState>(viewState); - return new InlineSuggestionRequestConsumer(assistDataReceiverWeakReference, - viewStateWeakReference); + new WeakReference<ViewState>(viewState); + return new InlineSuggestionRequestConsumer( + assistDataReceiverWeakReference, viewStateWeakReference); } return null; } - void handleInlineSuggestionRequest(InlineSuggestionsRequest inlineSuggestionsRequest, - ViewState viewState) { + void handleInlineSuggestionRequest( + InlineSuggestionsRequest inlineSuggestionsRequest, ViewState viewState) { if (sVerbose) { Slog.v(TAG, "handleInlineSuggestionRequest(): inline suggestion request received"); } @@ -738,8 +712,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void maybeRequestFillLocked() { if (mPendingFillRequest == null) { if (sVerbose) { - Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request " - + "due to empty pending fill request"); + Slog.v( + TAG, + "maybeRequestFillLocked(): cancelling calling fill request " + + "due to empty pending fill request"); } return; } @@ -748,27 +724,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mWaitForInlineRequest) { if (mPendingInlineSuggestionsRequest == null) { if (sVerbose) { - Slog.v(TAG, "maybeRequestFillLocked(): cancelling calling fill request " - + "due to waiting for inline request and pending inline request is " - + "currently empty"); + Slog.v( + TAG, + "maybeRequestFillLocked(): cancelling calling fill request due to" + + " waiting for inline request and pending inline request is" + + " currently empty"); } return; } if (sVerbose) { - Slog.v(TAG, "maybeRequestFillLocked(): adding inline request to pending " - + "fill request"); + Slog.v( + TAG, + "maybeRequestFillLocked(): adding inline request to pending " + + "fill request"); } - mPendingFillRequest = new FillRequest(mPendingFillRequest.getId(), - mPendingFillRequest.getFillContexts(), - mPendingFillRequest.getHints(), - mPendingFillRequest.getClientState(), - mPendingFillRequest.getFlags(), - mPendingInlineSuggestionsRequest, - mPendingFillRequest.getDelayedFillIntentSender()); + mPendingFillRequest = + new FillRequest( + mPendingFillRequest.getId(), + mPendingFillRequest.getFillContexts(), + mPendingFillRequest.getHints(), + mPendingFillRequest.getClientState(), + mPendingFillRequest.getFlags(), + mPendingInlineSuggestionsRequest, + mPendingFillRequest.getDelayedFillIntentSender()); } else { if (sVerbose) { - Slog.v(TAG, "maybeRequestFillLocked(): not adding inline request to pending " - + "fill request"); + Slog.v( + TAG, + "maybeRequestFillLocked(): not adding inline request to pending " + + "fill request"); } } @@ -780,19 +764,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState && mSecondaryProviderHandler != null) { Slog.v(TAG, "Requesting fill response to secondary provider."); if (!mIsPrimaryCredential) { - mPendingFillRequest = addCredentialManagerDataToClientState( - mPendingFillRequest, - mPendingInlineSuggestionsRequest, id); + mPendingFillRequest = + addCredentialManagerDataToClientState( + mPendingFillRequest, mPendingInlineSuggestionsRequest, id); } - mSecondaryProviderHandler.onFillRequest(mPendingFillRequest, - mPendingFillRequest.getFlags(), mClient.asBinder()); + mSecondaryProviderHandler.onFillRequest( + mPendingFillRequest, mPendingFillRequest.getFlags(), mClient.asBinder()); } else if (mRemoteFillService != null) { if (mIsPrimaryCredential) { - mPendingFillRequest = addCredentialManagerDataToClientState( - mPendingFillRequest, - mPendingInlineSuggestionsRequest, id); - mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, - mClient.asBinder()); + mPendingFillRequest = + addCredentialManagerDataToClientState( + mPendingFillRequest, mPendingInlineSuggestionsRequest, id); + mRemoteFillService.onFillCredentialRequest( + mPendingFillRequest, mClient.asBinder()); } else { mRemoteFillService.onFillRequest(mPendingFillRequest); } @@ -813,8 +797,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onHandleAssistData(Bundle resultData) throws RemoteException { if (mRemoteFillService == null) { - wtf(null, "onHandleAssistData() called without a remote service. " - + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); + wtf( + null, + "onHandleAssistData() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", + mSessionFlags.mAugmentedAutofillOnly); return; } // Keeps to prevent it is cleared on multiple threads. @@ -824,7 +811,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class); + final AssistStructure structure = + resultData.getParcelable( + ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class); if (structure == null) { Slog.e(TAG, "No assist structure - app might have crashed providing it"); return; @@ -852,13 +841,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { structure.ensureDataForAutofill(); } catch (RuntimeException e) { - wtf(e, "Exception lazy loading assist structure for %s: %s", - structure.getActivityComponent(), e); + wtf( + e, + "Exception lazy loading assist structure for %s: %s", + structure.getActivityComponent(), + e); return; } - final ArrayList<AutofillId> ids = Helper.getAutofillIds(structure, - /* autofillableOnly= */false); + final ArrayList<AutofillId> ids = + Helper.getAutofillIds(structure, /* autofillableOnly= */ false); for (int i = 0; i < ids.size(); i++) { ids.get(i).setSessionId(Session.this.id); } @@ -868,8 +860,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mCompatMode) { // Sanitize URL bar, if needed - final String[] urlBarIds = mService.getUrlBarResourceIdsForCompatMode( - mComponentName.getPackageName()); + final String[] urlBarIds = + mService.getUrlBarResourceIdsForCompatMode( + mComponentName.getPackageName()); if (sDebug) { Slog.d(TAG, "url_bars in compat mode: " + Arrays.toString(urlBarIds)); } @@ -878,11 +871,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mUrlBar != null) { final AutofillId urlBarId = mUrlBar.getAutofillId(); if (sDebug) { - Slog.d(TAG, "Setting urlBar as id=" + urlBarId + " and domain " - + mUrlBar.getWebDomain()); + Slog.d( + TAG, + "Setting urlBar as id=" + + urlBarId + + " and domain " + + mUrlBar.getWebDomain()); } - final ViewState viewState = new ViewState(urlBarId, Session.this, - ViewState.STATE_URL_BAR, mIsPrimaryCredential); + final ViewState viewState = + new ViewState( + urlBarId, + Session.this, + ViewState.STATE_URL_BAR, + mIsPrimaryCredential); mViewStates.put(urlBarId, viewState); } } @@ -907,11 +908,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final List<String> hints = getTypeHintsForProvider(); mDelayedFillPendingIntent = createPendingIntent(requestId); - request = new FillRequest(requestId, contexts, hints, mClientState, flags, - /*inlineSuggestionsRequest=*/ null, - /*delayedFillIntentSender=*/ mDelayedFillPendingIntent == null - ? null - : mDelayedFillPendingIntent.getIntentSender()); + request = + new FillRequest( + requestId, + contexts, + hints, + mClientState, + flags, + /* inlineSuggestionsRequest= */ null, + /* delayedFillIntentSender= */ mDelayedFillPendingIntent == null + ? null + : mDelayedFillPendingIntent.getIntentSender()); mPendingFillRequest = request; maybeRequestFillLocked(); @@ -930,39 +937,49 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void processDelayedFillLocked(int requestId, FillResponse response) { if (mLastFillRequest != null && requestId == mLastFillRequest.getId()) { - Slog.v(TAG, "processDelayedFillLocked: " - + "calling onFillRequestSuccess with new response"); - onFillRequestSuccess(requestId, response, - mService.getServicePackageName(), mLastFillRequest.getFlags()); + Slog.v( + TAG, + "processDelayedFillLocked: " + + "calling onFillRequestSuccess with new response"); + onFillRequestSuccess( + requestId, + response, + mService.getServicePackageName(), + mLastFillRequest.getFlags()); } } } - private FillRequest addCredentialManagerDataToClientState(FillRequest pendingFillRequest, - InlineSuggestionsRequest pendingInlineSuggestionsRequest, int sessionId) { + private FillRequest addCredentialManagerDataToClientState( + FillRequest pendingFillRequest, + InlineSuggestionsRequest pendingInlineSuggestionsRequest, + int sessionId) { if (pendingFillRequest.getClientState() == null) { - pendingFillRequest = new FillRequest(pendingFillRequest.getId(), - pendingFillRequest.getFillContexts(), - pendingFillRequest.getHints(), - new Bundle(), - pendingFillRequest.getFlags(), - pendingInlineSuggestionsRequest, - pendingFillRequest.getDelayedFillIntentSender()); + pendingFillRequest = + new FillRequest( + pendingFillRequest.getId(), + pendingFillRequest.getFillContexts(), + pendingFillRequest.getHints(), + new Bundle(), + pendingFillRequest.getFlags(), + pendingInlineSuggestionsRequest, + pendingFillRequest.getDelayedFillIntentSender()); } pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, sessionId); pendingFillRequest.getClientState().putInt(REQUEST_ID_KEY, pendingFillRequest.getId()); - ResultReceiver resultReceiver = constructCredentialManagerCallback( - pendingFillRequest.getId()); - pendingFillRequest.getClientState().putParcelable( - CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver); + ResultReceiver resultReceiver = + constructCredentialManagerCallback(pendingFillRequest.getId()); + pendingFillRequest + .getClientState() + .putParcelable(CredentialManager.EXTRA_AUTOFILL_RESULT_RECEIVER, resultReceiver); return pendingFillRequest; } /** - * Get the list of valid autofill hint types from Device flags - * Returns empty list if PCC is off or no types available - */ + * Get the list of valid autofill hint types from Device flags Returns empty list if PCC is off + * or no types available + */ private List<String> getTypeHintsForProvider() { if (!mService.isPccClassificationEnabled()) { return Collections.EMPTY_LIST; @@ -978,9 +995,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return List.of(typeHints.split(PCC_HINTS_DELIMITER)); } - /** - * Assist Data Receiver for PCC - */ + /** Assist Data Receiver for PCC */ private final class PccAssistDataReceiverImpl extends IAssistDataReceiver.Stub { @GuardedBy("mLock") @@ -998,7 +1013,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState new WeakReference<>(Session.this); remoteFieldClassificationService.onFieldClassificationRequest( mClassificationState.mPendingFieldClassificationRequest, - fieldClassificationServiceCallbacksWeakRef); + fieldClassificationServiceCallbacksWeakRef); } mClassificationState.onFieldClassificationRequestSent(); } @@ -1006,26 +1021,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void onHandleAssistData(Bundle resultData) throws RemoteException { // TODO: add a check if pcc field classification service is present - final AssistStructure structure = resultData.getParcelable(ASSIST_KEY_STRUCTURE, - android.app.assist.AssistStructure.class); + final AssistStructure structure = + resultData.getParcelable( + ASSIST_KEY_STRUCTURE, android.app.assist.AssistStructure.class); if (structure == null) { - Slog.e(TAG, "No assist structure for pcc detection - " - + "app might have crashed providing it"); + Slog.e( + TAG, + "No assist structure for pcc detection - " + + "app might have crashed providing it"); return; } final Bundle receiverExtras = resultData.getBundle(ASSIST_KEY_RECEIVER_EXTRAS); if (receiverExtras == null) { - Slog.e(TAG, "No receiver extras for pcc detection - " - + "app might have crashed providing it"); + Slog.e( + TAG, + "No receiver extras for pcc detection - " + + "app might have crashed providing it"); return; } final int requestId = receiverExtras.getInt(EXTRA_REQUEST_ID); if (sVerbose) { - Slog.v(TAG, "New structure for PCC Detection: requestId " + requestId + ": " - + structure); + Slog.v( + TAG, + "New structure for PCC Detection: requestId " + + requestId + + ": " + + structure); } synchronized (mLock) { @@ -1037,13 +1061,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { structure.ensureDataForAutofill(); } catch (RuntimeException e) { - wtf(e, "Exception lazy loading assist structure for %s: %s", - structure.getActivityComponent(), e); + wtf( + e, + "Exception lazy loading assist structure for %s: %s", + structure.getActivityComponent(), + e); return; } - final ArrayList<AutofillId> ids = Helper.getAutofillIds(structure, - /* autofillableOnly= */false); + final ArrayList<AutofillId> ids = + Helper.getAutofillIds(structure, /* autofillableOnly= */ false); for (int i = 0; i < ids.size(); i++) { ids.get(i).setSessionId(Session.this.id); } @@ -1066,13 +1093,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState PendingIntent pendingIntent; final long identity = Binder.clearCallingIdentity(); try { - Intent intent = new Intent(ACTION_DELAYED_FILL).setPackage("android") - .putExtra(EXTRA_REQUEST_ID, requestId); - pendingIntent = PendingIntent.getBroadcast( - mContext, this.id, intent, - PendingIntent.FLAG_MUTABLE - | PendingIntent.FLAG_ONE_SHOT - | PendingIntent.FLAG_CANCEL_CURRENT); + Intent intent = + new Intent(ACTION_DELAYED_FILL) + .setPackage("android") + .putExtra(EXTRA_REQUEST_ID, requestId); + pendingIntent = + PendingIntent.getBroadcast( + mContext, + this.id, + intent, + PendingIntent.FLAG_MUTABLE + | PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_CANCEL_CURRENT); } finally { Binder.restoreCallingIdentity(identity); } @@ -1113,9 +1145,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * Returns the ids of all entries in {@link #mViewStates} in the same order. - */ + /** Returns the ids of all entries in {@link #mViewStates} in the same order. */ @GuardedBy("mLock") private AutofillId[] getIdsOfAllViewStatesLocked() { final int numViewState = mViewStates.size(); @@ -1164,8 +1194,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * <p>Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, - * or {@code null} when not found on either of them. + * Gets the value of a field, using either the {@code viewStates} or the {@code mContexts}, or + * {@code null} when not found on either of them. */ @GuardedBy("mLock") @Nullable @@ -1180,16 +1210,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<Session> previousSessions = mService.getPreviousSessionsLocked(this); if (previousSessions != null) { if (sDebug) { - Slog.d(TAG, "findValueLocked(): looking on " + previousSessions.size() - + " previous sessions for autofillId " + autofillId); + Slog.d( + TAG, + "findValueLocked(): looking on " + + previousSessions.size() + + " previous sessions for autofillId " + + autofillId); } for (int i = 0; i < previousSessions.size(); i++) { final Session previousSession = previousSessions.get(i); - final AutofillValue previousValue = previousSession - .findValueFromThisSessionOnlyLocked(autofillId); + final AutofillValue previousValue = + previousSession.findValueFromThisSessionOnlyLocked(autofillId); if (previousValue != null) { - return getSanitizedValue(createSanitizers(previousSession.getSaveInfoLocked()), - autofillId, previousValue); + return getSanitizedValue( + createSanitizers(previousSession.getSaveInfoLocked()), + autofillId, + previousValue); } } } @@ -1212,16 +1248,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState AutofillValue candidateSaveValue = state.getCandidateSaveValue(); if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { if (sDebug) { - Slog.d(TAG, "findValueLocked(): current value for " + autofillId - + " is empty, using candidateSaveValue instead."); + Slog.d( + TAG, + "findValueLocked(): current value for " + + autofillId + + " is empty, using candidateSaveValue instead."); } return candidateSaveValue; } } if (value == null) { if (sDebug) { - Slog.d(TAG, "findValueLocked(): no current value for " + autofillId - + ", checking value from previous fill contexts"); + Slog.d( + TAG, + "findValueLocked(): no current value for " + + autofillId + + ", checking value from previous fill contexts"); value = getValueFromContextsLocked(autofillId); } } @@ -1231,17 +1273,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Updates values of the nodes in the context's structure so that: * - * - proper node is focused - * - autofillValue is sent back to service when it was previously autofilled - * - autofillValue is sent in the view used to force a request + * <p>- proper node is focused - autofillValue is sent back to service when it was previously + * autofilled - autofillValue is sent in the view used to force a request * * @param fillContext The context to be filled * @param flags The flags that started the session */ @GuardedBy("mLock") private void fillContextWithAllowedValuesLocked(@NonNull FillContext fillContext, int flags) { - final ViewNode[] nodes = fillContext - .findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); + final ViewNode[] nodes = + fillContext.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); final int numViewState = mViewStates.size(); for (int i = 0; i < numViewState; i++) { @@ -1250,7 +1291,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ViewNode node = nodes[i]; if (node == null) { if (sVerbose) { - Slog.v(TAG, + Slog.v( + TAG, "fillContextWithAllowedValuesLocked(): no node for " + viewState.id); } continue; @@ -1277,14 +1319,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * Cancels the last request sent to the {@link #mRemoteFillService}. - */ + /** Cancels the last request sent to the {@link #mRemoteFillService}. */ @GuardedBy("mLock") private void cancelCurrentRequestLocked() { if (mRemoteFillService == null) { - wtf(null, "cancelCurrentRequestLocked() called without a remote service. " - + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); + wtf( + null, + "cancelCurrentRequestLocked() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", + mSessionFlags.mAugmentedAutofillOnly); return; } final int canceledRequest = mRemoteFillService.cancelCurrentRequest(); @@ -1318,21 +1361,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private Optional<Integer> requestNewFillResponseLocked( @NonNull ViewState viewState, int newState, int flags) { boolean isSecondary = shouldRequestSecondaryProvider(flags); - final FillResponse existingResponse = isSecondary - ? viewState.getSecondaryResponse() : viewState.getResponse(); + final FillResponse existingResponse = + isSecondary ? viewState.getSecondaryResponse() : viewState.getResponse(); mFillRequestEventLogger.startLogForNewRequest(); mRequestCount++; mFillRequestEventLogger.maybeSetAppPackageUid(uid); mFillRequestEventLogger.maybeSetFlags(mFlags); - if(mPreviouslyFillDialogPotentiallyStarted) { + if (mPreviouslyFillDialogPotentiallyStarted) { mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_PRE_TRIGGER); } else { if ((flags & FLAG_MANUAL_REQUEST) != 0) { mFillRequestEventLogger.maybeSetRequestTriggerReason( TRIGGER_REASON_EXPLICITLY_REQUESTED); } else { - mFillRequestEventLogger.maybeSetRequestTriggerReason( - TRIGGER_REASON_NORMAL_TRIGGER); + mFillRequestEventLogger.maybeSetRequestTriggerReason(TRIGGER_REASON_NORMAL_TRIGGER); } } if (existingResponse != null) { @@ -1348,9 +1390,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionState = STATE_ACTIVE; if (mSessionFlags.mAugmentedAutofillOnly || mRemoteFillService == null) { if (sVerbose) { - Slog.v(TAG, "requestNewFillResponse(): triggering augmented autofill instead " - + "(mForAugmentedAutofillOnly=" + mSessionFlags.mAugmentedAutofillOnly - + ", flags=" + flags + ")"); + Slog.v( + TAG, + "requestNewFillResponse(): triggering augmented autofill instead " + + "(mForAugmentedAutofillOnly=" + + mSessionFlags.mAugmentedAutofillOnly + + ", flags=" + + flags + + ")"); } mSessionFlags.mAugmentedAutofillOnly = true; mFillRequestEventLogger.maybeSetRequestId(AUGMENTED_AUTOFILL_REQUEST_ID); @@ -1365,16 +1412,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Create a metrics log for the request final int ordinal = mRequestLogs.size() + 1; - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_REQUEST) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal); + final LogMaker log = + newLogMaker(MetricsEvent.AUTOFILL_REQUEST) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL, ordinal); if (flags != 0) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags); } mRequestLogs.put(requestId, log); if (sVerbose) { - Slog.v(TAG, "Requesting structure for request #" + ordinal + " ,requestId=" + requestId - + ", flags=" + flags); + Slog.v( + TAG, + "Requesting structure for request #" + + ordinal + + " ,requestId=" + + requestId + + ", flags=" + + flags); } boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0; mFillRequestEventLogger.maybeSetRequestId(requestId); @@ -1406,11 +1460,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // is also not focused. final RemoteInlineSuggestionRenderService remoteRenderService = mService.getRemoteInlineSuggestionRenderServiceLocked(); - if (mSessionFlags.mInlineSupportedByService && remoteRenderService != null - && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { + if (mSessionFlags.mInlineSupportedByService + && remoteRenderService != null + && (isViewFocusedLocked(flags) || isRequestSupportFillDialog(flags))) { Consumer<InlineSuggestionsRequest> inlineSuggestionsRequestConsumer = - mAssistReceiver.newAutofillRequestLocked(viewState, - /* isInlineRequest= */ true); + mAssistReceiver.newAutofillRequestLocked( + viewState, /* isInlineRequest= */ true); if (inlineSuggestionsRequestConsumer != null) { final int requestIdCopy = requestId; final AutofillId focusedId = mCurrentViewId; @@ -1423,8 +1478,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState requestIdCopy, inlineSuggestionsRequestConsumer, focusedId); - RemoteCallback inlineSuggestionRendorInfoCallback = new RemoteCallback( - inlineSuggestionRendorInfoCallbackOnResultListener, mHandler); + RemoteCallback inlineSuggestionRendorInfoCallback = + new RemoteCallback( + inlineSuggestionRendorInfoCallbackOnResultListener, mHandler); remoteRenderService.getInlineSuggestionsRendererInfo( inlineSuggestionRendorInfoCallback); @@ -1465,8 +1521,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); final long identity = Binder.clearCallingIdentity(); try { - if (!ActivityTaskManager.getService().requestAutofillData(mPccAssistReceiver, - receiverExtras, mActivityToken, flags)) { + if (!ActivityTaskManager.getService() + .requestAutofillData( + mPccAssistReceiver, receiverExtras, mActivityToken, flags)) { Slog.w(TAG, "failed to request autofill data for " + mActivityToken); } } finally { @@ -1483,8 +1540,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState receiverExtras.putInt(EXTRA_REQUEST_ID, requestId); final long identity = Binder.clearCallingIdentity(); try { - if (!ActivityTaskManager.getService().requestAutofillData(mAssistReceiver, - receiverExtras, mActivityToken, flags)) { + if (!ActivityTaskManager.getService() + .requestAutofillData( + mAssistReceiver, receiverExtras, mActivityToken, flags)) { Slog.w(TAG, "failed to request autofill data for " + mActivityToken); } } finally { @@ -1495,13 +1553,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - Session(@NonNull AutofillManagerServiceImpl service, @NonNull AutoFillUI ui, - @NonNull Context context, @NonNull Handler handler, int userId, @NonNull Object lock, - int sessionId, int taskId, int uid, @NonNull IBinder activityToken, - @NonNull IBinder client, boolean hasCallback, @NonNull LocalLog uiLatencyHistory, - @NonNull LocalLog wtfHistory, @Nullable ComponentName serviceComponentName, - @NonNull ComponentName componentName, boolean compatMode, - boolean bindInstantServiceAllowed, boolean forAugmentedAutofillOnly, int flags, + Session( + @NonNull AutofillManagerServiceImpl service, + @NonNull AutoFillUI ui, + @NonNull Context context, + @NonNull Handler handler, + int userId, + @NonNull Object lock, + int sessionId, + int taskId, + int uid, + @NonNull IBinder activityToken, + @NonNull IBinder client, + boolean hasCallback, + @NonNull LocalLog uiLatencyHistory, + @NonNull LocalLog wtfHistory, + @Nullable ComponentName serviceComponentName, + @NonNull ComponentName componentName, + boolean compatMode, + boolean bindInstantServiceAllowed, + boolean forAugmentedAutofillOnly, + int flags, @NonNull InputMethodManagerInternal inputMethodManagerInternal, boolean isPrimaryCredential) { if (sessionId < 0) { @@ -1533,22 +1605,40 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState primaryServiceComponentName = serviceComponentName; secondaryServiceComponentName = mCredentialAutofillService; } - Slog.v(TAG, "Primary service component name: " + primaryServiceComponentName - + ", secondary service component name: " + secondaryServiceComponentName); - - mRemoteFillService = primaryServiceComponentName == null ? null - : new RemoteFillService(context, primaryServiceComponentName, userId, this, - bindInstantServiceAllowed, mCredentialAutofillService); - mSecondaryProviderHandler = secondaryServiceComponentName == null ? null - : new SecondaryProviderHandler(context, userId, bindInstantServiceAllowed, - this::onSecondaryFillResponse, secondaryServiceComponentName, - mCredentialAutofillService); + Slog.v( + TAG, + "Primary service component name: " + + primaryServiceComponentName + + ", secondary service component name: " + + secondaryServiceComponentName); + + mRemoteFillService = + primaryServiceComponentName == null + ? null + : new RemoteFillService( + context, + primaryServiceComponentName, + userId, + this, + bindInstantServiceAllowed, + mCredentialAutofillService); + mSecondaryProviderHandler = + secondaryServiceComponentName == null + ? null + : new SecondaryProviderHandler( + context, + userId, + bindInstantServiceAllowed, + this::onSecondaryFillResponse, + secondaryServiceComponentName, + mCredentialAutofillService); mActivityToken = activityToken; mHasCallback = hasCallback; mUiLatencyHistory = uiLatencyHistory; mWtfHistory = wtfHistory; - int displayId = LocalServices.getService(ActivityTaskManagerInternal.class) - .getDisplayId(activityToken); + int displayId = + LocalServices.getService(ActivityTaskManagerInternal.class) + .getDisplayId(activityToken); mContext = Helper.getDisplayContext(context, displayId); mComponentName = componentName; mCompatMode = compatMode; @@ -1557,8 +1647,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mStartTime = SystemClock.elapsedRealtime(); mLatencyBaseTime = mStartTime; mRequestCount = 0; - mPresentationStatsEventLogger = PresentationStatsEventLogger.createPresentationLog( - sessionId, uid, mLatencyBaseTime); + mPresentationStatsEventLogger = + PresentationStatsEventLogger.createPresentationLog( + sessionId, uid, mLatencyBaseTime); mFillRequestEventLogger = FillRequestEventLogger.forSessionId(sessionId); mFillResponseEventLogger = FillResponseEventLogger.forSessionId(sessionId); mSessionCommittedEventLogger = SessionCommittedEventLogger.forSessionId(sessionId); @@ -1574,33 +1665,39 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState setClientLocked(client); } - mInlineSessionController = new AutofillInlineSessionController(inputMethodManagerInternal, - userId, componentName, handler, mLock, - new InlineFillUi.InlineUiEventCallback() { - @Override - public void notifyInlineUiShown(AutofillId autofillId) { - notifyFillUiShown(autofillId); - } + mInlineSessionController = + new AutofillInlineSessionController( + inputMethodManagerInternal, + userId, + componentName, + handler, + mLock, + new InlineFillUi.InlineUiEventCallback() { + @Override + public void notifyInlineUiShown(AutofillId autofillId) { + notifyFillUiShown(autofillId); + } - @Override - public void notifyInlineUiHidden(AutofillId autofillId) { - notifyFillUiHidden(autofillId); - } - }); + @Override + public void notifyInlineUiHidden(AutofillId autofillId) { + notifyFillUiHidden(autofillId); + } + }); - mMetricsLogger.write(newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); + mMetricsLogger.write( + newLogMaker(MetricsEvent.AUTOFILL_SESSION_STARTED) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_FLAGS, flags)); mLogViewEntered = false; } private ComponentName getCredentialAutofillService(Context context) { ComponentName componentName = null; - String credentialManagerAutofillCompName = context.getResources().getString( - R.string.config_defaultCredentialManagerAutofillService); + String credentialManagerAutofillCompName = + context.getResources() + .getString(R.string.config_defaultCredentialManagerAutofillService); if (credentialManagerAutofillCompName != null && !credentialManagerAutofillCompName.isEmpty()) { - componentName = ComponentName.unflattenFromString( - credentialManagerAutofillCompName); + componentName = ComponentName.unflattenFromString(credentialManagerAutofillCompName); } if (componentName == null) { Slog.w(TAG, "Invalid CredentialAutofillService"); @@ -1614,7 +1711,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @return The activity token */ @GuardedBy("mLock") - @NonNull IBinder getActivityTokenLocked() { + @NonNull + IBinder getActivityTokenLocked() { return mActivityToken; } @@ -1627,8 +1725,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void switchActivity(@NonNull IBinder newActivity, @NonNull IBinder newClient) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#switchActivity() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#switchActivity() rejected - session: " + + id + + " destroyed"); return; } mActivityToken = newActivity; @@ -1643,17 +1744,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private void setClientLocked(@NonNull IBinder client) { unlinkClientVultureLocked(); mClient = IAutoFillManagerClient.Stub.asInterface(client); - mClientVulture = () -> { - synchronized (mLock) { - Slog.d(TAG, "handling death of " + mActivityToken + " when saving=" - + mSessionFlags.mShowingSaveUi); - if (mSessionFlags.mShowingSaveUi) { - mUi.hideFillUi(this); - } else { - mUi.destroyAll(mPendingSaveUi, this, false); - } - } - }; + mClientVulture = + () -> { + synchronized (mLock) { + Slog.d( + TAG, + "handling death of " + + mActivityToken + + " when saving=" + + mSessionFlags.mShowingSaveUi); + if (mSessionFlags.mShowingSaveUi) { + mUi.hideFillUi(this); + } else { + mUi.destroyAll(mPendingSaveUi, this, false); + } + } + }; try { mClient.asBinder().linkToDeath(mClientVulture, 0); } catch (RemoteException e) { @@ -1676,8 +1782,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override @SuppressWarnings("GuardedBy") - public void onFillRequestSuccess(int requestId, @Nullable FillResponse response, - @NonNull String servicePackageName, int requestFlags) { + public void onFillRequestSuccess( + int requestId, + @Nullable FillResponse response, + @NonNull String servicePackageName, + int requestFlags) { final AutofillId[] fieldClassificationIds; final LogMaker requestLog; @@ -1701,8 +1810,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState getDetectionPreferenceForLogging()); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onFillRequestSuccess() rejected - session: " + + id + + " destroyed"); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); mFillResponseEventLogger.logAndEndEvent(); return; @@ -1713,8 +1825,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // saveUi gets closed, the session will be destroyed and AutofillManager will reset // its state. Processing the fill request will result in a great chance of corrupt // state in Autofill. - Slog.w(TAG, "Call to Session#onFillRequestSuccess() rejected - session: " - + id + " is showing saveUi"); + Slog.w( + TAG, + "Call to Session#onFillRequestSuccess() rejected - session: " + + id + + " is showing saveUi"); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); mFillResponseEventLogger.logAndEndEvent(); return; @@ -1760,22 +1875,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - final long disableDuration = response.getDisableDuration(); final boolean autofillDisabled = disableDuration > 0; if (autofillDisabled) { final int flags = response.getFlags(); final boolean disableActivityOnly = (flags & FillResponse.FLAG_DISABLE_ACTIVITY_ONLY) != 0; - notifyDisableAutofillToClient(disableDuration, - disableActivityOnly ? mComponentName : null); + notifyDisableAutofillToClient( + disableDuration, disableActivityOnly ? mComponentName : null); if (disableActivityOnly) { - mService.disableAutofillForActivity(mComponentName, disableDuration, - id, mCompatMode); + mService.disableAutofillForActivity( + mComponentName, disableDuration, id, mCompatMode); } else { - mService.disableAutofillForApp(mComponentName.getPackageName(), disableDuration, - id, mCompatMode); + mService.disableAutofillForApp( + mComponentName.getPackageName(), disableDuration, id, mCompatMode); } synchronized (mLock) { @@ -1786,17 +1900,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (triggerAugmentedAutofillLocked(requestFlags) != null) { mSessionFlags.mAugmentedAutofillOnly = true; if (sDebug) { - Slog.d(TAG, "Service disabled autofill for " + mComponentName - + ", but session is kept for augmented autofill only"); + Slog.d( + TAG, + "Service disabled autofill for " + + mComponentName + + ", but session is kept for augmented autofill only"); } return; } } if (sDebug) { - final StringBuilder message = new StringBuilder("Service disabled autofill for ") + final StringBuilder message = + new StringBuilder("Service disabled autofill for ") .append(mComponentName) - .append(": flags=").append(flags) + .append(": flags=") + .append(flags) .append(", duration="); TimeUtils.formatDuration(disableDuration, message); Slog.d(TAG, message.toString()); @@ -1816,8 +1935,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (requestLog != null) { - requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, - response.getDatasets() == null ? 0 : response.getDatasets().size()); + requestLog.addTaggedData( + MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS, + response.getDatasets() == null ? 0 : response.getDatasets().size()); if (fieldClassificationIds != null) { requestLog.addTaggedData( MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS, @@ -1840,10 +1960,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - @GuardedBy("mLock") - private void processResponseLockedForPcc(@NonNull FillResponse response, - @Nullable Bundle newClientState, int flags) { + private void processResponseLockedForPcc( + @NonNull FillResponse response, @Nullable Bundle newClientState, int flags) { if (DBG) { Slog.d(TAG, "DBG: Initial response: " + response); } @@ -1851,9 +1970,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState response = getEffectiveFillResponse(response); if (isEmptyResponse(response)) { // Treat it as a null response. - processNullResponseLocked( - response != null ? response.getRequestId() : 0, - flags); + processNullResponseLocked(response != null ? response.getRequestId() : 0, flags); return; } if (DBG) { @@ -1870,9 +1987,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return ((response.getDatasets() == null || response.getDatasets().isEmpty()) && response.getAuthentication() == null && (saveInfo == null - || (ArrayUtils.isEmpty(saveInfo.getOptionalIds()) - && ArrayUtils.isEmpty(saveInfo.getRequiredIds()) - && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0))) + || (ArrayUtils.isEmpty(saveInfo.getOptionalIds()) + && ArrayUtils.isEmpty(saveInfo.getRequiredIds()) + && ((saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) == 0))) && (ArrayUtils.isEmpty(response.getFieldClassificationIds()))); } } @@ -1884,10 +2001,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState computeDatasetsForProviderAndUpdateContainer(response, autofillProviderContainer); if (DBG) { - Slog.d(TAG, "DBG: computeDatasetsForProviderAndUpdateContainer: " - + autofillProviderContainer); + Slog.d( + TAG, + "DBG: computeDatasetsForProviderAndUpdateContainer: " + + autofillProviderContainer); } - if (!mService.isPccClassificationEnabled()) { + if (!mService.isPccClassificationEnabled()) { if (sVerbose) { Slog.v(TAG, "PCC classification is disabled"); } @@ -1897,10 +2016,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mClassificationState.mState != ClassificationState.STATE_RESPONSE || mClassificationState.mLastFieldClassificationResponse == null) { if (sVerbose) { - Slog.v(TAG, "PCC classification no last response:" - + (mClassificationState.mLastFieldClassificationResponse == null) - + " ,ineligible state=" - + (mClassificationState.mState != ClassificationState.STATE_RESPONSE)); + Slog.v( + TAG, + "PCC classification no last response:" + + (mClassificationState.mLastFieldClassificationResponse + == null) + + " ,ineligible state=" + + (mClassificationState.mState + != ClassificationState.STATE_RESPONSE)); } return createShallowCopy(response, autofillProviderContainer); } @@ -1966,8 +2089,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFillResponseEventLogger.maybeSetLatencyFillResponseReceivedMillis( (int) (fillRequestReceivedRelativeTimestamp)); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onSecondaryFillResponse() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onSecondaryFillResponse() rejected - session: " + + id + + " destroyed"); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); mFillResponseEventLogger.logAndEndEvent(); return; @@ -1981,7 +2107,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSecondaryResponses = new SparseArray<>(2); } mSecondaryResponses.put(fillResponse.getRequestId(), fillResponse); - setViewStatesLocked(fillResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false, + setViewStatesLocked( + fillResponse, + ViewState.STATE_FILLABLE, + /* clearResponse= */ false, /* isPrimary= */ false); // Updates the UI, if necessary. @@ -1997,16 +2126,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState private FillResponse createShallowCopy( FillResponse response, DatasetComputationContainer container) { return FillResponse.shallowCopy( - response, - new ArrayList<>(container.mDatasets), - getEligibleSaveInfo(response)); + response, new ArrayList<>(container.mDatasets), getEligibleSaveInfo(response)); } private SaveInfo getEligibleSaveInfo(FillResponse response) { SaveInfo saveInfo = response.getSaveInfo(); - if (saveInfo == null || (!ArrayUtils.isEmpty(saveInfo.getOptionalIds()) - || !ArrayUtils.isEmpty(saveInfo.getRequiredIds()) - || (saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0)) { + if (saveInfo == null + || (!ArrayUtils.isEmpty(saveInfo.getOptionalIds()) + || !ArrayUtils.isEmpty(saveInfo.getRequiredIds()) + || (saveInfo.getFlags() & SaveInfo.FLAG_DELAY_SAVE) != 0)) { return saveInfo; } synchronized (mLock) { @@ -2019,12 +2147,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState ArraySet<AutofillId> ids = new ArraySet<>(); int saveType = saveInfo.getType(); if (saveType == SaveInfo.SAVE_DATA_TYPE_GENERIC) { - for (Set<AutofillId> autofillIds: hintsToAutofillIdMap.values()) { + for (Set<AutofillId> autofillIds : hintsToAutofillIdMap.values()) { ids.addAll(autofillIds); } } else { Set<String> hints = HintsHelper.getHintsForSaveType(saveType); - for (Map.Entry<String, Set<AutofillId>> entry: hintsToAutofillIdMap.entrySet()) { + for (Map.Entry<String, Set<AutofillId>> entry : hintsToAutofillIdMap.entrySet()) { String hint = entry.getKey(); if (hints.contains(hint)) { ids.addAll(entry.getValue()); @@ -2039,9 +2167,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * A private class to hold & compute datasets to be shown - */ + /** A private class to hold & compute datasets to be shown */ private static class DatasetComputationContainer { // List of all autofill ids that have a corresponding datasets Set<AutofillId> mAutofillIds = new LinkedHashSet<>(); @@ -2103,9 +2229,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Computes datasets that are eligible to be shown based on provider detections. - * Datasets are populated in the provided container for them to be later merged with the - * PCC eligible datasets based on preference strategy. + * Computes datasets that are eligible to be shown based on provider detections. Datasets are + * populated in the provided container for them to be later merged with the PCC eligible + * datasets based on preference strategy. + * * @param response * @param container */ @@ -2210,9 +2337,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Computes datasets that are eligible to be shown based on PCC detections. - * Datasets are populated in the provided container for them to be later merged with the - * provider eligible datasets based on preference strategy. + * Computes datasets that are eligible to be shown based on PCC detections. Datasets are + * populated in the provided container for them to be later merged with the provider eligible + * datasets based on preference strategy. + * * @param response * @param container */ @@ -2267,14 +2395,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // For that, there has to be a datatype detected by PCC, and the dataset // for that datatype provided by the provider. AutofillId autofillId = dataset.getFieldIds().get(j); - if (!mClassificationState.mClassificationCombinedHintsMap - .containsKey(autofillId)) { + if (!mClassificationState.mClassificationCombinedHintsMap.containsKey( + autofillId)) { additionalEligibleAutofillIds.add(autofillId); additionalDatasetAutofillIds.add(autofillId); // For each of the field, copy over values. - copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues, - fieldPresentations, fieldDialogPresentations, - fieldInlinePresentations, fieldInlineTooltipPresentations, + copyFieldsFromDataset( + dataset, + j, + autofillId, + fieldIds, + fieldValues, + fieldPresentations, + fieldDialogPresentations, + fieldInlinePresentations, + fieldInlineTooltipPresentations, fieldFilters); } continue; @@ -2292,9 +2427,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState eligibleAutofillIds.add(autofillId); datasetAutofillIds.add(autofillId); // For each of the field, copy over values. - copyFieldsFromDataset(dataset, j, autofillId, fieldIds, fieldValues, - fieldPresentations, fieldDialogPresentations, - fieldInlinePresentations, fieldInlineTooltipPresentations, + copyFieldsFromDataset( + dataset, + j, + autofillId, + fieldIds, + fieldValues, + fieldPresentations, + fieldDialogPresentations, + fieldInlinePresentations, + fieldInlineTooltipPresentations, fieldFilters); } } @@ -2364,8 +2506,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState fieldPresentations.add(dataset.getFieldPresentation(index)); fieldDialogPresentations.add(dataset.getFieldDialogPresentation(index)); fieldInlinePresentations.add(dataset.getFieldInlinePresentation(index)); - fieldInlineTooltipPresentations.add( - dataset.getFieldInlineTooltipPresentation(index)); + fieldInlineTooltipPresentations.add(dataset.getFieldInlineTooltipPresentation(index)); fieldFilters.add(dataset.getFilter(index)); } @@ -2395,16 +2536,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState unregisterDelayedFillBroadcastLocked(); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onFillRequestFailureOrTimeout(req=" + requestId - + ") rejected - session: " + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onFillRequestFailureOrTimeout(req=" + + requestId + + ") rejected - session: " + + id + + " destroyed"); mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_SESSION_DESTROYED); mFillResponseEventLogger.logAndEndEvent(); return; } if (sDebug) { - Slog.d(TAG, "finishing session due to service " - + (timedOut ? "timeout" : "failure")); + Slog.d( + TAG, + "finishing session due to service " + (timedOut ? "timeout" : "failure")); } mService.resetLastResponse(); mLastFillDialogTriggerIds = null; @@ -2418,12 +2565,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int targetSdk = mService.getTargedSdkLocked(); if (targetSdk >= Build.VERSION_CODES.Q) { showMessage = false; - Slog.w(TAG, "onFillRequestFailureOrTimeout(): not showing '" + message - + "' because service's targetting API " + targetSdk); + Slog.w( + TAG, + "onFillRequestFailureOrTimeout(): not showing '" + + message + + "' because service's targeting API " + + targetSdk); } if (message != null) { - requestLog.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, - message.length()); + requestLog.addTaggedData( + MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, message.length()); } } @@ -2445,8 +2596,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis(); mFillResponseEventLogger.logAndEndEvent(); } - notifyUnavailableToClient(AutofillManager.STATE_UNKNOWN_FAILED, - /* autofillableIds= */ null); + notifyUnavailableToClient( + AutofillManager.STATE_UNKNOWN_FAILED, /* autofillableIds= */ null); if (showMessage) { getUiForShowing().showError(message, this); } @@ -2455,8 +2606,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override - public void onSaveRequestSuccess(@NonNull String servicePackageName, - @Nullable IntentSender intentSender) { + public void onSaveRequestSuccess( + @NonNull String servicePackageName, @Nullable IntentSender intentSender) { synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; // Log onSaveRequest result. @@ -2464,16 +2615,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSaveEventLogger.maybeSetLatencySaveFinishMillis(); mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onSaveRequestSuccess() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onSaveRequestSuccess() rejected - session: " + + id + + " destroyed"); return; } } - LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) - .setType(intentSender == null ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_OPEN); + LogMaker log = + newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) + .setType( + intentSender == null + ? MetricsEvent.TYPE_SUCCESS + : MetricsEvent.TYPE_OPEN); mMetricsLogger.write(log); - if (intentSender != null) { if (sDebug) Slog.d(TAG, "Starting intent sender on save()"); startIntentSenderAndFinishSession(intentSender); @@ -2485,8 +2642,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override - public void onSaveRequestFailure(@Nullable CharSequence message, - @NonNull String servicePackageName) { + public void onSaveRequestFailure( + @Nullable CharSequence message, @NonNull String servicePackageName) { boolean showMessage = !TextUtils.isEmpty(message); synchronized (mLock) { @@ -2495,28 +2652,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSaveEventLogger.maybeSetLatencySaveFinishMillis(); mSaveEventLogger.logAndEndEvent(); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onSaveRequestFailure() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onSaveRequestFailure() rejected - session: " + + id + + " destroyed"); return; } if (showMessage) { final int targetSdk = mService.getTargedSdkLocked(); if (targetSdk >= Build.VERSION_CODES.Q) { showMessage = false; - Slog.w(TAG, "onSaveRequestFailure(): not showing '" + message - + "' because service's targetting API " + targetSdk); + Slog.w( + TAG, + "onSaveRequestFailure(): not showing '" + + message + + "' because service's targeting API " + + targetSdk); } } } final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_DATA_SAVE_REQUEST, servicePackageName) - .setType(MetricsEvent.TYPE_FAILURE); + .setType(MetricsEvent.TYPE_FAILURE); if (message != null) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_TEXT_LEN, message.length()); } mMetricsLogger.write(log); - if (showMessage) { getUiForShowing().showError(message, this); } @@ -2525,8 +2688,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // FillServiceCallbacks @Override - public void onConvertCredentialRequestSuccess(@NonNull ConvertCredentialResponse - convertCredentialResponse) { + public void onConvertCredentialRequestSuccess( + @NonNull ConvertCredentialResponse convertCredentialResponse) { Dataset dataset = convertCredentialResponse.getDataset(); Bundle clientState = convertCredentialResponse.getClientState(); if (dataset != null) { @@ -2534,15 +2697,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (clientState != null) { requestId = clientState.getInt(EXTRA_AUTOFILL_REQUEST_ID); } else { - Slog.e(TAG, "onConvertCredentialRequestSuccess(): client state is null, this " - + "would cause loss in logging."); + Slog.e( + TAG, + "onConvertCredentialRequestSuccess(): client state is null, this " + + "would cause loss in logging."); } // TODO: Add autofill related logging; consider whether to log the index - fill(requestId, /* datasetIndex=*/ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET); + fill(requestId, /* datasetIndex= */ -1, dataset, UI_TYPE_CREDMAN_BOTTOM_SHEET); } else { // TODO: Add logging to log this error case - Slog.e(TAG, "onConvertCredentialRequestSuccess(): dataset inside response is " - + "null"); + Slog.e( + TAG, + "onConvertCredentialRequestSuccess(): dataset inside response is " + "null"); } } @@ -2550,11 +2716,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Gets the {@link FillContext} for a request. * * @param requestId The id of the request - * * @return The context or {@code null} if there is no context */ @GuardedBy("mLock") - @Nullable private FillContext getFillContextByRequestIdLocked(int requestId) { + @Nullable + private FillContext getFillContextByRequestIdLocked(int requestId) { if (mContexts == null) { return null; } @@ -2582,19 +2748,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override - public void authenticate(int requestId, int datasetIndex, IntentSender intent, Bundle extras, - int uiType) { + public void authenticate( + int requestId, int datasetIndex, IntentSender intent, Bundle extras, int uiType) { if (sDebug) { - Slog.d(TAG, "authenticate(): requestId=" + requestId + "; datasetIdx=" + datasetIndex - + "; intentSender=" + intent); + Slog.d( + TAG, + "authenticate(): requestId=" + + requestId + + "; datasetIdx=" + + datasetIndex + + "; intentSender=" + + intent); } final Intent fillInIntent; synchronized (mLock) { mPresentationStatsEventLogger.maybeSetAuthenticationType( - AUTHENTICATION_TYPE_FULL_AUTHENTICATION); + AUTHENTICATION_TYPE_FULL_AUTHENTICATION); if (mDestroyed) { - Slog.w(TAG, "Call to Session#authenticate() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#authenticate() rejected - session: " + id + " destroyed"); return; } fillInIntent = createAuthFillInIntentLocked(requestId, extras); @@ -2607,10 +2780,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mService.setAuthenticationSelected(id, mClientState, uiType); final int authenticationId = AutofillManager.makeAuthenticationId(requestId, datasetIndex); - mHandler.sendMessage(obtainMessage( - Session::startAuthentication, - this, authenticationId, intent, fillInIntent, - /* authenticateInline= */ uiType == UI_TYPE_INLINE)); + mHandler.sendMessage( + obtainMessage( + Session::startAuthentication, + this, + authenticationId, + intent, + fillInIntent, + /* authenticateInline= */ uiType == UI_TYPE_INLINE)); } // AutoFillUiCallback @@ -2618,14 +2795,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void fill(int requestId, int datasetIndex, Dataset dataset, int uiType) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#fill() rejected - session: " - + id + " destroyed"); + Slog.w(TAG, "Call to Session#fill() rejected - session: " + id + " destroyed"); return; } } - mHandler.sendMessage(obtainMessage( - Session::autoFill, - this, requestId, datasetIndex, dataset, true, uiType)); + mHandler.sendMessage( + obtainMessage( + Session::autoFill, this, requestId, datasetIndex, dataset, true, uiType)); } // AutoFillUiCallback @@ -2633,15 +2809,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void save() { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#save() rejected - session: " - + id + " destroyed"); + Slog.w(TAG, "Call to Session#save() rejected - session: " + id + " destroyed"); return; } mSaveEventLogger.maybeSetLatencySaveRequestMillis(); } - mHandler.sendMessage(obtainMessage( - AutofillManagerServiceImpl::handleSessionSave, - mService, this)); + mHandler.sendMessage( + obtainMessage(AutofillManagerServiceImpl::handleSessionSave, mService, this)); } // AutoFillUiCallback @@ -2650,13 +2824,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { mSessionFlags.mShowingSaveUi = false; if (mDestroyed) { - Slog.w(TAG, "Call to Session#cancelSave() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#cancelSave() rejected - session: " + id + " destroyed"); return; } } - mHandler.sendMessage(obtainMessage( - Session::removeFromService, this)); + mHandler.sendMessage(obtainMessage(Session::removeFromService, this)); } // AutofillUiCallback @@ -2692,26 +2866,34 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // AutoFillUiCallback @Override - public void requestShowFillUi(AutofillId id, int width, int height, - IAutofillWindowPresenter presenter) { + public void requestShowFillUi( + AutofillId id, int width, int height, IAutofillWindowPresenter presenter) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#requestShowFillUi() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#requestShowFillUi() rejected - session: " + + id + + " destroyed"); return; } if (id.equals(mCurrentViewId)) { try { final ViewState view = mViewStates.get(id); - mClient.requestShowFillUi(this.id, id, width, height, view.getVirtualBounds(), - presenter); + mClient.requestShowFillUi( + this.id, id, width, height, view.getVirtualBounds(), presenter); } catch (RemoteException e) { Slog.e(TAG, "Error requesting to show fill UI", e); } } else { if (sDebug) { - Slog.d(TAG, "Do not show full UI on " + id + " as it is not the current view (" - + mCurrentViewId + ") anymore"); + Slog.d( + TAG, + "Do not show full UI on " + + id + + " as it is not the current view (" + + mCurrentViewId + + ") anymore"); } } } @@ -2722,8 +2904,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void dispatchUnhandledKey(AutofillId id, KeyEvent keyEvent) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#dispatchUnhandledKey() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#dispatchUnhandledKey() rejected - session: " + + id + + " destroyed"); return; } if (id.equals(mCurrentViewId)) { @@ -2733,8 +2918,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.e(TAG, "Error requesting to dispatch unhandled key", e); } } else { - Slog.w(TAG, "Do not dispatch unhandled key on " + id - + " as it is not the current view (" + mCurrentViewId + ") anymore"); + Slog.w( + TAG, + "Do not dispatch unhandled key on " + + id + + " as it is not the current view (" + + mCurrentViewId + + ") anymore"); } } } @@ -2790,17 +2980,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public void startIntentSender(IntentSender intentSender, Intent intent) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#startIntentSender() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#startIntentSender() rejected - session: " + + id + + " destroyed"); return; } if (intent == null) { removeFromServiceLocked(); } } - mHandler.sendMessage(obtainMessage( - Session::doStartIntentSender, - this, intentSender, intent)); + mHandler.sendMessage( + obtainMessage(Session::doStartIntentSender, this, intentSender, intent)); } // AutoFillUiCallback @@ -2865,13 +3057,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void setAuthenticationResultLocked(Bundle data, int authenticationId) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#setAuthenticationResultLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#setAuthenticationResultLocked() rejected - session: " + + id + + " destroyed"); return; } if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): id= " + authenticationId - + ", data=" + data); + Slog.d( + TAG, + "setAuthenticationResultLocked(): id= " + authenticationId + ", data=" + data); } final int requestId = AutofillManager.getRequestIdFromAuthenticationId(authenticationId); if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) { @@ -2890,9 +3086,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState removeFromService(); return; } - final FillResponse authenticatedResponse = mRequestId.isSecondaryProvider(requestId) - ? mSecondaryResponses.get(requestId) - : mResponses.get(requestId); + final FillResponse authenticatedResponse = + mRequestId.isSecondaryProvider(requestId) + ? mSecondaryResponses.get(requestId) + : mResponses.get(requestId); if (authenticatedResponse == null || data == null) { Slog.w(TAG, "no authenticated response"); mPresentationStatsEventLogger.maybeSetAuthenticationResult( @@ -2902,8 +3099,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return; } - final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId( - authenticationId); + final int datasetIdx = AutofillManager.getDatasetIdFromAuthenticationId(authenticationId); Dataset dataset = null; // Authenticated a dataset - reset view state regardless if we got a response or a dataset if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { @@ -2922,33 +3118,45 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags.mExpiredResponse = false; final Parcelable result = data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT); - final GetCredentialException exception = data.getSerializable( - CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, - GetCredentialException.class); + final GetCredentialException exception = + data.getSerializable( + CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, + GetCredentialException.class); final Bundle newClientState = data.getBundle(AutofillManager.EXTRA_CLIENT_STATE); if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): result=" + result - + ", clientState=" + newClientState + ", authenticationId=" + authenticationId); - } - if (Flags.autofillCredmanDevIntegration() && exception != null + Slog.d( + TAG, + "setAuthenticationResultLocked(): result=" + + result + + ", clientState=" + + newClientState + + ", authenticationId=" + + authenticationId); + } + if (Flags.autofillCredmanDevIntegration() + && exception != null && !exception.getType().equals(GetCredentialException.TYPE_USER_CANCELED)) { if (dataset != null && dataset.getFieldIds().size() == 1) { if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): result returns with" - + "Credential Manager Exception"); + Slog.d( + TAG, + "setAuthenticationResultLocked(): result returns with" + + "Credential Manager Exception"); } AutofillId autofillId = dataset.getFieldIds().get(0); - sendCredentialManagerResponseToApp(/*response=*/ null, - (GetCredentialException) exception, autofillId); + sendCredentialManagerResponseToApp( + /* response= */ null, (GetCredentialException) exception, autofillId); } return; } if (result instanceof FillResponse) { if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): received FillResponse from" - + " authentication flow"); + Slog.d( + TAG, + "setAuthenticationResultLocked(): received FillResponse from" + + " authentication flow"); } logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_AUTHENTICATED); mPresentationStatsEventLogger.maybeSetAuthenticationResult( @@ -2963,33 +3171,40 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (dataset != null && dataset.getFieldIds().size() == 1) { AutofillId autofillId = dataset.getFieldIds().get(0); if (sDebug) { - Slog.d(TAG, "Received GetCredentialResponse from authentication flow," - + "for autofillId: " + autofillId); + Slog.d( + TAG, + "Received GetCredentialResponse from authentication flow," + + "for autofillId: " + + autofillId); } - sendCredentialManagerResponseToApp(response, - /*exception=*/ null, autofillId); + sendCredentialManagerResponseToApp(response, /* exception= */ null, autofillId); } } else if (Flags.autofillCredmanIntegration()) { - Dataset datasetFromCredentialResponse = getDatasetFromCredentialResponse( - (GetCredentialResponse) result); + Dataset datasetFromCredentialResponse = + getDatasetFromCredentialResponse((GetCredentialResponse) result); if (datasetFromCredentialResponse != null) { - autoFill(requestId, datasetIdx, datasetFromCredentialResponse, - false, UI_TYPE_UNKNOWN); + autoFill( + requestId, + datasetIdx, + datasetFromCredentialResponse, + false, + UI_TYPE_UNKNOWN); } } } else if (result instanceof Dataset) { if (sDebug) { - Slog.d(TAG, "setAuthenticationResultLocked(): received Dataset from" - + " authentication flow"); + Slog.d( + TAG, + "setAuthenticationResultLocked(): received Dataset from" + + " authentication flow"); } if (datasetIdx != AutofillManager.AUTHENTICATION_ID_DATASET_ID_UNDEFINED) { - logAuthenticationStatusLocked(requestId, - MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED); + logAuthenticationStatusLocked( + requestId, MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_SUCCESS); if (newClientState != null) { - if (sDebug) - Slog.d(TAG, "Updating client state from auth dataset"); + if (sDebug) Slog.d(TAG, "Updating client state from auth dataset"); mClientState = newClientState; } Dataset datasetFromResult = getEffectiveDatasetForAuthentication((Dataset) result); @@ -2999,10 +3214,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } autoFill(requestId, datasetIdx, datasetFromResult, false, UI_TYPE_UNKNOWN); } else { - Slog.w(TAG, "invalid index (" + datasetIdx + ") for authentication id " - + authenticationId); - logAuthenticationStatusLocked(requestId, - MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION); + Slog.w( + TAG, + "invalid index (" + + datasetIdx + + ") for authentication id " + + authenticationId); + logAuthenticationStatusLocked( + requestId, MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); } @@ -3010,8 +3229,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (result != null) { Slog.w(TAG, "service returned invalid auth type: " + result); } - logAuthenticationStatusLocked(requestId, - MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION); + logAuthenticationStatusLocked(requestId, MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION); mPresentationStatsEventLogger.maybeSetAuthenticationResult( AUTHENTICATION_RESULT_FAILURE); processNullResponseLocked(requestId, 0); @@ -3036,8 +3254,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "DBG: authenticated effective response: " + response); } if (response == null || response.getDatasets().size() == 0) { - Log.wtf(TAG, "No datasets in fill response on authentication. response = " - + (response == null ? "null" : response.toString())); + Log.wtf( + TAG, + "No datasets in fill response on authentication. response = " + + (response == null ? "null" : response.toString())); return authenticatedDataset; } List<Dataset> datasets = response.getDatasets(); @@ -3047,8 +3267,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState for (Dataset dataset : datasets) { if (!dataset.getFieldIds().isEmpty()) { for (int i = 0; i < dataset.getFieldIds().size(); i++) { - builder.setField(dataset.getFieldIds().get(i), - new Field.Builder().setValue(dataset.getFieldValues().get(i)) + builder.setField( + dataset.getFieldIds().get(i), + new Field.Builder() + .setValue(dataset.getFieldValues().get(i)) .build()); } } @@ -3063,12 +3285,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Returns whether the dataset returned from the authentication result is ephemeral or not. - * See {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more - * information. + * Returns whether the dataset returned from the authentication result is ephemeral or not. See + * {@link AutofillManager#EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET} for more information. */ - private static boolean isAuthResultDatasetEphemeral(@Nullable Dataset oldDataset, - @NonNull Bundle authResultData) { + private static boolean isAuthResultDatasetEphemeral( + @Nullable Dataset oldDataset, @NonNull Bundle authResultData) { if (authResultData.containsKey( AutofillManager.EXTRA_AUTHENTICATION_RESULT_EPHEMERAL_DATASET)) { return authResultData.getBoolean( @@ -3079,11 +3300,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * A dataset can potentially have multiple fields, and it's possible that some of the fields' - * has inline presentation and some don't. It's also possible that some of the fields' - * inline presentation is pinned and some isn't. So the concept of whether a dataset is - * pinned or not is ill-defined. Here we say a dataset is pinned if any of the field has a - * pinned inline presentation in the dataset. It's not ideal but hopefully it is sufficient - * for most of the cases. + * has inline presentation and some don't. It's also possible that some of the fields' inline + * presentation is pinned and some isn't. So the concept of whether a dataset is pinned or not + * is ill-defined. Here we say a dataset is pinned if any of the field has a pinned inline + * presentation in the dataset. It's not ideal but hopefully it is sufficient for most of the + * cases. */ private static boolean isPinnedDataset(@Nullable Dataset dataset) { if (dataset != null && dataset.getFieldIds() != null) { @@ -3100,16 +3321,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void setAuthenticationResultForAugmentedAutofillLocked(Bundle data, int authId) { - final Dataset dataset = (data == null) ? null : - data.getParcelable(AutofillManager.EXTRA_AUTHENTICATION_RESULT, android.service.autofill.Dataset.class); + final Dataset dataset = + (data == null) + ? null + : data.getParcelable( + AutofillManager.EXTRA_AUTHENTICATION_RESULT, + android.service.autofill.Dataset.class); if (sDebug) { - Slog.d(TAG, "Auth result for augmented autofill: sessionId=" + id - + ", authId=" + authId + ", dataset=" + dataset); - } - final AutofillId fieldId = (dataset != null && dataset.getFieldIds().size() == 1) - ? dataset.getFieldIds().get(0) : null; - final AutofillValue value = (dataset != null && dataset.getFieldValues().size() == 1) - ? dataset.getFieldValues().get(0) : null; + Slog.d( + TAG, + "Auth result for augmented autofill: sessionId=" + + id + + ", authId=" + + authId + + ", dataset=" + + dataset); + } + final AutofillId fieldId = + (dataset != null && dataset.getFieldIds().size() == 1) + ? dataset.getFieldIds().get(0) + : null; + final AutofillValue value = + (dataset != null && dataset.getFieldValues().size() == 1) + ? dataset.getFieldValues().get(0) + : null; final ClipData content = (dataset != null) ? dataset.getFieldContent() : null; if (fieldId == null || (value == null && content == null)) { if (sDebug) { @@ -3154,8 +3389,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Fill the value into the field. if (sDebug) { - Slog.d(TAG, "Filling after auth: fieldId=" + fieldId + ", value=" + value - + ", content=" + content); + Slog.d( + TAG, + "Filling after auth: fieldId=" + + fieldId + + ", value=" + + value + + ", content=" + + content); } try { if (content != null) { @@ -3164,8 +3405,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mClient.autofill(id, dataset.getFieldIds(), dataset.getFieldValues(), true); } } catch (RemoteException e) { - Slog.w(TAG, "Error filling after auth: fieldId=" + fieldId + ", value=" + value - + ", content=" + content, e); + Slog.w( + TAG, + "Error filling after auth: fieldId=" + + fieldId + + ", value=" + + value + + ", content=" + + content, + e); } // Clear the suggestions since the user already accepted one of them. @@ -3175,8 +3423,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void setHasCallbackLocked(boolean hasIt) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#setHasCallbackLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#setHasCallbackLocked() rejected - session: " + + id + + " destroyed"); return; } mHasCallback = hasIt; @@ -3185,9 +3436,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") @Nullable private FillResponse getLastResponseLocked(@Nullable String logPrefixFmt) { - final String logPrefix = sDebug && logPrefixFmt != null - ? String.format(logPrefixFmt, this.id) - : null; + final String logPrefix = + sDebug && logPrefixFmt != null ? String.format(logPrefixFmt, this.id) : null; if (mContexts == null) { if (logPrefix != null) Slog.d(TAG, logPrefix + ": no contexts"); return null; @@ -3204,16 +3454,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int lastResponseIdx = getLastResponseIndexLocked(); if (lastResponseIdx < 0) { if (logPrefix != null) { - Slog.w(TAG, logPrefix + ": did not get last response. mResponses=" + mResponses - + ", mViewStates=" + mViewStates); + Slog.w( + TAG, + logPrefix + + ": did not get last response. mResponses=" + + mResponses + + ", mViewStates=" + + mViewStates); } return null; } final FillResponse response = mResponses.valueAt(lastResponseIdx); if (sVerbose && logPrefix != null) { - Slog.v(TAG, logPrefix + ": mResponses=" + mResponses + ", mContexts=" + mContexts - + ", mViewStates=" + mViewStates); + Slog.v( + TAG, + logPrefix + + ": mResponses=" + + mResponses + + ", mContexts=" + + mContexts + + ", mViewStates=" + + mViewStates); } return response; } @@ -3232,9 +3494,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Get statistic information of save info in current session. Specifically - * 1. how many save info the current session has. - * 2. How many distinct save data types current session has. + * Get statistic information of save info in current session. Specifically 1. how many save info + * the current session has. 2. How many distinct save data types current session has. * * @return SaveInfoStats returns the above two number in a SaveInfoStats object */ @@ -3255,12 +3516,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ public void logContextCommitted() { if (sVerbose) { - Slog.v(TAG, "logContextCommitted (" + id + "): commit_reason:" + COMMIT_REASON_UNKNOWN - + " no_save_reason:" + Event.NO_SAVE_UI_REASON_NONE); - } - mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this, - Event.NO_SAVE_UI_REASON_NONE, - COMMIT_REASON_UNKNOWN)); + Slog.v( + TAG, + "logContextCommitted (" + + id + + "): commit_reason:" + + COMMIT_REASON_UNKNOWN + + " no_save_reason:" + + Event.NO_SAVE_UI_REASON_NONE); + } + mHandler.sendMessage( + obtainMessage( + Session::handleLogContextCommitted, + this, + Event.NO_SAVE_UI_REASON_NONE, + COMMIT_REASON_UNKNOWN)); synchronized (mLock) { logAllEventsLocked(COMMIT_REASON_UNKNOWN); } @@ -3274,16 +3544,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @param saveDialogNotShowReason The reason why a save dialog was not shown. * @param commitReason The reason why context is committed. */ - @GuardedBy("mLock") - public void logContextCommittedLocked(@NoSaveReason int saveDialogNotShowReason, - @AutofillCommitReason int commitReason) { + public void logContextCommittedLocked( + @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { if (sVerbose) { - Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason - + " no_save_reason:" + saveDialogNotShowReason); - } - mHandler.sendMessage(obtainMessage(Session::handleLogContextCommitted, this, - saveDialogNotShowReason, commitReason)); + Slog.v( + TAG, + "logContextCommittedLocked (" + + id + + "): commit_reason:" + + commitReason + + " no_save_reason:" + + saveDialogNotShowReason); + } + mHandler.sendMessage( + obtainMessage( + Session::handleLogContextCommitted, + this, + saveDialogNotShowReason, + commitReason)); mSessionCommittedEventLogger.maybeSetCommitReason(commitReason); mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); @@ -3295,8 +3574,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE); } - private void handleLogContextCommitted(@NoSaveReason int saveDialogNotShowReason, - @AutofillCommitReason int commitReason) { + private void handleLogContextCommitted( + @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { final FillResponse lastResponse; synchronized (mLock) { lastResponse = getLastResponseLocked("logContextCommited(%s)"); @@ -3326,31 +3605,42 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Sets field classification scores if (userData != null && fcStrategy != null) { - logFieldClassificationScore(fcStrategy, userData, saveDialogNotShowReason, - commitReason); + logFieldClassificationScore( + fcStrategy, userData, saveDialogNotShowReason, commitReason); } else { logContextCommitted(null, null, saveDialogNotShowReason, commitReason); } } - private void logContextCommitted(@Nullable ArrayList<AutofillId> detectedFieldIds, + private void logContextCommitted( + @Nullable ArrayList<AutofillId> detectedFieldIds, @Nullable ArrayList<FieldClassification> detectedFieldClassifications, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { synchronized (mLock) { - logContextCommittedLocked(detectedFieldIds, detectedFieldClassifications, - saveDialogNotShowReason, commitReason); + logContextCommittedLocked( + detectedFieldIds, + detectedFieldClassifications, + saveDialogNotShowReason, + commitReason); } } @GuardedBy("mLock") - private void logContextCommittedLocked(@Nullable ArrayList<AutofillId> detectedFieldIds, + private void logContextCommittedLocked( + @Nullable ArrayList<AutofillId> detectedFieldIds, @Nullable ArrayList<FieldClassification> detectedFieldClassifications, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { if (sVerbose) { - Slog.v(TAG, "logContextCommittedLocked (" + id + "): commit_reason:" + commitReason - + " no_save_reason:" + saveDialogNotShowReason); + Slog.v( + TAG, + "logContextCommittedLocked (" + + id + + "): commit_reason:" + + commitReason + + " no_save_reason:" + + saveDialogNotShowReason); } final FillResponse lastResponse = getLastResponseLocked("logContextCommited(%s)"); if (lastResponse == null) return; @@ -3423,8 +3713,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue currentValue = viewState.getCurrentValue(); if (autofilledValue != null && autofilledValue.equals(currentValue)) { if (sDebug) { - Slog.d(TAG, "logContextCommitted(): ignoring changed " + viewState - + " because it has same value that was autofilled"); + Slog.d( + TAG, + "logContextCommitted(): ignoring changed " + + viewState + + " because it has same value that was autofilled"); } continue; } @@ -3442,8 +3735,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue currentValue = viewState.getCurrentValue(); if (currentValue == null) { if (sDebug) { - Slog.d(TAG, "logContextCommitted(): skipping view without current " - + "value ( " + viewState + ")"); + Slog.d( + TAG, + "logContextCommitted(): skipping view without current " + + "value ( " + + viewState + + ")"); } continue; } @@ -3455,7 +3752,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final List<Dataset> datasets = response.getDatasets(); if (datasets == null || datasets.isEmpty()) { if (sVerbose) { - Slog.v(TAG, "logContextCommitted() no datasets at " + j); + Slog.v(TAG, "logContextCommitted() no datasets at " + j); } } else { for (int k = 0; k < datasets.size(); k++) { @@ -3463,8 +3760,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final String datasetId = dataset.getId(); if (datasetId == null) { if (sVerbose) { - Slog.v(TAG, "logContextCommitted() skipping idless " - + "dataset " + dataset); + Slog.v( + TAG, + "logContextCommitted() skipping idless " + + "dataset " + + dataset); } } else { final ArrayList<AutofillValue> values = @@ -3473,9 +3773,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue candidate = values.get(l); if (currentValue.equals(candidate)) { if (sDebug) { - Slog.d(TAG, "field " + viewState.id + " was " - + "manually filled with value set by " - + "dataset " + datasetId); + Slog.d( + TAG, + "field " + + viewState.id + + " was manually filled with" + + " value set by dataset " + + datasetId); } if (manuallyFilledIds == null) { manuallyFilledIds = new ArrayMap<>(); @@ -3524,10 +3828,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - mService.logContextCommittedLocked(id, mClientState, mSelectedDatasetIds, ignoredDatasets, - changedFieldIds, changedDatasetIds, manuallyFilledFieldIds, - manuallyFilledDatasetIds, detectedFieldIds, detectedFieldClassifications, - mComponentName, mCompatMode, saveDialogNotShowReason); + mService.logContextCommittedLocked( + id, + mClientState, + mSelectedDatasetIds, + ignoredDatasets, + changedFieldIds, + changedDatasetIds, + manuallyFilledFieldIds, + manuallyFilledDatasetIds, + detectedFieldIds, + detectedFieldClassifications, + mComponentName, + mCompatMode, + saveDialogNotShowReason); mSessionCommittedEventLogger.maybeSetCommitReason(commitReason); mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); mSaveEventLogger.maybeSetSaveUiNotShownReason(saveDialogNotShowReason); @@ -3537,7 +3851,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Adds the matches to {@code detectedFieldsIds} and {@code detectedFieldClassifications} for * {@code fieldId} based on its {@code currentValue} and {@code userData}. */ - private void logFieldClassificationScore(@NonNull FieldClassificationStrategy fcStrategy, + private void logFieldClassificationScore( + @NonNull FieldClassificationStrategy fcStrategy, @NonNull FieldClassificationUserData userData, @NoSaveReason int saveDialogNotShowReason, @AutofillCommitReason int commitReason) { @@ -3555,16 +3870,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (userValues == null || categoryIds == null || userValues.length != categoryIds.length) { final int valuesLength = userValues == null ? -1 : userValues.length; final int idsLength = categoryIds == null ? -1 : categoryIds.length; - Slog.w(TAG, "setScores(): user data mismatch: values.length = " - + valuesLength + ", ids.length = " + idsLength); + Slog.w( + TAG, + "setScores(): user data mismatch: values.length = " + + valuesLength + + ", ids.length = " + + idsLength); return; } final int maxFieldsSize = UserData.getMaxFieldClassificationIdsSize(); final ArrayList<AutofillId> detectedFieldIds = new ArrayList<>(maxFieldsSize); - final ArrayList<FieldClassification> detectedFieldClassifications = new ArrayList<>( - maxFieldsSize); + final ArrayList<FieldClassification> detectedFieldClassifications = + new ArrayList<>(maxFieldsSize); final Collection<ViewState> viewStates; synchronized (mLock) { @@ -3583,33 +3902,49 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Then use the results, asynchronously - final RemoteCallback callback = new RemoteCallback( - new LogFieldClassificationScoreOnResultListener( - this, - saveDialogNotShowReason, - commitReason, - viewsSize, - autofillIds, - userValues, - categoryIds, - detectedFieldIds, - detectedFieldClassifications)); - - fcStrategy.calculateScores(callback, currentValues, userValues, categoryIds, - defaultAlgorithm, defaultArgs, algorithms, args); - } - - void handleLogFieldClassificationScore(@Nullable Bundle result, int saveDialogNotShowReason, - int commitReason, int viewsSize, AutofillId[] autofillIds, String[] userValues, - String[] categoryIds, ArrayList<AutofillId> detectedFieldIds, + final RemoteCallback callback = + new RemoteCallback( + new LogFieldClassificationScoreOnResultListener( + this, + saveDialogNotShowReason, + commitReason, + viewsSize, + autofillIds, + userValues, + categoryIds, + detectedFieldIds, + detectedFieldClassifications)); + + fcStrategy.calculateScores( + callback, + currentValues, + userValues, + categoryIds, + defaultAlgorithm, + defaultArgs, + algorithms, + args); + } + + void handleLogFieldClassificationScore( + @Nullable Bundle result, + int saveDialogNotShowReason, + int commitReason, + int viewsSize, + AutofillId[] autofillIds, + String[] userValues, + String[] categoryIds, + ArrayList<AutofillId> detectedFieldIds, ArrayList<FieldClassification> detectedFieldClassifications) { if (result == null) { if (sDebug) Slog.d(TAG, "setFieldClassificationScore(): no results"); logContextCommitted(null, null, saveDialogNotShowReason, commitReason); return; } - final Scores scores = result.getParcelable(EXTRA_SCORES, - android.service.autofill.AutofillFieldClassificationService.Scores.class); + final Scores scores = + result.getParcelable( + EXTRA_SCORES, + android.service.autofill.AutofillFieldClassificationService.Scores.class); if (scores == null) { Slog.w(TAG, "No field classification score on " + result); return; @@ -3633,14 +3968,24 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final Float currentScore = scoresByField.get(categoryId); if (currentScore != null && currentScore > score) { if (sVerbose) { - Slog.v(TAG, "skipping score " + score - + " because it's less than " + currentScore); + Slog.v( + TAG, + "skipping score " + + score + + " because it's less than " + + currentScore); } continue; } if (sVerbose) { - Slog.v(TAG, "adding score " + score + " at index " + j + " and id " - + autofillId); + Slog.v( + TAG, + "adding score " + + score + + " at index " + + j + + " and id " + + autofillId); } scoresByField.put(categoryId, score); } else if (sVerbose) { @@ -3666,13 +4011,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState wtf(e, "Error accessing FC score at [%d, %d] (%s): %s", i, j, scores, e); return; } - logContextCommitted(detectedFieldIds, detectedFieldClassifications, - saveDialogNotShowReason, commitReason); + logContextCommitted( + detectedFieldIds, + detectedFieldClassifications, + saveDialogNotShowReason, + commitReason); } /** - * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} - * when necessary. + * Generates a {@link android.service.autofill.FillEventHistory.Event#TYPE_SAVE_SHOWN} when + * necessary. * * <p>Note: It is necessary to call logContextCommitted() first before calling this method. */ @@ -3689,11 +4037,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @NonNull public SaveResult showSaveLocked() { if (mDestroyed) { - Slog.w(TAG, "Call to Session#showSaveLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#showSaveLocked() rejected - session: " + id + " destroyed"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ false, Event.NO_SAVE_UI_REASON_NONE); } mSessionState = STATE_FINISHED; @@ -3705,12 +4056,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ if (mSessionFlags.mScreenHasCredmanField) { if (sVerbose) { - Slog.v(TAG, "Call to Session#showSaveLocked() rejected - " - + "there is credman field in screen"); + Slog.v( + TAG, + "Call to Session#showSaveLocked() rejected - " + + "there is credman field in screen"); } mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SCREEN_HAS_CREDMAN_FIELD); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_NONE); } @@ -3728,7 +4083,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) Slog.v(TAG, "showSaveLocked(" + this.id + "): no saveInfo from service"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_SAVE_INFO); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_NO_SAVE_INFO); } @@ -3737,7 +4094,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) Slog.v(TAG, "showSaveLocked(" + this.id + "): service asked to delay save"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_WITH_DELAY_SAVE_FLAG); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ false, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ false, Event.NO_SAVE_UI_REASON_WITH_DELAY_SAVE_FLAG); } @@ -3774,12 +4133,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Some apps clear the form before navigating to other activities. // If current value is empty, consider fall back to last cached // non-empty result first. - final AutofillValue candidateSaveValue = - viewState.getCandidateSaveValue(); + final AutofillValue candidateSaveValue = viewState.getCandidateSaveValue(); if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { if (sVerbose) { - Slog.v(TAG, "current value is empty, using cached last non-empty " - + "value instead"); + Slog.v( + TAG, + "current value is empty, using cached last non-empty " + + "value instead"); } value = candidateSaveValue; } else { @@ -3788,8 +4148,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue initialValue = getValueFromContextsLocked(id); if (initialValue != null) { if (sDebug) { - Slog.d(TAG, "Value of required field " + id + " didn't change; " - + "using initial value (" + initialValue + ") instead"); + Slog.d( + TAG, + "Value of required field " + + id + + " didn't change; " + + "using initial value (" + + initialValue + + ") instead"); } value = initialValue; } else { @@ -3821,8 +4187,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue initialValue = getValueFromContextsLocked(id); if (initialValue != null && initialValue.equals(value)) { if (sDebug) { - Slog.d(TAG, "id " + id + " is part of dataset but initial value " - + "didn't change: " + value); + Slog.d( + TAG, + "id " + + id + + " is part of dataset but initial value " + + "didn't change: " + + value); } changed = false; } else { @@ -3833,8 +4204,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (changed) { if (sDebug) { - Slog.d(TAG, "found a change on required " + id + ": " + filledValue - + " => " + value); + Slog.d( + TAG, + "found a change on required " + + id + + ": " + + filledValue + + " => " + + value); } atLeastOneChanged = true; } @@ -3844,8 +4221,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId[] optionalIds = saveInfo.getOptionalIds(); if (sVerbose) { - Slog.v(TAG, "allRequiredAreNotEmpty: " + allRequiredAreNotEmpty + " hasOptional: " - + (optionalIds != null)); + Slog.v( + TAG, + "allRequiredAreNotEmpty: " + + allRequiredAreNotEmpty + + " hasOptional: " + + (optionalIds != null)); } int saveDialogNotShowReason; if (!allRequiredAreNotEmpty) { @@ -3859,7 +4240,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // - if at least one required id changed but it was not part of a filled dataset, we // need to check if an optional id is part of a filled datased (in which case we show // Update instead of Save) - if (optionalIds!= null && (!atLeastOneChanged || !isUpdate)) { + if (optionalIds != null && (!atLeastOneChanged || !isUpdate)) { // No change on required ids yet, look for changes on optional ids. for (int i = 0; i < optionalIds.length; i++) { final AutofillId id = optionalIds[i]; @@ -3879,8 +4260,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState viewState.getCandidateSaveValue(); if (candidateSaveValue != null && !candidateSaveValue.isEmpty()) { if (sVerbose) { - Slog.v(TAG, "current value is empty, using cached last " - + "non-empty value instead"); + Slog.v( + TAG, + "current value is empty, using cached last " + + "non-empty value instead"); } currentValue = candidateSaveValue; } @@ -3897,8 +4280,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue filledValue = viewState.getAutofilledValue(); if (value != null && !value.equals(filledValue)) { if (sDebug) { - Slog.d(TAG, "found a change on optional " + id + ": " + filledValue - + " => " + value); + Slog.d( + TAG, + "found a change on optional " + + id + + ": " + + filledValue + + " => " + + value); } if (filledValue != null) { isUpdate = true; @@ -3907,12 +4296,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } atLeastOneChanged = true; } - } else { + } else { // Update current values cache based on initial value final AutofillValue initialValue = getValueFromContextsLocked(id); if (sDebug) { - Slog.d(TAG, "no current value for " + id + "; initial value is " - + initialValue); + Slog.d( + TAG, + "no current value for " + + id + + "; initial value is " + + initialValue); } if (initialValue != null) { currentValues.put(id, initialValue); @@ -3935,17 +4328,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { isValid = validator.isValid(this); if (sDebug) Slog.d(TAG, validator + " returned " + isValid); - log.setType(isValid - ? MetricsEvent.TYPE_SUCCESS - : MetricsEvent.TYPE_DISMISS); + log.setType( + isValid ? MetricsEvent.TYPE_SUCCESS : MetricsEvent.TYPE_DISMISS); } catch (Exception e) { Slog.e(TAG, "Not showing save UI because validation failed:", e); log.setType(MetricsEvent.TYPE_FAILURE); mMetricsLogger.write(log); mSaveEventLogger.maybeSetSaveUiNotShownReason( - NO_SAVE_REASON_FIELD_VALIDATION_FAILED); + NO_SAVE_REASON_FIELD_VALIDATION_FAILED); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED); } @@ -3953,9 +4347,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (!isValid) { Slog.i(TAG, "not showing save UI because fields failed validation"); mSaveEventLogger.maybeSetSaveUiNotShownReason( - NO_SAVE_REASON_FIELD_VALIDATION_FAILED); + NO_SAVE_REASON_FIELD_VALIDATION_FAILED); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_FIELD_VALIDATION_FAILED); } } @@ -3964,15 +4360,23 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // content. final List<Dataset> datasets = response.getDatasets(); if (datasets != null) { - datasets_loop: for (int i = 0; i < datasets.size(); i++) { + datasets_loop: + for (int i = 0; i < datasets.size(); i++) { final Dataset dataset = datasets.get(i); final ArrayMap<AutofillId, AutofillValue> datasetValues = Helper.getFields(dataset); if (sVerbose) { - Slog.v(TAG, "Checking if saved fields match contents of dataset #" + i - + ": " + dataset + "; savableIds=" + savableIds); + Slog.v( + TAG, + "Checking if saved fields match contents of dataset #" + + i + + ": " + + dataset + + "; savableIds=" + + savableIds); } - savable_ids_loop: for (int j = 0; j < savableIds.size(); j++) { + savable_ids_loop: + for (int j = 0; j < savableIds.size(); j++) { final AutofillId id = savableIds.valueAt(j); final AutofillValue currentValue = currentValues.get(id); if (currentValue == null) { @@ -3984,20 +4388,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillValue datasetValue = datasetValues.get(id); if (!currentValue.equals(datasetValue)) { if (sDebug) { - Slog.d(TAG, "found a dataset change on id " + id + ": from " - + datasetValue + " to " + currentValue); + Slog.d( + TAG, + "found a dataset change on id " + + id + + ": from " + + datasetValue + + " to " + + currentValue); } continue datasets_loop; } if (sVerbose) Slog.v(TAG, "no dataset changes for id " + id); } if (sDebug) { - Slog.d(TAG, "ignoring Save UI because all fields match contents of " - + "dataset #" + i + ": " + dataset); + Slog.d( + TAG, + "ignoring Save UI because all fields match contents of " + + "dataset #" + + i + + ": " + + dataset); } mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_DATASET_MATCH); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_DATASET_MATCH); } } @@ -4015,13 +4432,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState wtf(null, "showSaveLocked(): no service label or icon"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NONE); mSaveEventLogger.logAndEndEvent(); - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, + return new SaveResult( + /* logSaveShown= */ false, + /* removeSession= */ true, Event.NO_SAVE_UI_REASON_NONE); } - getUiForShowing().showSaveUi(serviceLabel, serviceIcon, - mService.getServicePackageName(), saveInfo, this, - mComponentName, this, mContext, mPendingSaveUi, isUpdate, mCompatMode, - response.getShowSaveDialogIcon(), mSaveEventLogger); + getUiForShowing() + .showSaveUi( + serviceLabel, + serviceIcon, + mService.getServicePackageName(), + saveInfo, + this, + mComponentName, + this, + mContext, + mPendingSaveUi, + isUpdate, + mCompatMode, + response.getShowSaveDialogIcon(), + mSaveEventLogger); if (client != null) { try { client.setSaveUiState(id, true); @@ -4031,21 +4461,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } mSessionFlags.mShowingSaveUi = true; if (sDebug) { - Slog.d(TAG, "Good news, everyone! All checks passed, show save UI for " - + id + "!"); + Slog.d( + TAG, + "Good news, everyone! All checks passed, show save UI for " + id + "!"); } - return new SaveResult(/* logSaveShown= */ true, /* removeSession= */ false, + return new SaveResult( + /* logSaveShown= */ true, + /* removeSession= */ false, Event.NO_SAVE_UI_REASON_NONE); } } // Nothing changed... if (sDebug) { - Slog.d(TAG, "showSaveLocked(" + id +"): with no changes, comes no responsibilities." - + "allRequiredAreNotNull=" + allRequiredAreNotEmpty - + ", atLeastOneChanged=" + atLeastOneChanged); + Slog.d( + TAG, + "showSaveLocked(" + + id + + "): with no changes, comes no responsibilities." + + "allRequiredAreNotNull=" + + allRequiredAreNotEmpty + + ", atLeastOneChanged=" + + atLeastOneChanged); } - return new SaveResult(/* logSaveShown= */ false, /* removeSession= */ true, - saveDialogNotShowReason); + return new SaveResult( + /* logSaveShown= */ false, /* removeSession= */ true, saveDialogNotShowReason); } private void logSaveShown() { @@ -4078,25 +4517,21 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return sanitized; } - /** - * Returns whether the session is currently showing the save UI - */ + /** Returns whether the session is currently showing the save UI */ @GuardedBy("mLock") boolean isSaveUiShowingLocked() { return mSessionFlags.mShowingSaveUi; } - /** - * Gets the latest non-empty value for the given id in the autofill contexts. - */ + /** Gets the latest non-empty value for the given id in the autofill contexts. */ @GuardedBy("mLock") @Nullable private ViewNode getViewNodeFromContextsLocked(@NonNull AutofillId autofillId) { final int numContexts = mContexts.size(); for (int i = numContexts - 1; i >= 0; i--) { final FillContext context = mContexts.get(i); - final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), - autofillId); + final ViewNode node = + Helper.findViewNodeByAutofillId(context.getStructure(), autofillId); if (node != null) { return node; } @@ -4104,22 +4539,28 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } - /** - * Gets the latest non-empty value for the given id in the autofill contexts. - */ + /** Gets the latest non-empty value for the given id in the autofill contexts. */ @GuardedBy("mLock") @Nullable private AutofillValue getValueFromContextsLocked(@NonNull AutofillId autofillId) { final int numContexts = mContexts.size(); for (int i = numContexts - 1; i >= 0; i--) { final FillContext context = mContexts.get(i); - final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), - autofillId); + final ViewNode node = + Helper.findViewNodeByAutofillId(context.getStructure(), autofillId); if (node != null) { final AutofillValue value = node.getAutofillValue(); if (sDebug) { - Slog.d(TAG, "getValueFromContexts(" + this.id + "/" + autofillId + ") at " - + i + ": " + value); + Slog.d( + TAG, + "getValueFromContexts(" + + this.id + + "/" + + autofillId + + ") at " + + i + + ": " + + value); } if (value != null && !value.isEmpty()) { return value; @@ -4129,17 +4570,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } - /** - * Gets the latest autofill options for the given id in the autofill contexts. - */ + /** Gets the latest autofill options for the given id in the autofill contexts. */ @GuardedBy("mLock") @Nullable private CharSequence[] getAutofillOptionsFromContextsLocked(@NonNull AutofillId autofillId) { final int numContexts = mContexts.size(); for (int i = numContexts - 1; i >= 0; i--) { final FillContext context = mContexts.get(i); - final ViewNode node = Helper.findViewNodeByAutofillId(context.getStructure(), - autofillId); + final ViewNode node = + Helper.findViewNodeByAutofillId(context.getStructure(), autofillId); if (node != null && node.getAutofillOptions() != null) { return node.getAutofillOptions(); } @@ -4160,7 +4599,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final FillContext context = mContexts.get(contextNum); final ViewNode[] nodes = - context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); + context.findViewNodesByAutofillIds(getIdsOfAllViewStatesLocked()); if (sVerbose) Slog.v(TAG, "updateValuesForSaveLocked(): updating " + context); @@ -4191,8 +4630,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sanitizedValue != null) { node.updateAutofillValue(sanitizedValue); } else if (sDebug) { - Slog.d(TAG, "updateValuesForSaveLocked(): not updating field " + id - + " because it failed sanitization"); + Slog.d( + TAG, + "updateValuesForSaveLocked(): not updating field " + + id + + " because it failed sanitization"); } } @@ -4200,28 +4642,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState context.getStructure().sanitizeForParceling(false); if (sVerbose) { - Slog.v(TAG, "updateValuesForSaveLocked(): dumping structure of " + context - + " before calling service.save()"); + Slog.v( + TAG, + "updateValuesForSaveLocked(): dumping structure of " + + context + + " before calling service.save()"); context.getStructure().dump(false); } } } - /** - * Calls service when user requested save. - */ + /** Calls service when user requested save. */ @GuardedBy("mLock") void callSaveLocked() { if (mDestroyed) { - Slog.w(TAG, "Call to Session#callSaveLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#callSaveLocked() rejected - session: " + id + " destroyed"); mSaveEventLogger.maybeSetIsSaved(false); mSaveEventLogger.logAndEndEvent(); return; } if (mRemoteFillService == null) { - wtf(null, "callSaveLocked() called without a remote service. " - + "mForAugmentedAutofillOnly: %s", mSessionFlags.mAugmentedAutofillOnly); + wtf( + null, + "callSaveLocked() called without a remote service. " + + "mForAugmentedAutofillOnly: %s", + mSessionFlags.mAugmentedAutofillOnly); mSaveEventLogger.maybeSetIsSaved(false); mSaveEventLogger.logAndEndEvent(); return; @@ -4241,7 +4688,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Remove pending fill requests as the session is finished. cancelCurrentRequestLocked(); - final ArrayList<FillContext> contexts = mergePreviousSessionLocked( /* forSave= */ true); + final ArrayList<FillContext> contexts = mergePreviousSessionLocked(/* forSave= */ true); FieldClassificationResponse fieldClassificationResponse = mClassificationState.mLastFieldClassificationResponse; @@ -4251,8 +4698,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mClientState == null) { mClientState = new Bundle(); } - mClientState.putParcelableArrayList(EXTRA_KEY_DETECTIONS, new ArrayList<>( - fieldClassificationResponse.getClassifications())); + mClientState.putParcelableArrayList( + EXTRA_KEY_DETECTIONS, + new ArrayList<>(fieldClassificationResponse.getClassifications())); } final SaveRequest saveRequest = new SaveRequest(contexts, mClientState, mSelectedDatasetIds); @@ -4270,11 +4718,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * from previous sessions that were asked by the service to be delayed (if any). * * <p>As a side-effect: + * * <ul> - * <li>If the current {@link #mClientState} is {@code null}, sets it with the last non- - * {@code null} client state from previous sessions. + * <li>If the current {@link #mClientState} is {@code null}, sets it with the last non- {@code + * null} client state from previous sessions. * <li>When {@code forSave} is {@code true}, calls {@link #updateValuesForSaveLocked()} in the - * previous sessions. + * previous sessions. * </ul> */ @NonNull @@ -4283,30 +4732,51 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<FillContext> contexts; if (previousSessions != null) { if (sDebug) { - Slog.d(TAG, "mergeSessions(" + this.id + "): Merging the content of " - + previousSessions.size() + " sessions for task " + taskId); + Slog.d( + TAG, + "mergeSessions(" + + this.id + + "): Merging the content of " + + previousSessions.size() + + " sessions for task " + + taskId); } contexts = new ArrayList<>(); for (int i = 0; i < previousSessions.size(); i++) { final Session previousSession = previousSessions.get(i); final ArrayList<FillContext> previousContexts = previousSession.mContexts; if (previousContexts == null) { - Slog.w(TAG, "mergeSessions(" + this.id + "): Not merging null contexts from " - + previousSession.id); + Slog.w( + TAG, + "mergeSessions(" + + this.id + + "): Not merging null contexts from " + + previousSession.id); continue; } if (forSave) { previousSession.updateValuesForSaveLocked(); } if (sDebug) { - Slog.d(TAG, "mergeSessions(" + this.id + "): adding " + previousContexts.size() - + " context from previous session #" + previousSession.id); + Slog.d( + TAG, + "mergeSessions(" + + this.id + + "): adding " + + previousContexts.size() + + " context from previous session #" + + previousSession.id); } contexts.addAll(previousContexts); if (mClientState == null && previousSession.mClientState != null) { if (sDebug) { - Slog.d(TAG, "mergeSessions(" + this.id + "): setting client state from " - + "previous session" + previousSession.id); + Slog.d( + TAG, + "mergeSessions(" + + this.id + + "): setting client state from " + + "previous session" + + previousSession.id); } mClientState = previousSession.mClientState; } @@ -4351,8 +4821,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // If it's not, then check if it should start a partition. if (shouldStartNewPartitionLocked(id, flags)) { if (sDebug) { - Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": " - + viewState.getStateAsString()); + Slog.d( + TAG, + "Starting partition or augmented request for view id " + + id + + ": " + + viewState.getStateAsString()); } // Fix to always let standard autofill start. // Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as @@ -4363,8 +4837,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } if (sVerbose) { - Slog.v(TAG, "Not starting new partition for view " + id + ": " - + viewState.getStateAsString()); + Slog.v( + TAG, + "Not starting new partition for view " + + id + + ": " + + viewState.getStateAsString()); } return Optional.empty(); } @@ -4373,17 +4851,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Determines if a new partition should be started for an id. * * @param id The id of the view that is entered - * * @return {@code true} if a new partition should be started */ @GuardedBy("mLock") private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id, int flags) { final ViewState currentView = mViewStates.get(id); - SparseArray<FillResponse> responses = shouldRequestSecondaryProvider(flags) - ? mSecondaryResponses : mResponses; + SparseArray<FillResponse> responses = + shouldRequestSecondaryProvider(flags) ? mSecondaryResponses : mResponses; if (responses == null) { - return currentView != null && (currentView.getState() - & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0; + return currentView != null + && (currentView.getState() & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) + == 0; } if (mSessionFlags.mExpiredResponse) { @@ -4395,8 +4873,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int numResponses = responses.size(); if (numResponses >= AutofillManagerService.getPartitionMaxCount()) { - Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id - + " reached maximum of " + AutofillManagerService.getPartitionMaxCount()); + Slog.e( + TAG, + "Not starting a new partition on " + + id + + " because session " + + this.id + + " reached maximum of " + + AutofillManagerService.getPartitionMaxCount()); return false; } @@ -4437,8 +4921,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } boolean shouldRequestSecondaryProvider(int flags) { - if (!mService.isAutofillCredmanIntegrationEnabled() - || mSecondaryProviderHandler == null) { + if (!mService.isAutofillCredmanIntegrationEnabled() || mSecondaryProviderHandler == null) { return false; } if (mIsPrimaryCredential) { @@ -4452,8 +4935,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // 'Session.this.mLock', which is the same as mLock. @SuppressWarnings("GuardedBy") @GuardedBy("mLock") - void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action, - int flags) { + void updateLocked( + AutofillId id, Rect virtualBounds, AutofillValue value, int action, int flags) { if (mDestroyed) { Slog.w(TAG, "updateLocked(" + id + "): rejected - session: destroyed"); return; @@ -4464,7 +4947,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState Slog.d(TAG, "updateLocked(" + id + "): Set the response has expired."); } mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists( - NOT_SHOWN_REASON_VIEW_CHANGED); + NOT_SHOWN_REASON_VIEW_CHANGED); mPresentationStatsEventLogger.logAndEndEvent("ACTION_RESPONSE_EXPIRED"); return; } @@ -4474,23 +4957,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sVerbose) { Slog.v( TAG, - "updateLocked(" + id + "): " - + "id=" + this.id - + ", action=" + actionAsString(action) - + ", flags=" + flags - + ", mCurrentViewId=" + mCurrentViewId - + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse - + ", viewState=" + viewState); + "updateLocked(" + + id + + "): " + + "id=" + + this.id + + ", action=" + + actionAsString(action) + + ", flags=" + + flags + + ", mCurrentViewId=" + + mCurrentViewId + + ", mExpiredResponse=" + + mSessionFlags.mExpiredResponse + + ", viewState=" + + viewState); } if (viewState == null) { - if (action == ACTION_START_SESSION || action == ACTION_VALUE_CHANGED + if (action == ACTION_START_SESSION + || action == ACTION_VALUE_CHANGED || action == ACTION_VIEW_ENTERED) { if (sVerbose) Slog.v(TAG, "Creating viewState for " + id); boolean isIgnored = isIgnoredLocked(id); - viewState = new ViewState(id, this, - isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL, - mIsPrimaryCredential); + viewState = + new ViewState( + id, + this, + isIgnored ? ViewState.STATE_IGNORED : ViewState.STATE_INITIAL, + mIsPrimaryCredential); mViewStates.put(id, viewState); // TODO(b/73648631): for optimization purposes, should also ignore if change is @@ -4521,7 +5016,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionFlags.mScreenHasCredmanField = true; } - switch(action) { + switch (action) { case ACTION_START_SESSION: // View is triggering autofill. mCurrentViewId = viewState.id; @@ -4545,14 +5040,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState case ACTION_VALUE_CHANGED: if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) { // Must cancel the session if the value of the URL bar changed - final String currentUrl = mUrlBar == null ? null - : mUrlBar.getText().toString().trim(); + final String currentUrl = + mUrlBar == null ? null : mUrlBar.getText().toString().trim(); if (currentUrl == null) { // Validation check - shouldn't happen. wtf(null, "URL bar value changed, but current value is null"); return; } - if (value == null || ! value.isText()) { + if (value == null || !value.isText()) { // Validation check - shouldn't happen. wtf(null, "URL bar value changed to null or non-text: %s", value); return; @@ -4567,8 +5062,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // are finished, as the URL bar changed callback is usually called before // the virtual views become invisible. if (sDebug) { - Slog.d(TAG, "Ignoring change on URL because session will finish when " - + "views are gone"); + Slog.d( + TAG, + "Ignoring change on URL because session will finish when " + + "views are gone"); } return; } @@ -4598,8 +5095,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // isSameViewEntered has some limitations, where it isn't considered same view when // autofill suggestions pop up, user selects, and the focus lands back on the view. // isSameViewAgain tries to overcome that situation. - final boolean isSameViewAgain = isSameViewEntered - || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId); + final boolean isSameViewAgain = + isSameViewEntered + || Objects.equals(mCurrentViewId, mPreviousNonNullEnteredViewId); if (mCurrentViewId != null) { mPreviousNonNullEnteredViewId = mCurrentViewId; } @@ -4655,8 +5153,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Trigger augmented autofill if applicable if ((flags & FLAG_MANUAL_REQUEST) == 0) { // Not a manual request - if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains( - id)) { + if (mAugmentedAutofillableIds != null + && mAugmentedAutofillableIds.contains(id)) { // Regular autofill handled the view and returned null response, but it // triggered augmented autofill if (!isSameViewEntered) { @@ -4664,16 +5162,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState triggerAugmentedAutofillLocked(flags); } else { if (sDebug) { - Slog.d(TAG, "skip augmented autofill for same view: " - + "same view entered"); + Slog.d( + TAG, + "skip augmented autofill for same view: " + + "same view entered"); } } return; } else if (mSessionFlags.mAugmentedAutofillOnly && isSameViewEntered) { // Regular autofill is disabled. if (sDebug) { - Slog.d(TAG, "skip augmented autofill for same view: " - + "standard autofill disabled."); + Slog.d( + TAG, + "skip augmented autofill for same view: " + + "standard autofill disabled."); } return; } @@ -4717,8 +5219,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // on the IME side if it arrives before the input view is finished on the IME. mInlineSessionController.resetInlineFillUiLocked(); - if ((viewState.getState() & - ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) != 0) { + if ((viewState.getState() & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) + != 0) { // View was exited before Inline Request sent back, do not set it to // null yet to let onHandleAssistData finish processing } else { @@ -4728,7 +5230,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // It's not necessary that there's no more presentation for this view. It could // be that the user chose some suggestion, in which case, view exits. mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); + NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED); } break; default: @@ -4737,8 +5239,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void logPresentationStatsOnViewEnteredLocked(FillResponse response, - boolean isCredmanRequested) { + private void logPresentationStatsOnViewEnteredLocked( + FillResponse response, boolean isCredmanRequested) { mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested); mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( mFieldClassificationIdSnapshot); @@ -4754,16 +5256,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void hideAugmentedAutofillLocked(@NonNull ViewState viewState) { - if ((viewState.getState() - & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) { + if ((viewState.getState() & ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL) != 0) { viewState.resetState(ViewState.STATE_TRIGGERED_AUGMENTED_AUTOFILL); cancelAugmentedAutofillLocked(); } } - /** - * Checks whether a view should be ignored. - */ + /** Checks whether a view should be ignored. */ @GuardedBy("mLock") private boolean isIgnoredLocked(AutofillId id) { // Always check the latest response only @@ -4782,22 +5281,30 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState && getSaveInfoLocked() != null) { final int length = viewState.getCurrentValue().getTextValue().length(); if (sDebug) { - Slog.d(TAG, "updateLocked(" + id + "): resetting value that was " - + length + " chars long"); - } - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length); + Slog.d( + TAG, + "updateLocked(" + + id + + "): resetting value that was " + + length + + " chars long"); + } + final LogMaker log = + newLogMaker(MetricsEvent.AUTOFILL_VALUE_RESET) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_PREVIOUS_LENGTH, length); mMetricsLogger.write(log); } } @GuardedBy("mLock") - private void updateViewStateAndUiOnValueChangedLocked(AutofillId id, AutofillValue value, - ViewState viewState, int flags) { + private void updateViewStateAndUiOnValueChangedLocked( + AutofillId id, AutofillValue value, ViewState viewState, int flags) { // Cache the last non-empty value for save purpose. Some apps clear the form before // navigating to other activities. - if (mIgnoreViewStateResetToEmpty && (value == null || value.isEmpty()) - && viewState.getCurrentValue() != null && viewState.getCurrentValue().isText() + if (mIgnoreViewStateResetToEmpty + && (value == null || value.isEmpty()) + && viewState.getCurrentValue() != null + && viewState.getCurrentValue().isText() && viewState.getCurrentValue().getTextValue() != null && viewState.getCurrentValue().getTextValue().length() > 1) { if (sVerbose) { @@ -4875,8 +5382,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * indicate the IME attempting to probe the potentially sensitive content of inline suggestions. */ @GuardedBy("mLock") - private void updateFilteringStateOnValueChangedLocked(@Nullable String newTextValue, - ViewState viewState) { + private void updateFilteringStateOnValueChangedLocked( + @Nullable String newTextValue, ViewState viewState) { if (newTextValue == null) { // Don't just return here, otherwise the IME can circumvent this logic using non-text // values. @@ -4901,18 +5408,22 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @Override - public void onFillReady(@NonNull FillResponse response, @NonNull AutofillId filledId, - @Nullable AutofillValue value, int flags) { + public void onFillReady( + @NonNull FillResponse response, + @NonNull AutofillId filledId, + @Nullable AutofillValue value, + int flags) { synchronized (mLock) { mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId( mFieldClassificationIdSnapshot); if (mDestroyed) { - Slog.w(TAG, "Call to Session#onFillReady() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#onFillReady() rejected - session: " + id + " destroyed"); mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_SESSION_DESTROYED); mSaveEventLogger.logAndEndEvent(); mPresentationStatsEventLogger.maybeSetNoPresentationEventReason( - NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY); + NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY); mPresentationStatsEventLogger.logAndEndEvent("on fill ready"); return; } @@ -4954,7 +5465,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } else { setFillDialogDisabled(); } - } if (response.supportsInlineSuggestions()) { @@ -4971,10 +5481,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - getUiForShowing().showFillUi(filledId, response, filterText, - mService.getServicePackageName(), mComponentName, - serviceLabel, serviceIcon, this, mContext, id, mCompatMode, - mService.getMaster().getMaxInputLengthForAutofill()); + getUiForShowing() + .showFillUi( + filledId, + response, + filterText, + mService.getServicePackageName(), + mComponentName, + serviceLabel, + serviceIcon, + this, + mContext, + id, + mCompatMode, + mService.getMaster().getMaxInputLengthForAutofill()); synchronized (mLock) { if (mUiShownTime == 0) { @@ -4983,21 +5503,26 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final long duration = mUiShownTime - mStartTime; if (sDebug) { - final StringBuilder msg = new StringBuilder("1st UI for ") - .append(mActivityToken) - .append(" shown in "); + final StringBuilder msg = + new StringBuilder("1st UI for ") + .append(mActivityToken) + .append(" shown in "); TimeUtils.formatDuration(duration, msg); Slog.d(TAG, msg.toString()); } - final StringBuilder historyLog = new StringBuilder("id=").append(id) - .append(" app=").append(mActivityToken) - .append(" svc=").append(mService.getServicePackageName()) - .append(" latency="); + final StringBuilder historyLog = + new StringBuilder("id=") + .append(id) + .append(" app=") + .append(mActivityToken) + .append(" svc=") + .append(mService.getServicePackageName()) + .append(" latency="); TimeUtils.formatDuration(duration, historyLog); mUiLatencyHistory.log(historyLog.toString()); - addTaggedDataToRequestLogLocked(response.getRequestId(), - MetricsEvent.FIELD_AUTOFILL_DURATION, duration); + addTaggedDataToRequestLogLocked( + response.getRequestId(), MetricsEvent.FIELD_AUTOFILL_DURATION, duration); } } } @@ -5052,8 +5577,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private boolean requestShowFillDialog(FillResponse response, - AutofillId filledId, String filterText, int flags) { + private boolean requestShowFillDialog( + FillResponse response, AutofillId filledId, String filterText, int flags) { if (!isFillDialogUiEnabled()) { // Unsupported fill dialog UI if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled"); @@ -5079,7 +5604,6 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed."); return false; } - } Drawable serviceIcon = null; @@ -5087,21 +5611,31 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState serviceIcon = getServiceIcon(response); } - getUiForShowing().showFillDialog(filledId, response, filterText, - mService.getServicePackageName(), mComponentName, serviceIcon, this, - id, mCompatMode, mPresentationStatsEventLogger, mLock); + getUiForShowing() + .showFillDialog( + filledId, + response, + filterText, + mService.getServicePackageName(), + mComponentName, + serviceIcon, + this, + id, + mCompatMode, + mPresentationStatsEventLogger, + mLock); return true; } /** - * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able - * to be fetched, use the default provider icon instead + * Get the custom icon that was passed through FillResponse. If the custom icon wasn't able to + * be fetched, use the default provider icon instead * * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise */ @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's - // actually the same object as mLock. - // TODO: Expose mService.mLock or redesign instead. + // actually the same object as mLock. + // TODO: Expose mService.mLock or redesign instead. @GuardedBy("mLock") private Drawable getServiceIcon(FillResponse response) { Drawable serviceIcon = null; @@ -5130,14 +5664,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Get the custom label that was passed through FillResponse. If the custom label - * wasn't able to be fetched, use the default provider icon instead + * Get the custom label that was passed through FillResponse. If the custom label wasn't able to + * be fetched, use the default provider icon instead * * @return Drawable of the provider icon, if it was able to be fetched. Null otherwise */ @SuppressWarnings("GuardedBy") // ErrorProne says we need to use mService.mLock, but it's - // actually the same object as mLock. - // TODO: Expose mService.mLock or redesign instead. + // actually the same object as mLock. + // TODO: Expose mService.mLock or redesign instead. @GuardedBy("mLock") private CharSequence getServiceLabel(FillResponse response) { CharSequence serviceLabel = null; @@ -5167,11 +5701,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return serviceLabel; } - /** - * Returns whether we made a request to show inline suggestions. - */ - private boolean requestShowInlineSuggestionsLocked(@NonNull FillResponse response, - @Nullable String filterText) { + /** Returns whether we made a request to show inline suggestions. */ + private boolean requestShowInlineSuggestionsLocked( + @NonNull FillResponse response, @Nullable String filterText) { if (mCurrentViewId == null) { Log.w(TAG, "requestShowInlineSuggestionsLocked(): no view currently focused"); return false; @@ -5199,89 +5731,122 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } final InlineFillUi.InlineFillUiInfo inlineFillUiInfo = - new InlineFillUi.InlineFillUiInfo(inlineSuggestionsRequest.get(), focusedId, - filterText, remoteRenderService, userId, id); - InlineFillUi inlineFillUi = InlineFillUi.forAutofill(inlineFillUiInfo, response, - new InlineFillUi.InlineSuggestionUiCallback() { - @Override - public void autofill(@NonNull Dataset dataset, int datasetIndex) { - fill(response.getRequestId(), datasetIndex, dataset, UI_TYPE_INLINE); - } + new InlineFillUi.InlineFillUiInfo( + inlineSuggestionsRequest.get(), + focusedId, + filterText, + remoteRenderService, + userId, + id); + InlineFillUi inlineFillUi = + InlineFillUi.forAutofill( + inlineFillUiInfo, + response, + new InlineFillUi.InlineSuggestionUiCallback() { + @Override + public void autofill(@NonNull Dataset dataset, int datasetIndex) { + fill( + response.getRequestId(), + datasetIndex, + dataset, + UI_TYPE_INLINE); + } - @Override - public void authenticate(int requestId, int datasetIndex) { - Session.this.authenticate(response.getRequestId(), datasetIndex, - response.getAuthentication(), response.getClientState(), - UI_TYPE_INLINE); - } + @Override + public void authenticate(int requestId, int datasetIndex) { + Session.this.authenticate( + response.getRequestId(), + datasetIndex, + response.getAuthentication(), + response.getClientState(), + UI_TYPE_INLINE); + } - @Override - public void startIntentSender(@NonNull IntentSender intentSender) { - Session.this.startIntentSender(intentSender, new Intent()); - } + @Override + public void startIntentSender(@NonNull IntentSender intentSender) { + Session.this.startIntentSender(intentSender, new Intent()); + } - @Override - public void onError() { - synchronized (mLock) { - mInlineSessionController.setInlineFillUiLocked( - InlineFillUi.emptyUi(focusedId)); - } - } + @Override + public void onError() { + synchronized (mLock) { + mInlineSessionController.setInlineFillUiLocked( + InlineFillUi.emptyUi(focusedId)); + } + } - @Override - public void onInflate() { - Session.this.onShown(UI_TYPE_INLINE, 1); - } - }, mService.getMaster().getMaxInputLengthForAutofill()); + @Override + public void onInflate() { + Session.this.onShown(UI_TYPE_INLINE, 1); + } + }, + mService.getMaster().getMaxInputLengthForAutofill()); return mInlineSessionController.setInlineFillUiLocked(inlineFillUi); } private ResultReceiver constructCredentialManagerCallback(int requestId) { - final ResultReceiver resultReceiver = new ResultReceiver(mHandler) { - final AutofillId mAutofillId = mCurrentViewId; - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - if (resultCode == SUCCESS_CREDMAN_SELECTOR) { - Slog.d(TAG, "onReceiveResult from Credential Manager " - + "bottom sheet with mCurrentViewId: " + mAutofillId); - GetCredentialResponse getCredentialResponse = - resultData.getParcelable( - CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, - GetCredentialResponse.class); - - if (Flags.autofillCredmanDevIntegration()) { - sendCredentialManagerResponseToApp(getCredentialResponse, - /*exception=*/ null, mAutofillId); - } else { - Dataset datasetFromCredential = getDatasetFromCredentialResponse( - getCredentialResponse); - if (datasetFromCredential != null) { - autoFill(requestId, /*datasetIndex=*/-1, - datasetFromCredential, false, - UI_TYPE_CREDMAN_BOTTOM_SHEET); + final ResultReceiver resultReceiver = + new ResultReceiver(mHandler) { + final AutofillId mAutofillId = mCurrentViewId; + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + if (resultCode == SUCCESS_CREDMAN_SELECTOR) { + Slog.d( + TAG, + "onReceiveResult from Credential Manager " + + "bottom sheet with mCurrentViewId: " + + mAutofillId); + GetCredentialResponse getCredentialResponse = + resultData.getParcelable( + CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, + GetCredentialResponse.class); + + if (Flags.autofillCredmanDevIntegration()) { + sendCredentialManagerResponseToApp( + getCredentialResponse, /* exception= */ null, mAutofillId); + } else { + Dataset datasetFromCredential = + getDatasetFromCredentialResponse(getCredentialResponse); + if (datasetFromCredential != null) { + autoFill( + requestId, + /* datasetIndex= */ -1, + datasetFromCredential, + false, + UI_TYPE_CREDMAN_BOTTOM_SHEET); + } + } + } else if (resultCode == FAILURE_CREDMAN_SELECTOR) { + String[] exception = + resultData.getStringArray( + CredentialProviderService + .EXTRA_GET_CREDENTIAL_EXCEPTION); + if (exception != null && exception.length >= 2) { + String errType = exception[0]; + String errMsg = exception[1]; + Slog.w( + TAG, + "Credman bottom sheet from pinned " + + "entry failed with: + " + + errType + + " , " + + errMsg); + sendCredentialManagerResponseToApp( + /* response= */ null, + new GetCredentialException(errType, errMsg), + mAutofillId); + } + } else { + Slog.d( + TAG, + "Unknown resultCode from credential " + + "manager bottom sheet: " + + resultCode); } } - } else if (resultCode == FAILURE_CREDMAN_SELECTOR) { - String[] exception = resultData.getStringArray( - CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION); - if (exception != null && exception.length >= 2) { - String errType = exception[0]; - String errMsg = exception[1]; - Slog.w(TAG, "Credman bottom sheet from pinned " - + "entry failed with: + " + errType + " , " - + errMsg); - sendCredentialManagerResponseToApp(/*response=*/ null, - new GetCredentialException(errType, errMsg), - mAutofillId); - } - } else { - Slog.d(TAG, "Unknown resultCode from credential " - + "manager bottom sheet: " + resultCode); - } - } - }; - ResultReceiver ipcFriendlyResultReceiver = - toIpcFriendlyResultReceiver(resultReceiver); + }; + ResultReceiver ipcFriendlyResultReceiver = toIpcFriendlyResultReceiver(resultReceiver); return ipcFriendlyResultReceiver; } @@ -5309,8 +5874,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private void notifyUnavailableToClient(int sessionFinishedState, - @Nullable ArrayList<AutofillId> autofillableIds) { + private void notifyUnavailableToClient( + int sessionFinishedState, @Nullable ArrayList<AutofillId> autofillableIds) { synchronized (mLock) { if (mCurrentViewId == null) return; try { @@ -5374,27 +5939,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (saveInfo.getRequiredIds() != null) { Collections.addAll(trackedViews, saveInfo.getRequiredIds()); mSaveEventLogger.maybeSetSaveUiShownReason( - SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE); + SAVE_UI_SHOWN_REASON_REQUIRED_ID_CHANGE); } if (saveInfo.getOptionalIds() != null) { Collections.addAll(trackedViews, saveInfo.getOptionalIds()); mSaveEventLogger.maybeSetSaveUiShownReason( - SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE); + SAVE_UI_SHOWN_REASON_OPTIONAL_ID_CHANGE); } } if ((flags & SaveInfo.FLAG_DONT_SAVE_ON_FINISH) != 0) { - mSaveEventLogger.maybeSetSaveUiShownReason( - SAVE_UI_SHOWN_REASON_UNKNOWN); + mSaveEventLogger.maybeSetSaveUiShownReason(SAVE_UI_SHOWN_REASON_UNKNOWN); mSaveEventLogger.maybeSetSaveUiNotShownReason( - NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG); + NO_SAVE_REASON_WITH_DONT_SAVE_ON_FINISH_FLAG); saveOnFinish = false; } } else { flags = 0; - mSaveEventLogger.maybeSetSaveUiNotShownReason( - NO_SAVE_REASON_NO_SAVE_INFO); + mSaveEventLogger.maybeSetSaveUiNotShownReason(NO_SAVE_REASON_NO_SAVE_INFO); saveTriggerId = null; } @@ -5427,21 +5990,35 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState try { if (sVerbose) { - Slog.v(TAG, "updateTrackedIdsLocked(): trackedViews: " + trackedViews - + " fillableIds: " + fillableIds + " triggerId: " + saveTriggerId - + " saveOnFinish:" + saveOnFinish + " flags: " + flags - + " hasSaveInfo: " + (saveInfo != null)); - } - mClient.setTrackedViews(id, toArray(trackedViews), mSaveOnAllViewsInvisible, - saveOnFinish, toArray(fillableIds), saveTriggerId, hasAuthentication); + Slog.v( + TAG, + "updateTrackedIdsLocked(): trackedViews: " + + trackedViews + + " fillableIds: " + + fillableIds + + " triggerId: " + + saveTriggerId + + " saveOnFinish:" + + saveOnFinish + + " flags: " + + flags + + " hasSaveInfo: " + + (saveInfo != null)); + } + mClient.setTrackedViews( + id, + toArray(trackedViews), + mSaveOnAllViewsInvisible, + saveOnFinish, + toArray(fillableIds), + saveTriggerId, + hasAuthentication); } catch (RemoteException e) { Slog.w(TAG, "Cannot set tracked ids", e); } } - /** - * Sets the state of views that failed to autofill. - */ + /** Sets the state of views that failed to autofill. */ @GuardedBy("mLock") void setAutofillFailureLocked(@NonNull List<AutofillId> ids, boolean isRefill) { if (sVerbose && !ids.isEmpty()) { @@ -5464,9 +6041,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeSetViewFillFailureCounts(ids, isRefill); } - /** - * Sets the state of views that failed to autofill. - */ + /** Sets the state of views that failed to autofill. */ @GuardedBy("mLock") void setViewAutofilledLocked(@NonNull AutofillId id) { if (sVerbose) { @@ -5478,17 +6053,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeAddSuccessId(id); } - /** - * Sets the state of views that failed to autofill. - */ + /** Sets the state of views that failed to autofill. */ void setNotifyNotExpiringResponseDuringAuth() { synchronized (mLock) { mPresentationStatsEventLogger.maybeSetNotifyNotExpiringResponseDuringAuth(); } } - /** - * Sets the state of views that failed to autofill. - */ + + /** Sets the state of views that failed to autofill. */ void setLogViewEnteredIgnoredDuringAuth() { synchronized (mLock) { mPresentationStatsEventLogger.notifyViewEnteredIgnoredDuringAuthCount(); @@ -5496,10 +6068,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void replaceResponseLocked(@NonNull FillResponse oldResponse, - @NonNull FillResponse newResponse, @Nullable Bundle newClientState) { + private void replaceResponseLocked( + @NonNull FillResponse oldResponse, + @NonNull FillResponse newResponse, + @Nullable Bundle newClientState) { // Disassociate view states with the old response - setViewStatesLocked(oldResponse, ViewState.STATE_INITIAL, /* clearResponse= */ true, + setViewStatesLocked( + oldResponse, + ViewState.STATE_INITIAL, + /* clearResponse= */ true, /* isPrimary= */ true); // Move over the id newResponse.setRequestId(oldResponse.getRequestId()); @@ -5519,7 +6096,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final ArrayList<AutofillId> autofillableIds; if (context != null) { final AssistStructure structure = context.getStructure(); - autofillableIds = Helper.getAutofillIds(structure, /* autofillableOnly= */true); + autofillableIds = Helper.getAutofillIds(structure, /* autofillableOnly= */ true); } else { Slog.w(TAG, "processNullResponseLocked(): no context for req " + requestId); autofillableIds = null; @@ -5535,8 +6112,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mAugmentedAutofillDestroyer = triggerAugmentedAutofillLocked(flags); if (mAugmentedAutofillDestroyer == null && ((flags & FLAG_PASSWORD_INPUT_TYPE) == 0)) { if (sVerbose) { - Slog.v(TAG, "canceling session " + id + " when service returned null and it cannot " - + "be augmented. AutofillableIds: " + autofillableIds); + Slog.v( + TAG, + "canceling session " + + id + + " when service returned null and it cannot " + + "be augmented. AutofillableIds: " + + autofillableIds); } // Nothing to be done, but need to notify client. notifyUnavailableToClient(AutofillManager.STATE_FINISHED, autofillableIds); @@ -5544,15 +6126,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } else { if ((flags & FLAG_PASSWORD_INPUT_TYPE) != 0) { if (sVerbose) { - Slog.v(TAG, "keeping session " + id + " when service returned null and " - + "augmented service is disabled for password fields. " - + "AutofillableIds: " + autofillableIds); + Slog.v( + TAG, + "keeping session " + + id + + " when service returned null and " + + "augmented service is disabled for password fields. " + + "AutofillableIds: " + + autofillableIds); } mInlineSessionController.hideInlineSuggestionsUiLocked(mCurrentViewId); } else { if (sVerbose) { - Slog.v(TAG, "keeping session " + id + " when service returned null but " - + "it can be augmented. AutofillableIds: " + autofillableIds); + Slog.v( + TAG, + "keeping session " + + id + + " when service returned null but " + + "it can be augmented. AutofillableIds: " + + autofillableIds); } } mAugmentedAutofillableIds = autofillableIds; @@ -5567,8 +6159,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Tries to trigger Augmented Autofill when the standard service could not fulfill a request. * - * <p> The request may not have been sent when this method returns as it may be waiting for - * the inline suggestion request asynchronously. + * <p>The request may not have been sent when this method returns as it may be waiting for the + * inline suggestion request asynchronously. * * @return callback to destroy the autofill UI, or {@code null} if not supported. */ @@ -5582,8 +6174,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // Check if Smart Suggestions is supported... - final @SmartSuggestionMode int supportedModes = mService - .getSupportedSmartSuggestionModesLocked(); + final @SmartSuggestionMode int supportedModes = + mService.getSupportedSmartSuggestionModesLocked(); if (supportedModes == 0) { if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no supported modes"); return null; @@ -5591,8 +6183,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // ...then if the service is set for the user - final RemoteAugmentedAutofillService remoteService = mService - .getRemoteAugmentedAutofillServiceLocked(); + final RemoteAugmentedAutofillService remoteService = + mService.getRemoteAugmentedAutofillServiceLocked(); if (remoteService == null) { if (sVerbose) Slog.v(TAG, "triggerAugmentedAutofillLocked(): no service for user"); return null; @@ -5612,25 +6204,37 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return null; } - final boolean isAllowlisted = mService - .isWhitelistedForAugmentedAutofillLocked(mComponentName); + final boolean isAllowlisted = + mService.isWhitelistedForAugmentedAutofillLocked(mComponentName); if (!isAllowlisted) { if (sVerbose) { - Slog.v(TAG, "triggerAugmentedAutofillLocked(): " - + ComponentName.flattenToShortString(mComponentName) + " not whitelisted "); - } - logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), - mCurrentViewId, isAllowlisted, /* isInline= */ null); + Slog.v( + TAG, + "triggerAugmentedAutofillLocked(): " + + ComponentName.flattenToShortString(mComponentName) + + " not whitelisted "); + } + logAugmentedAutofillRequestLocked( + mode, + remoteService.getComponentName(), + mCurrentViewId, + isAllowlisted, + /* isInline= */ null); return null; } if (sVerbose) { - Slog.v(TAG, "calling Augmented Autofill Service (" - + ComponentName.flattenToShortString(remoteService.getComponentName()) - + ") on view " + mCurrentViewId + " using suggestion mode " - + getSmartSuggestionModeToString(mode) - + " when server returned null for session " + this.id); + Slog.v( + TAG, + "calling Augmented Autofill Service (" + + ComponentName.flattenToShortString(remoteService.getComponentName()) + + ") on view " + + mCurrentViewId + + " using suggestion mode " + + getSmartSuggestionModeToString(mode) + + " when server returned null for session " + + this.id); } // Log FillRequest for Augmented Autofill. mFillRequestEventLogger.startLogForNewRequest(); @@ -5648,8 +6252,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState if (mAugmentedRequestsLogs == null) { mAugmentedRequestsLogs = new ArrayList<>(); } - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_AUGMENTED_REQUEST, - remoteService.getComponentName().getPackageName()); + final LogMaker log = + newLogMaker( + MetricsEvent.AUTOFILL_AUGMENTED_REQUEST, + remoteService.getComponentName().getPackageName()); mAugmentedRequestsLogs.add(log); final AutofillId focusedId = mCurrentViewId; @@ -5715,8 +6321,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } synchronized (session.mLock) { session.mInlineSessionController.onCreateInlineSuggestionsRequestLocked( - mFocusedId, /*requestConsumer=*/ mRequestAugmentedAutofill, - result); + mFocusedId, /* requestConsumer= */ mRequestAugmentedAutofill, result); } } } @@ -5741,19 +6346,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mIsAllowlisted = isAllowlisted; mMode = mode; mCurrentValue = currentValue; - } + @Override public void accept(InlineSuggestionsRequest inlineSuggestionsRequest) { Session session = mSessionWeakRef.get(); - if (logIfSessionNull( - session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) { + if (logIfSessionNull(session, "AugmentedAutofillInlineSuggestionRequestConsumer:")) { return; } session.onAugmentedAutofillInlineSuggestionAccept( inlineSuggestionsRequest, mFocusedId, mIsAllowlisted, mMode, mCurrentValue); - } } @@ -5770,8 +6373,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState public Boolean apply(InlineFillUi inlineFillUi) { Session session = mSessionWeakRef.get(); - if (logIfSessionNull( - session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) { + if (logIfSessionNull(session, "AugmentedAutofillInlineSuggestionsResponseCallback:")) { return false; } @@ -5801,8 +6403,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * If the session is null or has been destroyed, log the error msg, and return true. - * This is a helper function intended to be called when de-referencing from a weak reference. + * If the session is null or has been destroyed, log the error msg, and return true. This is a + * helper function intended to be called when de-referencing from a weak reference. + * * @param session * @param logPrefix * @return true if the session is null, false otherwise. @@ -5830,15 +6433,25 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState synchronized (mLock) { final RemoteAugmentedAutofillService remoteService = mService.getRemoteAugmentedAutofillServiceLocked(); - logAugmentedAutofillRequestLocked(mode, remoteService.getComponentName(), - focussedId, isAllowlisted, inlineSuggestionsRequest != null); - remoteService.onRequestAutofillLocked(id, mClient, - taskId, mComponentName, mActivityToken, - AutofillId.withoutSession(focussedId), currentValue, + logAugmentedAutofillRequestLocked( + mode, + remoteService.getComponentName(), + focussedId, + isAllowlisted, + inlineSuggestionsRequest != null); + remoteService.onRequestAutofillLocked( + id, + mClient, + taskId, + mComponentName, + mActivityToken, + AutofillId.withoutSession(focussedId), + currentValue, inlineSuggestionsRequest, new AugmentedAutofillInlineSuggestionsResponseCallback(this), new AugmentedAutofillErrorCallback(this), - mService.getRemoteInlineSuggestionRenderServiceLocked(), userId); + mService.getRemoteInlineSuggestionRenderServiceLocked(), + userId); } } @@ -5847,15 +6460,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState cancelAugmentedAutofillLocked(); // Also cancel augmented in IME - mInlineSessionController.setInlineFillUiLocked( - InlineFillUi.emptyUi(mCurrentViewId)); + mInlineSessionController.setInlineFillUiLocked(InlineFillUi.emptyUi(mCurrentViewId)); } } @GuardedBy("mLock") private void cancelAugmentedAutofillLocked() { - final RemoteAugmentedAutofillService remoteService = mService - .getRemoteAugmentedAutofillServiceLocked(); + final RemoteAugmentedAutofillService remoteService = + mService.getRemoteAugmentedAutofillServiceLocked(); if (remoteService == null) { Slog.w(TAG, "cancelAugmentedAutofillLocked(): no service for user"); return; @@ -5865,8 +6477,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void processResponseLocked(@NonNull FillResponse newResponse, - @Nullable Bundle newClientState, int flags) { + private void processResponseLocked( + @NonNull FillResponse newResponse, @Nullable Bundle newClientState, int flags) { // Make sure we are hiding the UI which will be shown // only if handling the current response requires it. mUi.hideAll(this); @@ -5878,9 +6490,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final int requestId = newResponse.getRequestId(); if (sVerbose) { - Slog.v(TAG, "processResponseLocked(): mCurrentViewId=" + mCurrentViewId - + ",flags=" + flags + ", reqId=" + requestId + ", resp=" + newResponse - + ",newClientState=" + newClientState); + Slog.v( + TAG, + "processResponseLocked(): mCurrentViewId=" + + mCurrentViewId + + ",flags=" + + flags + + ", reqId=" + + requestId + + ", resp=" + + newResponse + + ",newClientState=" + + newClientState); } if (mResponses == null) { @@ -5892,8 +6513,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mResponses.put(requestId, newResponse); mClientState = newClientState != null ? newClientState : newResponse.getClientState(); - boolean webviewRequestedCredman = newClientState != null && newClientState.getBoolean( - WEBVIEW_REQUESTED_CREDENTIAL_KEY, false); + boolean webviewRequestedCredman = + newClientState != null + && newClientState.getBoolean(WEBVIEW_REQUESTED_CREDENTIAL_KEY, false); List<Dataset> datasetList = newResponse.getDatasets(); mPresentationStatsEventLogger.maybeSetWebviewRequestedCredential(webviewRequestedCredman); @@ -5901,7 +6523,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mPresentationStatsEventLogger.maybeSetAvailableCount(datasetList, mCurrentViewId); mFillResponseEventLogger.maybeSetDatasetsCountAfterPotentialPccFiltering(datasetList); - setViewStatesLocked(newResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false, + setViewStatesLocked( + newResponse, + ViewState.STATE_FILLABLE, + /* clearResponse= */ false, /* isPrimary= */ true); updateFillDialogTriggerIdsLocked(); updateTrackedIdsLocked(); @@ -5914,12 +6539,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState currentView.maybeCallOnFillReady(flags); } - /** - * Sets the state of all views in the given response. - */ + /** Sets the state of all views in the given response. */ @GuardedBy("mLock") - private void setViewStatesLocked(FillResponse response, int state, boolean clearResponse, - boolean isPrimary) { + private void setViewStatesLocked( + FillResponse response, int state, boolean clearResponse, boolean isPrimary) { final List<Dataset> datasets = response.getDatasets(); if (datasets != null && !datasets.isEmpty()) { for (int i = 0; i < datasets.size(); i++) { @@ -5964,12 +6587,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * Sets the state and response of all views in the given dataset. - */ + /** Sets the state and response of all views in the given dataset. */ @GuardedBy("mLock") - private void setViewStatesLocked(@Nullable FillResponse response, @NonNull Dataset dataset, - int state, boolean clearResponse, boolean isPrimary) { + private void setViewStatesLocked( + @Nullable FillResponse response, + @NonNull Dataset dataset, + int state, + boolean clearResponse, + boolean isPrimary) { final ArrayList<AutofillId> ids = dataset.getFieldIds(); final ArrayList<AutofillValue> values = dataset.getFieldValues(); for (int j = 0; j < ids.size(); j++) { @@ -5989,10 +6614,10 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private ViewState createOrUpdateViewStateLocked(@NonNull AutofillId id, int state, - @Nullable AutofillValue value) { + private ViewState createOrUpdateViewStateLocked( + @NonNull AutofillId id, int state, @Nullable AutofillValue value) { ViewState viewState = mViewStates.get(id); - if (viewState != null) { + if (viewState != null) { viewState.setState(state); } else { viewState = new ViewState(id, this, state, mIsPrimaryCredential); @@ -6008,22 +6633,27 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState return viewState; } - void autoFill(int requestId, int datasetIndex, Dataset dataset, boolean generateEvent, - int uiType) { + void autoFill( + int requestId, int datasetIndex, Dataset dataset, boolean generateEvent, int uiType) { if (sDebug) { - Slog.d(TAG, "autoFill(): requestId=" + requestId + "; datasetIdx=" + datasetIndex - + "; dataset=" + dataset); + Slog.d( + TAG, + "autoFill(): requestId=" + + requestId + + "; datasetIdx=" + + datasetIndex + + "; dataset=" + + dataset); } synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#autoFill() rejected - session: " - + id + " destroyed"); + Slog.w(TAG, "Call to Session#autoFill() rejected - session: " + id + " destroyed"); return; } // Selected dataset id is logged regardless of authentication result. mPresentationStatsEventLogger.maybeSetSelectedDatasetId(datasetIndex); mPresentationStatsEventLogger.maybeSetSelectedDatasetPickReason( - dataset.getEligibleReason()); + dataset.getEligibleReason()); // Autofill it directly... if (dataset.getAuthentication() == null) { if (generateEvent) { @@ -6039,10 +6669,14 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // ...or handle authentication. mService.logDatasetAuthenticationSelected(dataset.getId(), id, mClientState, uiType); mPresentationStatsEventLogger.maybeSetAuthenticationType( - AUTHENTICATION_TYPE_DATASET_AUTHENTICATION); + AUTHENTICATION_TYPE_DATASET_AUTHENTICATION); // does not matter the value of isPrimary because null response won't be overridden. - setViewStatesLocked(null, dataset, ViewState.STATE_WAITING_DATASET_AUTH, - /* clearResponse= */ false, /* isPrimary= */ true); + setViewStatesLocked( + null, + dataset, + ViewState.STATE_WAITING_DATASET_AUTH, + /* clearResponse= */ false, + /* isPrimary= */ true); final Intent fillInIntent; if (dataset.getCredentialFillInIntent() != null && Flags.autofillCredmanIntegration()) { Slog.d(TAG, "Setting credential fill intent"); @@ -6055,11 +6689,13 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState forceRemoveFromServiceLocked(); return; } - final int authenticationId = AutofillManager.makeAuthenticationId(requestId, - datasetIndex); - startAuthentication(authenticationId, dataset.getAuthentication(), fillInIntent, - /* authenticateInline= */false); - + final int authenticationId = + AutofillManager.makeAuthenticationId(requestId, datasetIndex); + startAuthentication( + authenticationId, + dataset.getAuthentication(), + fillInIntent, + /* authenticateInline= */ false); } } @@ -6072,13 +6708,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final FillContext context = getFillContextByRequestIdLocked(requestId); if (context == null) { - wtf(null, "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s", - requestId, mContexts); + wtf( + null, + "createAuthFillInIntentLocked(): no FillContext. requestId=%d; mContexts=%s", + requestId, + mContexts); return null; } if (mLastInlineSuggestionsRequest != null && mLastInlineSuggestionsRequest.first == requestId) { - fillInIntent.putExtra(AutofillManager.EXTRA_INLINE_SUGGESTIONS_REQUEST, + fillInIntent.putExtra( + AutofillManager.EXTRA_INLINE_SUGGESTIONS_REQUEST, mLastInlineSuggestionsRequest.second); } fillInIntent.putExtra(AutofillManager.EXTRA_ASSIST_STRUCTURE, context.getStructure()); @@ -6121,12 +6761,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - private void startAuthentication(int authenticationId, IntentSender intent, - Intent fillInIntent, boolean authenticateInline) { + private void startAuthentication( + int authenticationId, + IntentSender intent, + Intent fillInIntent, + boolean authenticateInline) { try { synchronized (mLock) { - mClient.authenticate(id, authenticationId, intent, fillInIntent, - authenticateInline); + mClient.authenticate( + id, authenticationId, intent, fillInIntent, authenticateInline); } } catch (RemoteException e) { Slog.e(TAG, "Error launching auth intent", e); @@ -6139,22 +6782,18 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * @hide */ static final class SaveResult { - /** - * Whether to record the save dialog has been shown. - */ + /** Whether to record the save dialog has been shown. */ private boolean mLogSaveShown; - /** - * Whether to remove the session. - */ + /** Whether to remove the session. */ private boolean mRemoveSession; - /** - * The reason why a save dialog was not shown. - */ + /** The reason why a save dialog was not shown. */ @NoSaveReason private int mSaveDialogNotShowReason; - SaveResult(boolean logSaveShown, boolean removeSession, + SaveResult( + boolean logSaveShown, + boolean removeSession, @NoSaveReason int saveDialogNotShowReason) { mLogSaveShown = logSaveShown; mRemoveSession = removeSession; @@ -6218,15 +6857,19 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public String toString() { - return "SaveResult: [logSaveShown=" + mLogSaveShown - + ", removeSession=" + mRemoveSession - + ", saveDialogNotShowReason=" + mSaveDialogNotShowReason + "]"; + return "SaveResult: [logSaveShown=" + + mLogSaveShown + + ", removeSession=" + + mRemoveSession + + ", saveDialogNotShowReason=" + + mSaveDialogNotShowReason + + "]"; } } /** - * Class maintaining the state of the requests to - * {@link android.service.assist.classification.FieldClassificationService}. + * Class maintaining the state of the requests to {@link + * android.service.assist.classification.FieldClassificationService}. */ private static final class ClassificationState { @@ -6234,18 +6877,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState * Initial state indicating that the request for classification hasn't been triggered yet. */ private static final int STATE_INITIAL = 1; - /** - * Assist request has been triggered, but awaiting response. - */ + + /** Assist request has been triggered, but awaiting response. */ private static final int STATE_PENDING_ASSIST_REQUEST = 2; - /** - * Classification request has been triggered, but awaiting response. - */ + + /** Classification request has been triggered, but awaiting response. */ private static final int STATE_PENDING_REQUEST = 3; - /** - * Classification response has been received. - */ + + /** Classification response has been received. */ private static final int STATE_RESPONSE = 4; + /** * Classification state has been invalidated, and the last response may no longer be valid. * This could occur due to various reasons like views changing their layouts, becoming @@ -6254,15 +6895,17 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState */ private static final int STATE_INVALIDATED = 5; - @IntDef(prefix = { "STATE_" }, value = { - STATE_INITIAL, - STATE_PENDING_ASSIST_REQUEST, - STATE_PENDING_REQUEST, - STATE_RESPONSE, - STATE_INVALIDATED - }) + @IntDef( + prefix = {"STATE_"}, + value = { + STATE_INITIAL, + STATE_PENDING_ASSIST_REQUEST, + STATE_PENDING_REQUEST, + STATE_RESPONSE, + STATE_INVALIDATED + }) @Retention(RetentionPolicy.SOURCE) - @interface ClassificationRequestState{} + @interface ClassificationRequestState {} @GuardedBy("mLock") private @ClassificationRequestState int mState = STATE_INITIAL; @@ -6284,8 +6927,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Typically, there would be a 1:1 mapping. However, in certain cases, we may have a hint - * being applicable to many types. An example of this being new/change password forms, - * where you need to confirm the passward twice. + * being applicable to many types. An example of this being new/change password forms, where + * you need to confirm the passward twice. */ @GuardedBy("mLock") private ArrayMap<String, Set<AutofillId>> mHintsToAutofillIdMap; @@ -6317,8 +6960,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState /** * Process the response received. + * * @return true if the response was processed, false otherwise. If there wasn't any - * response, yet this function was called, it would return false. + * response, yet this function was called, it would return false. */ @GuardedBy("mLock") private boolean processResponse() { @@ -6358,7 +7002,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private static void processDetections(Set<String> detections, AutofillId id, + private static void processDetections( + Set<String> detections, + AutofillId id, ArrayMap<String, Set<AutofillId>> currentMap) { for (String detection : detections) { Set<AutofillId> autofillIds; @@ -6416,37 +7062,67 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public String toString() { return "ClassificationState: [" - + "state=" + stateToString() - + ", mPendingFieldClassificationRequest=" + mPendingFieldClassificationRequest - + ", mLastFieldClassificationResponse=" + mLastFieldClassificationResponse - + ", mClassificationHintsMap=" + mClassificationHintsMap - + ", mClassificationGroupHintsMap=" + mClassificationGroupHintsMap - + ", mHintsToAutofillIdMap=" + mHintsToAutofillIdMap - + ", mGroupHintsToAutofillIdMap=" + mGroupHintsToAutofillIdMap + + "state=" + + stateToString() + + ", mPendingFieldClassificationRequest=" + + mPendingFieldClassificationRequest + + ", mLastFieldClassificationResponse=" + + mLastFieldClassificationResponse + + ", mClassificationHintsMap=" + + mClassificationHintsMap + + ", mClassificationGroupHintsMap=" + + mClassificationGroupHintsMap + + ", mHintsToAutofillIdMap=" + + mHintsToAutofillIdMap + + ", mGroupHintsToAutofillIdMap=" + + mGroupHintsToAutofillIdMap + "]"; } - } @Override public String toString() { - return "Session: [id=" + id + ", component=" + mComponentName - + ", state=" + sessionStateAsString(mSessionState) + "]"; + return "Session: [id=" + + id + + ", component=" + + mComponentName + + ", state=" + + sessionStateAsString(mSessionState) + + "]"; } @GuardedBy("mLock") void dumpLocked(String prefix, PrintWriter pw) { final String prefix2 = prefix + " "; - pw.print(prefix); pw.print("id: "); pw.println(id); - pw.print(prefix); pw.print("uid: "); pw.println(uid); - pw.print(prefix); pw.print("taskId: "); pw.println(taskId); - pw.print(prefix); pw.print("flags: "); pw.println(mFlags); - pw.print(prefix); pw.print("displayId: "); pw.println(mContext.getDisplayId()); - pw.print(prefix); pw.print("state: "); pw.println(sessionStateAsString(mSessionState)); - pw.print(prefix); pw.print("mComponentName: "); pw.println(mComponentName); - pw.print(prefix); pw.print("mActivityToken: "); pw.println(mActivityToken); - pw.print(prefix); pw.print("mStartTime: "); pw.println(mStartTime); - pw.print(prefix); pw.print("Time to show UI: "); + pw.print(prefix); + pw.print("id: "); + pw.println(id); + pw.print(prefix); + pw.print("uid: "); + pw.println(uid); + pw.print(prefix); + pw.print("taskId: "); + pw.println(taskId); + pw.print(prefix); + pw.print("flags: "); + pw.println(mFlags); + pw.print(prefix); + pw.print("displayId: "); + pw.println(mContext.getDisplayId()); + pw.print(prefix); + pw.print("state: "); + pw.println(sessionStateAsString(mSessionState)); + pw.print(prefix); + pw.print("mComponentName: "); + pw.println(mComponentName); + pw.print(prefix); + pw.print("mActivityToken: "); + pw.println(mActivityToken); + pw.print(prefix); + pw.print("mStartTime: "); + pw.println(mStartTime); + pw.print(prefix); + pw.print("Time to show UI: "); if (mUiShownTime == 0) { pw.println("N/A"); } else { @@ -6454,41 +7130,67 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.println(); } final int requestLogsSizes = mRequestLogs.size(); - pw.print(prefix); pw.print("mSessionLogs: "); pw.println(requestLogsSizes); + pw.print(prefix); + pw.print("mSessionLogs: "); + pw.println(requestLogsSizes); for (int i = 0; i < requestLogsSizes; i++) { final int requestId = mRequestLogs.keyAt(i); final LogMaker log = mRequestLogs.valueAt(i); - pw.print(prefix2); pw.print('#'); pw.print(i); pw.print(": req="); - pw.print(requestId); pw.print(", log=" ); dumpRequestLog(pw, log); pw.println(); + pw.print(prefix2); + pw.print('#'); + pw.print(i); + pw.print(": req="); + pw.print(requestId); + pw.print(", log="); + dumpRequestLog(pw, log); + pw.println(); } - pw.print(prefix); pw.print("mResponses: "); + pw.print(prefix); + pw.print("mResponses: "); if (mResponses == null) { pw.println("null"); } else { pw.println(mResponses.size()); for (int i = 0; i < mResponses.size(); i++) { - pw.print(prefix2); pw.print('#'); pw.print(i); - pw.print(' '); pw.println(mResponses.valueAt(i)); - } - } - pw.print(prefix); pw.print("mCurrentViewId: "); pw.println(mCurrentViewId); - pw.print(prefix); pw.print("mDestroyed: "); pw.println(mDestroyed); - pw.print(prefix); pw.print("mShowingSaveUi: "); pw.println(mSessionFlags.mShowingSaveUi); - pw.print(prefix); pw.print("mPendingSaveUi: "); pw.println(mPendingSaveUi); + pw.print(prefix2); + pw.print('#'); + pw.print(i); + pw.print(' '); + pw.println(mResponses.valueAt(i)); + } + } + pw.print(prefix); + pw.print("mCurrentViewId: "); + pw.println(mCurrentViewId); + pw.print(prefix); + pw.print("mDestroyed: "); + pw.println(mDestroyed); + pw.print(prefix); + pw.print("mShowingSaveUi: "); + pw.println(mSessionFlags.mShowingSaveUi); + pw.print(prefix); + pw.print("mPendingSaveUi: "); + pw.println(mPendingSaveUi); final int numberViews = mViewStates.size(); - pw.print(prefix); pw.print("mViewStates size: "); pw.println(mViewStates.size()); + pw.print(prefix); + pw.print("mViewStates size: "); + pw.println(mViewStates.size()); for (int i = 0; i < numberViews; i++) { - pw.print(prefix); pw.print("ViewState at #"); pw.println(i); + pw.print(prefix); + pw.print("ViewState at #"); + pw.println(i); mViewStates.valueAt(i).dump(prefix2, pw); } - pw.print(prefix); pw.print("mContexts: " ); + pw.print(prefix); + pw.print("mContexts: "); if (mContexts != null) { int numContexts = mContexts.size(); for (int i = 0; i < numContexts; i++) { FillContext context = mContexts.get(i); - pw.print(prefix2); pw.print(context); + pw.print(prefix2); + pw.print(context); if (sVerbose) { pw.println("AssistStructure dumped at logcat)"); @@ -6500,43 +7202,62 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.println("null"); } - pw.print(prefix); pw.print("mHasCallback: "); pw.println(mHasCallback); + pw.print(prefix); + pw.print("mHasCallback: "); + pw.println(mHasCallback); if (mClientState != null) { - pw.print(prefix); pw.print("mClientState: "); pw.print(mClientState.getSize()); pw - .println(" bytes"); - } - pw.print(prefix); pw.print("mCompatMode: "); pw.println(mCompatMode); - pw.print(prefix); pw.print("mUrlBar: "); + pw.print(prefix); + pw.print("mClientState: "); + pw.print(mClientState.getSize()); + pw.println(" bytes"); + } + pw.print(prefix); + pw.print("mCompatMode: "); + pw.println(mCompatMode); + pw.print(prefix); + pw.print("mUrlBar: "); if (mUrlBar == null) { pw.println("N/A"); } else { - pw.print("id="); pw.print(mUrlBar.getAutofillId()); - pw.print(" domain="); pw.print(mUrlBar.getWebDomain()); - pw.print(" text="); Helper.printlnRedactedText(pw, mUrlBar.getText()); - } - pw.print(prefix); pw.print("mSaveOnAllViewsInvisible: "); pw.println( - mSaveOnAllViewsInvisible); - pw.print(prefix); pw.print("mSelectedDatasetIds: "); pw.println(mSelectedDatasetIds); + pw.print("id="); + pw.print(mUrlBar.getAutofillId()); + pw.print(" domain="); + pw.print(mUrlBar.getWebDomain()); + pw.print(" text="); + Helper.printlnRedactedText(pw, mUrlBar.getText()); + } + pw.print(prefix); + pw.print("mSaveOnAllViewsInvisible: "); + pw.println(mSaveOnAllViewsInvisible); + pw.print(prefix); + pw.print("mSelectedDatasetIds: "); + pw.println(mSelectedDatasetIds); if (mSessionFlags.mAugmentedAutofillOnly) { - pw.print(prefix); pw.println("For Augmented Autofill Only"); + pw.print(prefix); + pw.println("For Augmented Autofill Only"); } if (mSessionFlags.mFillDialogDisabled) { - pw.print(prefix); pw.println("Fill Dialog disabled"); + pw.print(prefix); + pw.println("Fill Dialog disabled"); } if (mLastFillDialogTriggerIds != null) { - pw.print(prefix); pw.println("Last Fill Dialog trigger ids: "); + pw.print(prefix); + pw.println("Last Fill Dialog trigger ids: "); pw.println(mSelectedDatasetIds); } if (mAugmentedAutofillDestroyer != null) { - pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer"); + pw.print(prefix); + pw.println("has mAugmentedAutofillDestroyer"); } if (mAugmentedRequestsLogs != null) { - pw.print(prefix); pw.print("number augmented requests: "); + pw.print(prefix); + pw.print("number augmented requests: "); pw.println(mAugmentedRequestsLogs.size()); } if (mAugmentedAutofillableIds != null) { - pw.print(prefix); pw.print("mAugmentedAutofillableIds: "); + pw.print(prefix); + pw.print("mAugmentedAutofillableIds: "); pw.println(mAugmentedAutofillableIds); } if (mRemoteFillService != null) { @@ -6545,21 +7266,32 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } private static void dumpRequestLog(@NonNull PrintWriter pw, @NonNull LogMaker log) { - pw.print("CAT="); pw.print(log.getCategory()); + pw.print("CAT="); + pw.print(log.getCategory()); pw.print(", TYPE="); final int type = log.getType(); switch (type) { - case MetricsEvent.TYPE_SUCCESS: pw.print("SUCCESS"); break; - case MetricsEvent.TYPE_FAILURE: pw.print("FAILURE"); break; - case MetricsEvent.TYPE_CLOSE: pw.print("CLOSE"); break; - default: pw.print("UNSUPPORTED"); - } - pw.print('('); pw.print(type); pw.print(')'); - pw.print(", PKG="); pw.print(log.getPackageName()); - pw.print(", SERVICE="); pw.print(log - .getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE)); - pw.print(", ORDINAL="); pw.print(log - .getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL)); + case MetricsEvent.TYPE_SUCCESS: + pw.print("SUCCESS"); + break; + case MetricsEvent.TYPE_FAILURE: + pw.print("FAILURE"); + break; + case MetricsEvent.TYPE_CLOSE: + pw.print("CLOSE"); + break; + default: + pw.print("UNSUPPORTED"); + } + pw.print('('); + pw.print(type); + pw.print(')'); + pw.print(", PKG="); + pw.print(log.getPackageName()); + pw.print(", SERVICE="); + pw.print(log.getTaggedData(MetricsEvent.FIELD_AUTOFILL_SERVICE)); + pw.print(", ORDINAL="); + pw.print(log.getTaggedData(MetricsEvent.FIELD_AUTOFILL_REQUEST_ORDINAL)); dumpNumericValue(pw, log, "FLAGS", MetricsEvent.FIELD_AUTOFILL_FLAGS); dumpNumericValue(pw, log, "NUM_DATASETS", MetricsEvent.FIELD_AUTOFILL_NUM_DATASETS); dumpNumericValue(pw, log, "UI_LATENCY", MetricsEvent.FIELD_AUTOFILL_DURATION); @@ -6569,64 +7301,86 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState pw.print(", AUTH_STATUS="); switch (authStatus) { case MetricsEvent.AUTOFILL_AUTHENTICATED: - pw.print("AUTHENTICATED"); break; + pw.print("AUTHENTICATED"); + break; case MetricsEvent.AUTOFILL_DATASET_AUTHENTICATED: - pw.print("DATASET_AUTHENTICATED"); break; + pw.print("DATASET_AUTHENTICATED"); + break; case MetricsEvent.AUTOFILL_INVALID_AUTHENTICATION: - pw.print("INVALID_AUTHENTICATION"); break; + pw.print("INVALID_AUTHENTICATION"); + break; case MetricsEvent.AUTOFILL_INVALID_DATASET_AUTHENTICATION: - pw.print("INVALID_DATASET_AUTHENTICATION"); break; - default: pw.print("UNSUPPORTED"); + pw.print("INVALID_DATASET_AUTHENTICATION"); + break; + default: + pw.print("UNSUPPORTED"); } - pw.print('('); pw.print(authStatus); pw.print(')'); + pw.print('('); + pw.print(authStatus); + pw.print(')'); } - dumpNumericValue(pw, log, "FC_IDS", - MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS); - dumpNumericValue(pw, log, "COMPAT_MODE", - MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE); + dumpNumericValue( + pw, log, "FC_IDS", MetricsEvent.FIELD_AUTOFILL_NUM_FIELD_CLASSIFICATION_IDS); + dumpNumericValue(pw, log, "COMPAT_MODE", MetricsEvent.FIELD_AUTOFILL_COMPAT_MODE); } - private static void dumpNumericValue(@NonNull PrintWriter pw, @NonNull LogMaker log, - @NonNull String field, int tag) { + private static void dumpNumericValue( + @NonNull PrintWriter pw, @NonNull LogMaker log, @NonNull String field, int tag) { final int value = getNumericValue(log, tag); if (value != 0) { - pw.print(", "); pw.print(field); pw.print('='); pw.print(value); + pw.print(", "); + pw.print(field); + pw.print('='); + pw.print(value); } } - void sendCredentialManagerResponseToApp(@Nullable GetCredentialResponse response, - @Nullable GetCredentialException exception, @NonNull AutofillId viewId) { + void sendCredentialManagerResponseToApp( + @Nullable GetCredentialResponse response, + @Nullable GetCredentialException exception, + @NonNull AutofillId viewId) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#sendCredentialManagerResponseToApp() rejected " - + "- session: " + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#sendCredentialManagerResponseToApp() rejected " + + "- session: " + + id + + " destroyed"); return; } try { final ViewState viewState = mViewStates.get(viewId); if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly() - && viewState != null && viewState.id.getSessionId() != id) { + && viewState != null + && viewState.id.getSessionId() != id) { if (sVerbose) { - Slog.v(TAG, "Skipping sending credential response to view: " - + viewId + " as it isn't part of the current session: " + id); + Slog.v( + TAG, + "Skipping sending credential response to view: " + + viewId + + " as it isn't part of the current session: " + + id); } } if (exception != null) { if (viewId.isVirtualInt()) { - sendResponseToViewNode(viewId, /*response=*/ null, exception); + sendResponseToViewNode(viewId, /* response= */ null, exception); } else { - mClient.onGetCredentialException(id, viewId, exception.getType(), - exception.getMessage()); + mClient.onGetCredentialException( + id, viewId, exception.getType(), exception.getMessage()); } } else if (response != null) { if (viewId.isVirtualInt()) { - sendResponseToViewNode(viewId, response, /*exception=*/ null); + sendResponseToViewNode(viewId, response, /* exception= */ null); } else { mClient.onGetCredentialResponse(id, viewId, response); } } else { - Slog.w(TAG, "sendCredentialManagerResponseToApp called with null response" - + "and exception"); + Slog.w( + TAG, + "sendCredentialManagerResponseToApp called with null response" + + "and exception"); } } catch (RemoteException e) { Slog.w(TAG, "Error sending credential response to activity: " + e); @@ -6635,23 +7389,20 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void sendResponseToViewNode(AutofillId viewId, GetCredentialResponse response, - GetCredentialException exception) { + private void sendResponseToViewNode( + AutofillId viewId, GetCredentialResponse response, GetCredentialException exception) { ViewNode viewNode = getViewNodeFromContextsLocked(viewId); if (viewNode != null && viewNode.getPendingCredentialCallback() != null) { Bundle resultData = new Bundle(); if (response != null) { resultData.putParcelable( - CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, - response); - viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, - resultData); + CredentialProviderService.EXTRA_GET_CREDENTIAL_RESPONSE, response); + viewNode.getPendingCredentialCallback().send(SUCCESS_CREDMAN_SELECTOR, resultData); } else if (exception != null) { resultData.putStringArray( CredentialProviderService.EXTRA_GET_CREDENTIAL_EXCEPTION, new String[] {exception.getType(), exception.getMessage()}); - viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR, - resultData); + viewNode.getPendingCredentialCallback().send(FAILURE_CREDMAN_SELECTOR, resultData); } } else { Slog.w(TAG, "View node not found after GetCredentialResponse"); @@ -6661,8 +7412,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void autoFillApp(Dataset dataset) { synchronized (mLock) { if (mDestroyed) { - Slog.w(TAG, "Call to Session#autoFillApp() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#autoFillApp() rejected - session: " + id + " destroyed"); return; } try { @@ -6671,8 +7423,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final List<AutofillId> ids = new ArrayList<>(entryCount); final List<AutofillValue> values = new ArrayList<>(entryCount); boolean waitingDatasetAuth = false; - boolean hideHighlight = (entryCount == 1 - && dataset.getFieldIds().get(0).equals(mCurrentViewId)); + boolean hideHighlight = + highlightAutofillSingleField() + ? false + : (entryCount == 1 + && dataset.getFieldIds().get(0).equals(mCurrentViewId)); // Count how many views are filtered because they are not in current session int numOfViewsFiltered = 0; for (int i = 0; i < entryCount; i++) { @@ -6682,10 +7437,15 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState final AutofillId viewId = dataset.getFieldIds().get(i); final ViewState viewState = mViewStates.get(viewId); if (mService.getMaster().getIsFillFieldsFromCurrentSessionOnly() - && viewState != null && viewState.id.getSessionId() != id) { + && viewState != null + && viewState.id.getSessionId() != id) { if (sVerbose) { - Slog.v(TAG, "Skipping filling view: " + - viewId + " as it isn't part of the current session: " + id); + Slog.v( + TAG, + "Skipping filling view: " + + viewId + + " as it isn't part of the current session: " + + id); } numOfViewsFiltered += 1; continue; @@ -6721,8 +7481,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } // does not matter the value of isPrimary because null response won't be // overridden. - setViewStatesLocked(null, dataset, ViewState.STATE_AUTOFILLED, - /* clearResponse= */ false, /* isPrimary= */ true); + setViewStatesLocked( + null, + dataset, + ViewState.STATE_AUTOFILLED, + /* clearResponse= */ false, + /* isPrimary= */ true); } } catch (RemoteException e) { Slog.w(TAG, "Error autofilling activity: " + e); @@ -6750,7 +7514,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mSessionCommittedEventLogger.maybeSetCommitReason(val); mSessionCommittedEventLogger.maybeSetRequestCount(mRequestCount); mSessionCommittedEventLogger.maybeSetSessionDurationMillis( - SystemClock.elapsedRealtime() - mStartTime); + SystemClock.elapsedRealtime() - mStartTime); mFillRequestEventLogger.logAndEndEvent(); mFillResponseEventLogger.logAndEndEvent(); mPresentationStatsEventLogger.logAndEndEvent("log all events"); @@ -6808,8 +7572,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - final int totalAugmentedRequests = mAugmentedRequestsLogs == null ? 0 - : mAugmentedRequestsLogs.size(); + final int totalAugmentedRequests = + mAugmentedRequestsLogs == null ? 0 : mAugmentedRequestsLogs.size(); if (totalAugmentedRequests > 0) { if (sVerbose) { Slog.v(TAG, "destroyLocked(): logging " + totalRequests + " augmented requests"); @@ -6820,11 +7584,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - final LogMaker log = newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED) - .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests); + final LogMaker log = + newLogMaker(MetricsEvent.AUTOFILL_SESSION_FINISHED) + .addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_REQUESTS, totalRequests); if (totalAugmentedRequests > 0) { - log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS, - totalAugmentedRequests); + log.addTaggedData( + MetricsEvent.FIELD_AUTOFILL_NUMBER_AUGMENTED_REQUESTS, totalAugmentedRequests); } if (mSessionFlags.mAugmentedAutofillOnly) { log.addTaggedData(MetricsEvent.FIELD_AUTOFILL_AUGMENTED_ONLY, 1); @@ -6846,8 +7611,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") void forceRemoveFromServiceIfForAugmentedOnlyLocked() { if (sVerbose) { - Slog.v(TAG, "forceRemoveFromServiceIfForAugmentedOnlyLocked(" + this.id + "): " - + mSessionFlags.mAugmentedAutofillOnly); + Slog.v( + TAG, + "forceRemoveFromServiceIfForAugmentedOnlyLocked(" + + this.id + + "): " + + mSessionFlags.mAugmentedAutofillOnly); } if (!mSessionFlags.mAugmentedAutofillOnly) return; @@ -6880,9 +7649,7 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } } - /** - * Thread-safe version of {@link #removeFromServiceLocked()}. - */ + /** Thread-safe version of {@link #removeFromServiceLocked()}. */ private void removeFromService() { synchronized (mLock) { removeFromServiceLocked(); @@ -6897,8 +7664,11 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState void removeFromServiceLocked() { if (sVerbose) Slog.v(TAG, "removeFromServiceLocked(" + this.id + "): " + mPendingSaveUi); if (mDestroyed) { - Slog.w(TAG, "Call to Session#removeFromServiceLocked() rejected - session: " - + id + " destroyed"); + Slog.w( + TAG, + "Call to Session#removeFromServiceLocked() rejected - session: " + + id + + " destroyed"); return; } if (isSaveUiPendingLocked()) { @@ -6922,18 +7692,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } /** - * Checks whether this session is hiding the Save UI to handle a custom description link for - * a specific {@code token} created by - * {@link PendingUi#PendingUi(IBinder, int, IAutoFillManagerClient)}. + * Checks whether this session is hiding the Save UI to handle a custom description link for a + * specific {@code token} created by {@link PendingUi#PendingUi(IBinder, int, + * IAutoFillManagerClient)}. */ @GuardedBy("mLock") boolean isSaveUiPendingForTokenLocked(@NonNull IBinder token) { return isSaveUiPendingLocked() && token.equals(mPendingSaveUi.getToken()); } - /** - * Checks whether this session is hiding the Save UI to handle a custom description link. - */ + /** Checks whether this session is hiding the Save UI to handle a custom description link. */ @GuardedBy("mLock") private boolean isSaveUiPendingLocked() { return mPendingSaveUi != null && mPendingSaveUi.getState() == PendingUi.STATE_PENDING; @@ -6942,8 +7710,8 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState // Return latest response index in mResponses SparseArray. @GuardedBy("mLock") private int getLastResponseIndexLocked() { - if (mResponses == null || mResponses.size() == 0) { - return -1; + if (mResponses == null || mResponses.size() == 0) { + return -1; } List<Integer> requestIdList = new ArrayList<>(); final int responseCount = mResponses.size(); @@ -6967,15 +7735,16 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @GuardedBy("mLock") private void logAuthenticationStatusLocked(int requestId, int status) { - addTaggedDataToRequestLogLocked(requestId, - MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status); + addTaggedDataToRequestLogLocked( + requestId, MetricsEvent.FIELD_AUTOFILL_AUTHENTICATION_STATUS, status); } @GuardedBy("mLock") private void addTaggedDataToRequestLogLocked(int requestId, int tag, @Nullable Object value) { final LogMaker requestLog = mRequestLogs.get(requestId); if (requestLog == null) { - Slog.w(TAG, + Slog.w( + TAG, "addTaggedDataToRequestLogLocked(tag=" + tag + "): no log for id " + requestId); return; } @@ -6983,20 +7752,33 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState } @GuardedBy("mLock") - private void logAugmentedAutofillRequestLocked(int mode, - ComponentName augmentedRemoteServiceName, AutofillId focusedId, boolean isWhitelisted, + private void logAugmentedAutofillRequestLocked( + int mode, + ComponentName augmentedRemoteServiceName, + AutofillId focusedId, + boolean isWhitelisted, Boolean isInline) { final String historyItem = - "aug:id=" + id + " u=" + uid + " m=" + mode - + " a=" + ComponentName.flattenToShortString(mComponentName) - + " f=" + focusedId - + " s=" + augmentedRemoteServiceName - + " w=" + isWhitelisted - + " i=" + isInline; + "aug:id=" + + id + + " u=" + + uid + + " m=" + + mode + + " a=" + + ComponentName.flattenToShortString(mComponentName) + + " f=" + + focusedId + + " s=" + + augmentedRemoteServiceName + + " w=" + + isWhitelisted + + " i=" + + isInline; mService.getMaster().logRequestLocked(historyItem); } - private void wtf(@Nullable Exception e, String fmt, Object...args) { + private void wtf(@Nullable Exception e, String fmt, Object... args) { final String message = String.format(fmt, args); synchronized (mLock) { mWtfHistory.log(message); @@ -7051,13 +7833,9 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState mClassificationState.updateResponseReceived(response); } - public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) { + public void onClassificationRequestFailure(int requestId, @Nullable CharSequence message) {} - } - - public void onClassificationRequestTimeout(int requestId) { - - } + public void onClassificationRequestTimeout(int requestId) {} @Override public void onServiceDied(@NonNull RemoteFieldClassificationService service) { @@ -7070,12 +7848,12 @@ final class Session implements RemoteFillService.FillServiceCallbacks, ViewState @Override public void logFieldClassificationEvent( - long startTime, FieldClassificationResponse response, + long startTime, + FieldClassificationResponse response, @FieldClassificationEventLogger.FieldClassificationStatus int status) { final FieldClassificationEventLogger logger = FieldClassificationEventLogger.createLogger(); logger.startNewLogForRequest(); - logger.maybeSetLatencyMillis( - SystemClock.elapsedRealtime() - startTime); + logger.maybeSetLatencyMillis(SystemClock.elapsedRealtime() - startTime); logger.maybeSetAppPackageUid(uid); logger.maybeSetNextFillRequestId(mFillRequestIdSnapshot + 1); logger.maybeSetRequestId(sIdCounterForPcc.get()); diff --git a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java index 57c643bb08a1..a7aab49cde56 100644 --- a/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java +++ b/services/contentsuggestions/java/com/android/server/contentsuggestions/ContentSuggestionsManagerService.java @@ -160,11 +160,13 @@ public class ContentSuggestionsManagerService extends HardwareBuffer snapshotBuffer = null; int colorSpaceId = 0; + TaskSnapshot snapshot = null; // Skip taking TaskSnapshot when bitmap is provided. if (!imageContextRequestExtras.containsKey(ContentSuggestionsManager.EXTRA_BITMAP)) { // Can block, so call before acquiring the lock. - TaskSnapshot snapshot = - mActivityTaskManagerInternal.getTaskSnapshotBlocking(taskId, false); + snapshot = mActivityTaskManagerInternal.getTaskSnapshotBlocking( + taskId, false /* isLowResolution */, + TaskSnapshot.REFERENCE_CONTENT_SUGGESTION); if (snapshot != null) { snapshotBuffer = snapshot.getHardwareBuffer(); ColorSpace colorSpace = snapshot.getColorSpace(); @@ -185,6 +187,9 @@ public class ContentSuggestionsManagerService extends } } } + if (snapshot != null) { + snapshot.removeReference(TaskSnapshot.REFERENCE_CONTENT_SUGGESTION); + } } @Override diff --git a/services/core/java/com/android/server/SystemConfig.java b/services/core/java/com/android/server/SystemConfig.java index 504137a29977..6a1e319b4039 100644 --- a/services/core/java/com/android/server/SystemConfig.java +++ b/services/core/java/com/android/server/SystemConfig.java @@ -46,6 +46,7 @@ import android.util.TimingsTraceLog; import android.util.Xml; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.pm.RoSystemFeatures; import com.android.internal.util.XmlUtils; import com.android.modules.utils.build.UnboundedSdkLevel; import com.android.server.pm.permission.PermissionAllowlist; @@ -212,6 +213,30 @@ public class SystemConfig { } } + /** + * Utility class for testing interaction with compile-time defined system features. + * @hide + */ + @VisibleForTesting + public static class Injector { + /** Whether a system feature is defined as enabled and available at compile-time. */ + public boolean isReadOnlySystemEnabledFeature(String featureName, int version) { + return Boolean.TRUE.equals(RoSystemFeatures.maybeHasFeature(featureName, version)); + } + + /** Whether a system feature is defined as disabled and unavailable at compile-time. */ + public boolean isReadOnlySystemDisabledFeature(String featureName, int version) { + return Boolean.FALSE.equals(RoSystemFeatures.maybeHasFeature(featureName, version)); + } + + /** The full set of system features defined as compile-time enabled and available. */ + public ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() { + return RoSystemFeatures.getReadOnlySystemEnabledFeatures(); + } + } + + private final Injector mInjector; + // These are the built-in shared libraries that were read from the // system configuration files. Keys are the library names; values are // the individual entries that contain information such as filename @@ -220,7 +245,7 @@ public class SystemConfig { // These are the features this devices supports that were read from the // system configuration files. - final ArrayMap<String, FeatureInfo> mAvailableFeatures = new ArrayMap<>(); + final ArrayMap<String, FeatureInfo> mAvailableFeatures; // These are the features which this device doesn't support; the OEM // partition uses these to opt-out of features from the system image. @@ -602,12 +627,26 @@ public class SystemConfig { public ArrayMap<String, Integer> getOemDefinedUids() { return mOemDefinedUids; } + /** * Only use for testing. Do NOT use in production code. * @param readPermissions false to create an empty SystemConfig; true to read the permissions. */ @VisibleForTesting public SystemConfig(boolean readPermissions) { + this(readPermissions, new Injector()); + } + + /** + * Only use for testing. Do NOT use in production code. + * @param readPermissions false to create an empty SystemConfig; true to read the permissions. + * @param injector Additional dependency injection for testing. + */ + @VisibleForTesting + public SystemConfig(boolean readPermissions, Injector injector) { + mInjector = injector; + mAvailableFeatures = mInjector.getReadOnlySystemEnabledFeatures(); + if (readPermissions) { Slog.w(TAG, "Constructing a test SystemConfig"); readAllPermissions(); @@ -617,6 +656,9 @@ public class SystemConfig { } SystemConfig() { + mInjector = new Injector(); + mAvailableFeatures = mInjector.getReadOnlySystemEnabledFeatures(); + TimingsTraceLog log = new TimingsTraceLog(TAG, Trace.TRACE_TAG_SYSTEM_SERVER); log.traceBegin("readAllPermissions"); try { @@ -1775,6 +1817,10 @@ public class SystemConfig { } private void addFeature(String name, int version) { + if (mInjector.isReadOnlySystemDisabledFeature(name, version)) { + Slog.w(TAG, "Skipping feature addition for compile-time disabled feature: " + name); + return; + } FeatureInfo fi = mAvailableFeatures.get(name); if (fi == null) { fi = new FeatureInfo(); @@ -1787,6 +1833,10 @@ public class SystemConfig { } private void removeFeature(String name) { + if (mInjector.isReadOnlySystemEnabledFeature(name, /*version=*/0)) { + Slog.w(TAG, "Skipping feature removal for compile-time enabled feature: " + name); + return; + } if (mAvailableFeatures.remove(name) != null) { Slog.d(TAG, "Removed unavailable feature " + name); } diff --git a/services/core/java/com/android/server/UiModeManagerService.java b/services/core/java/com/android/server/UiModeManagerService.java index f8857d3e152a..d13dd2f2e1fc 100644 --- a/services/core/java/com/android/server/UiModeManagerService.java +++ b/services/core/java/com/android/server/UiModeManagerService.java @@ -443,6 +443,11 @@ final class UiModeManagerService extends SystemService { mDreamsDisabledByAmbientModeSuppression = disabledByAmbientModeSuppression; } + @VisibleForTesting + void setCurrentUser(int currentUserId) { + mCurrentUser = currentUserId; + } + @Override public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { mCurrentUser = to.getUserIdentifier(); @@ -864,9 +869,9 @@ final class UiModeManagerService extends SystemService { throw new IllegalArgumentException("Unknown mode: " + mode); } - final int user = UserHandle.getCallingUserId(); - enforceCurrentUserIfVisibleBackgroundEnabled(user); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); + final int user = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { synchronized (mLock) { @@ -929,7 +934,7 @@ final class UiModeManagerService extends SystemService { @AttentionModeThemeOverlayType int attentionModeThemeOverlayType) { setAttentionModeThemeOverlay_enforcePermission(); - enforceCurrentUserIfVisibleBackgroundEnabled(UserHandle.getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); synchronized (mLock) { if (mAttentionModeThemeOverlay != attentionModeThemeOverlayType) { @@ -1020,16 +1025,16 @@ final class UiModeManagerService extends SystemService { return false; } final int user = Binder.getCallingUserHandle().getIdentifier(); - enforceCurrentUserIfVisibleBackgroundEnabled(user); - if (user != mCurrentUser && getContext().checkCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS) != PackageManager.PERMISSION_GRANTED) { Slog.e(TAG, "Target user is not current user," + " INTERACT_ACROSS_USERS permission is required"); return false; - } + + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); + // Store the last requested bedtime night mode state so that we don't need to notify // anyone if the user decides to switch to the night mode to bedtime. if (modeCustomType == MODE_NIGHT_CUSTOM_TYPE_BEDTIME) { @@ -1078,9 +1083,10 @@ final class UiModeManagerService extends SystemService { Slog.e(TAG, "Set custom time start, requires MODIFY_DAY_NIGHT_MODE permission"); return; } - final int user = UserHandle.getCallingUserId(); - enforceCurrentUserIfVisibleBackgroundEnabled(user); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); + + final int user = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000); @@ -1108,9 +1114,10 @@ final class UiModeManagerService extends SystemService { Slog.e(TAG, "Set custom time end, requires MODIFY_DAY_NIGHT_MODE permission"); return; } - final int user = UserHandle.getCallingUserId(); - enforceCurrentUserIfVisibleBackgroundEnabled(user); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); + + final int user = UserHandle.getCallingUserId(); final long ident = Binder.clearCallingIdentity(); try { LocalTime newTime = LocalTime.ofNanoOfDay(time * 1000); @@ -1131,7 +1138,7 @@ final class UiModeManagerService extends SystemService { assertLegit(callingPackage); assertSingleProjectionType(projectionType); enforceProjectionTypePermissions(projectionType); - enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); synchronized (mLock) { if (mProjectionHolders == null) { @@ -1177,7 +1184,7 @@ final class UiModeManagerService extends SystemService { assertLegit(callingPackage); assertSingleProjectionType(projectionType); enforceProjectionTypePermissions(projectionType); - enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); return releaseProjectionUnchecked(projectionType, callingPackage); } @@ -1219,7 +1226,7 @@ final class UiModeManagerService extends SystemService { return; } - enforceCurrentUserIfVisibleBackgroundEnabled(getCallingUserId()); + enforceCurrentUserIfVisibleBackgroundEnabled(mCurrentUser); synchronized (mLock) { if (mProjectionListeners == null) { diff --git a/services/core/java/com/android/server/am/ActivityManagerConstants.java b/services/core/java/com/android/server/am/ActivityManagerConstants.java index f5a297bfd4f5..65a2c187f1c8 100644 --- a/services/core/java/com/android/server/am/ActivityManagerConstants.java +++ b/services/core/java/com/android/server/am/ActivityManagerConstants.java @@ -246,7 +246,7 @@ final class ActivityManagerConstants extends ContentObserver { static final int DEFAULT_MAX_SERVICE_CONNECTIONS_PER_PROCESS = 3000; - private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = false; + private static final boolean DEFAULT_USE_TIERED_CACHED_ADJ = Flags.oomadjusterCachedAppTiers(); private static final long DEFAULT_TIERED_CACHED_ADJ_DECAY_TIME = 60 * 1000; /** diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 7f1d912d9a79..746c55f8fc9d 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -5542,7 +5542,6 @@ public class ActivityManagerService extends IActivityManager.Stub public int sendIntentSender(IApplicationThread caller, IIntentSender target, IBinder allowlistToken, int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) { - addCreatorToken(intent); if (target instanceof PendingIntentRecord) { final PendingIntentRecord originalRecord = (PendingIntentRecord) target; @@ -5584,19 +5583,23 @@ public class ActivityManagerService extends IActivityManager.Stub intent = new Intent(Intent.ACTION_MAIN); } try { + final int callingUid = Binder.getCallingUid(); + final String packageName; + final long token = Binder.clearCallingIdentity(); + try { + packageName = AppGlobals.getPackageManager().getNameForUid(callingUid); + } finally { + Binder.restoreCallingIdentity(token); + } + if (allowlistToken != null) { - final int callingUid = Binder.getCallingUid(); - final String packageName; - final long token = Binder.clearCallingIdentity(); - try { - packageName = AppGlobals.getPackageManager().getNameForUid(callingUid); - } finally { - Binder.restoreCallingIdentity(token); - } Slog.wtf(TAG, "Send a non-null allowlistToken to a non-PI target." + " Calling package: " + packageName + "; intent: " + intent + "; options: " + options); } + + addCreatorToken(intent, packageName); + target.send(code, intent, resolvedType, null, null, requiredPermission, options); } catch (RemoteException e) { @@ -12371,7 +12374,7 @@ public class ActivityManagerService extends IActivityManager.Stub continue; } endTime = SystemClock.currentThreadTimeMillis(); - hasSwapPss = mi.hasSwappedOutPss; + hasSwapPss = hasSwapPss || mi.hasSwappedOutPss; memtrackGraphics = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GRAPHICS); memtrackGl = mi.getOtherPrivate(Debug.MemoryInfo.OTHER_GL); } else { @@ -13049,7 +13052,7 @@ public class ActivityManagerService extends IActivityManager.Stub continue; } endTime = SystemClock.currentThreadTimeMillis(); - hasSwapPss = mi.hasSwappedOutPss; + hasSwapPss = hasSwapPss || mi.hasSwappedOutPss; } else { reportType = ProcessStats.ADD_PSS_EXTERNAL; startTime = SystemClock.currentThreadTimeMillis(); @@ -13628,7 +13631,7 @@ public class ActivityManagerService extends IActivityManager.Stub throws TransactionTooLargeException { enforceNotIsolatedCaller("startService"); enforceAllowedToStartOrBindServiceIfSdkSandbox(service); - addCreatorToken(service); + addCreatorToken(service, callingPackage); if (service != null) { // Refuse possible leaked file descriptors if (service.hasFileDescriptors()) { @@ -13890,7 +13893,7 @@ public class ActivityManagerService extends IActivityManager.Stub validateServiceInstanceName(instanceName); - addCreatorToken(service); + addCreatorToken(service, callingPackage); try { if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) { final ComponentName cn = service.getComponent(); @@ -17174,7 +17177,7 @@ public class ActivityManagerService extends IActivityManager.Stub Slog.v(TAG_SERVICE, "startServiceInPackage: " + service + " type=" + resolvedType); } - addCreatorToken(service); + addCreatorToken(service, callingPackage); final long origId = Binder.clearCallingIdentity(); ComponentName res; try { @@ -18002,8 +18005,8 @@ public class ActivityManagerService extends IActivityManager.Stub } @Override - public void addCreatorToken(Intent intent) { - ActivityManagerService.this.addCreatorToken(intent); + public void addCreatorToken(Intent intent, String creatorPackage) { + ActivityManagerService.this.addCreatorToken(intent, creatorPackage); } } @@ -19160,9 +19163,9 @@ public class ActivityManagerService extends IActivityManager.Stub private final Key mKeyFields; private final WeakReference<IntentCreatorToken> mRef; - public IntentCreatorToken(int creatorUid, Intent intent) { + public IntentCreatorToken(int creatorUid, String creatorPackage, Intent intent) { super(); - this.mKeyFields = new Key(creatorUid, intent); + this.mKeyFields = new Key(creatorUid, creatorPackage, intent); mRef = new WeakReference<>(this); } @@ -19170,7 +19173,10 @@ public class ActivityManagerService extends IActivityManager.Stub return mKeyFields.mCreatorUid; } - /** {@hide} */ + public String getCreatorPackage() { + return mKeyFields.mCreatorPackage; + } + public static boolean isValid(@NonNull Intent intent) { IBinder binder = intent.getCreatorToken(); IntentCreatorToken token = null; @@ -19178,7 +19184,8 @@ public class ActivityManagerService extends IActivityManager.Stub token = (IntentCreatorToken) binder; } return token != null && token.mKeyFields.equals( - new Key(token.mKeyFields.mCreatorUid, intent)); + new Key(token.mKeyFields.mCreatorUid, token.mKeyFields.mCreatorPackage, + intent)); } @Override @@ -19202,8 +19209,9 @@ public class ActivityManagerService extends IActivityManager.Stub } private static class Key { - private Key(int creatorUid, Intent intent) { + private Key(int creatorUid, String creatorPackage, Intent intent) { this.mCreatorUid = creatorUid; + this.mCreatorPackage = creatorPackage; this.mAction = intent.getAction(); this.mData = intent.getData(); this.mType = intent.getType(); @@ -19220,6 +19228,7 @@ public class ActivityManagerService extends IActivityManager.Stub } private final int mCreatorUid; + private final String mCreatorPackage; private final String mAction; private final Uri mData; private final String mType; @@ -19233,17 +19242,20 @@ public class ActivityManagerService extends IActivityManager.Stub if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Key key = (Key) o; - return mCreatorUid == key.mCreatorUid && mFlags == key.mFlags && Objects.equals( - mAction, key.mAction) && Objects.equals(mData, key.mData) - && Objects.equals(mType, key.mType) && Objects.equals(mPackage, - key.mPackage) && Objects.equals(mComponent, key.mComponent) + return mCreatorUid == key.mCreatorUid && mFlags == key.mFlags + && Objects.equals(mCreatorPackage, key.mCreatorPackage) + && Objects.equals(mAction, key.mAction) + && Objects.equals(mData, key.mData) + && Objects.equals(mType, key.mType) + && Objects.equals(mPackage, key.mPackage) + && Objects.equals(mComponent, key.mComponent) && Objects.equals(mClipDataUris, key.mClipDataUris); } @Override public int hashCode() { - return Objects.hash(mCreatorUid, mAction, mData, mType, mPackage, mComponent, - mFlags, mClipDataUris); + return Objects.hash(mCreatorUid, mCreatorPackage, mAction, mData, mType, mPackage, + mComponent, mFlags, mClipDataUris); } } } @@ -19254,7 +19266,7 @@ public class ActivityManagerService extends IActivityManager.Stub * @param intent The given intent * @hide */ - public void addCreatorToken(@Nullable Intent intent) { + public void addCreatorToken(@Nullable Intent intent, String creatorPackage) { if (!preventIntentRedirect()) return; if (intent == null || intent.getExtraIntentKeys() == null) return; @@ -19267,7 +19279,7 @@ public class ActivityManagerService extends IActivityManager.Stub continue; } Slog.wtf(TAG, "A creator token is added to an intent."); - IBinder creatorToken = createIntentCreatorToken(extraIntent); + IBinder creatorToken = createIntentCreatorToken(extraIntent, creatorPackage); if (creatorToken != null) { extraIntent.setCreatorToken(creatorToken); } @@ -19280,15 +19292,15 @@ public class ActivityManagerService extends IActivityManager.Stub } } - private IBinder createIntentCreatorToken(Intent intent) { + private IBinder createIntentCreatorToken(Intent intent, String creatorPackage) { if (IntentCreatorToken.isValid(intent)) return null; int creatorUid = getCallingUid(); - IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, intent); + IntentCreatorToken.Key key = new IntentCreatorToken.Key(creatorUid, creatorPackage, intent); IntentCreatorToken token; synchronized (sIntentCreatorTokenCache) { WeakReference<IntentCreatorToken> ref = sIntentCreatorTokenCache.get(key); if (ref == null || ref.get() == null) { - token = new IntentCreatorToken(creatorUid, intent); + token = new IntentCreatorToken(creatorUid, creatorPackage, intent); sIntentCreatorTokenCache.put(key, token.mRef); } else { token = ref.get(); diff --git a/services/core/java/com/android/server/am/BroadcastController.java b/services/core/java/com/android/server/am/BroadcastController.java index 15f1085b7125..a00cac6aba4f 100644 --- a/services/core/java/com/android/server/am/BroadcastController.java +++ b/services/core/java/com/android/server/am/BroadcastController.java @@ -258,6 +258,7 @@ class BroadcastController { final StringBuilder sb = new StringBuilder("registerReceiver: "); sb.append(Binder.getCallingUid()); sb.append('/'); sb.append(receiverId == null ? "null" : receiverId); sb.append('/'); + sb.append("p:"); sb.append(filter.getPriority()); sb.append('/'); final int actionsCount = filter.safeCountActions(); if (actionsCount > 0) { for (int i = 0; i < actionsCount; ++i) { diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java index 776a3455acc4..f60ee66cb236 100644 --- a/services/core/java/com/android/server/am/OomAdjuster.java +++ b/services/core/java/com/android/server/am/OomAdjuster.java @@ -3283,7 +3283,12 @@ public class OomAdjuster { baseCapabilities = PROCESS_CAPABILITY_ALL; // BFSL allowed break; case PROCESS_STATE_BOUND_TOP: - baseCapabilities = PROCESS_CAPABILITY_BFSL; + if (app.getActiveInstrumentation() != null) { + baseCapabilities = PROCESS_CAPABILITY_BFSL | + PROCESS_CAPABILITY_ALL_IMPLICIT; + } else { + baseCapabilities = PROCESS_CAPABILITY_BFSL; + } break; case PROCESS_STATE_FOREGROUND_SERVICE: if (app.getActiveInstrumentation() != null) { diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 6857b6bcde15..3fb06a75d79f 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -432,6 +432,14 @@ public final class PendingIntentRecord extends IIntentSender.Stub { } } + /** + * get package name of the PendingIntent sender. + * @return package name of the PendingIntent sender. + */ + public String getPackageName() { + return key.packageName; + } + @Deprecated public int sendInner(int code, Intent intent, String resolvedType, IBinder allowlistToken, IIntentReceiver finishedReceiver, String requiredPermission, IBinder resultTo, diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 5236b0399f25..cdb01889c139 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -813,10 +813,8 @@ public final class ProcessList { private final Object mProcessChangeLock = new Object(); /** - * All of the applications we currently have running organized by name. - * The keys are strings of the application package name (as - * returned by the package manager), and the keys are ApplicationRecord - * objects. + * All of the processes that are running organized by name. + * The keys are process names and the values are the associated ProcessRecord objects. */ @CompositeRWLock({"mService", "mProcLock"}) private final MyProcessMap mProcessNames = new MyProcessMap(); @@ -5189,6 +5187,7 @@ public final class ProcessList { if (ai != null) { if (ai.packageName.equals(app.info.packageName)) { app.info = ai; + app.getWindowProcessController().updateApplicationInfo(ai); PlatformCompatCache.getInstance() .onApplicationInfoChanged(ai); } diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig index adf0e640f6bf..d67fea09f945 100644 --- a/services/core/java/com/android/server/am/flags.aconfig +++ b/services/core/java/com/android/server/am/flags.aconfig @@ -225,3 +225,11 @@ flag { description: "Migrate OomAdjuster pulled device state to a push model" bug: "302575389" } + +flag { + name: "oomadjuster_cached_app_tiers" + namespace: "system_performance" + is_fixed_read_only: true + description: "Assign cached oom_score_adj in tiers." + bug: "369893532" +} diff --git a/services/core/java/com/android/server/display/brightness/BrightnessReason.java b/services/core/java/com/android/server/display/brightness/BrightnessReason.java index 9a0ee034a8f2..a4804e1887fe 100644 --- a/services/core/java/com/android/server/display/brightness/BrightnessReason.java +++ b/services/core/java/com/android/server/display/brightness/BrightnessReason.java @@ -50,8 +50,10 @@ public final class BrightnessReason { public static final int MODIFIER_THROTTLED = 0x8; public static final int MODIFIER_MIN_LUX = 0x10; public static final int MODIFIER_MIN_USER_SET_LOWER_BOUND = 0x20; + public static final int MODIFIER_STYLUS_UNDER_USE = 0x40; public static final int MODIFIER_MASK = MODIFIER_DIMMED | MODIFIER_LOW_POWER | MODIFIER_HDR - | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND; + | MODIFIER_THROTTLED | MODIFIER_MIN_LUX | MODIFIER_MIN_USER_SET_LOWER_BOUND + | MODIFIER_STYLUS_UNDER_USE; // ADJUSTMENT_* // These things can happen at any point, even if the main brightness reason doesn't @@ -158,6 +160,9 @@ public final class BrightnessReason { if ((mModifier & MODIFIER_MIN_USER_SET_LOWER_BOUND) != 0) { sb.append(" user_min_pref"); } + if ((mModifier & MODIFIER_STYLUS_UNDER_USE) != 0) { + sb.append(" stylus_under_use"); + } int strlen = sb.length(); if (sb.charAt(strlen - 1) == '[') { sb.setLength(strlen - 2); diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java index 71fdaf3f85b6..4bd980822e46 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessController.java @@ -110,6 +110,9 @@ public final class DisplayBrightnessController { @VisibleForTesting AutomaticBrightnessController mAutomaticBrightnessController; + // True if the stylus is being used + private boolean mIsStylusBeingUsed; + /** * The constructor of DisplayBrightnessController. */ @@ -460,6 +463,8 @@ public final class DisplayBrightnessController { writer.println(" mScreenBrightnessDefault=" + mScreenBrightnessDefault); writer.println(" mPersistBrightnessNitsForDefaultDisplay=" + mPersistBrightnessNitsForDefaultDisplay); + writer.println(" mIsStylusBeingUsed=" + + mIsStylusBeingUsed); synchronized (mLock) { writer.println(" mPendingScreenBrightness=" + mPendingScreenBrightness); writer.println(" mCurrentScreenBrightness=" + mCurrentScreenBrightness); @@ -505,7 +510,12 @@ public final class DisplayBrightnessController { * Notifies if the stylus is currently being used or not. */ public void setStylusBeingUsed(boolean isEnabled) { - // Todo(b/369977976) - Disable the auto-brightness strategy + mIsStylusBeingUsed = isEnabled; + } + + @VisibleForTesting + boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; } @VisibleForTesting @@ -626,13 +636,14 @@ public final class DisplayBrightnessController { lastUserSetScreenBrightness = mLastUserSetScreenBrightness; } return new StrategySelectionRequest(displayPowerRequest, targetDisplayState, - lastUserSetScreenBrightness, userSetBrightnessChanged, displayOffloadSession); + lastUserSetScreenBrightness, userSetBrightnessChanged, displayOffloadSession, + mIsStylusBeingUsed); } private StrategyExecutionRequest constructStrategyExecutionRequest( DisplayManagerInternal.DisplayPowerRequest displayPowerRequest) { float currentScreenBrightness = getCurrentBrightness(); return new StrategyExecutionRequest(displayPowerRequest, currentScreenBrightness, - mUserSetScreenBrightnessUpdated); + mUserSetScreenBrightnessUpdated, mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java index 06890e72f068..ded7447c5fbc 100644 --- a/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java +++ b/services/core/java/com/android/server/display/brightness/DisplayBrightnessStrategySelector.java @@ -306,7 +306,8 @@ public class DisplayBrightnessStrategySelector { strategySelectionRequest.getDisplayPowerRequest().useNormalBrightnessForDoze, strategySelectionRequest.getLastUserSetScreenBrightness(), strategySelectionRequest.isUserSetBrightnessChanged()); - return mAutomaticBrightnessStrategy1.isAutoBrightnessValid(); + return !strategySelectionRequest.isStylusBeingUsed() + && mAutomaticBrightnessStrategy1.isAutoBrightnessValid(); } private StrategySelectionNotifyRequest constructStrategySelectionNotifyRequest( diff --git a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java index 304640b884ef..7a07c4fc22bf 100644 --- a/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java +++ b/services/core/java/com/android/server/display/brightness/StrategyExecutionRequest.java @@ -32,11 +32,15 @@ public final class StrategyExecutionRequest { // Represents if the user set screen brightness was changed or not. private boolean mUserSetBrightnessChanged; + private boolean mIsStylusBeingUsed; + public StrategyExecutionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, - float currentScreenBrightness, boolean userSetBrightnessChanged) { + float currentScreenBrightness, boolean userSetBrightnessChanged, + boolean isStylusBeingUsed) { mDisplayPowerRequest = displayPowerRequest; mCurrentScreenBrightness = currentScreenBrightness; mUserSetBrightnessChanged = userSetBrightnessChanged; + mIsStylusBeingUsed = isStylusBeingUsed; } public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() { @@ -51,6 +55,10 @@ public final class StrategyExecutionRequest { return mUserSetBrightnessChanged; } + public boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof StrategyExecutionRequest)) { @@ -59,12 +67,13 @@ public final class StrategyExecutionRequest { StrategyExecutionRequest other = (StrategyExecutionRequest) obj; return Objects.equals(mDisplayPowerRequest, other.getDisplayPowerRequest()) && mCurrentScreenBrightness == other.getCurrentScreenBrightness() - && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged(); + && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged() + && mIsStylusBeingUsed == other.isStylusBeingUsed(); } @Override public int hashCode() { return Objects.hash(mDisplayPowerRequest, mCurrentScreenBrightness, - mUserSetBrightnessChanged); + mUserSetBrightnessChanged, mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java index aa2f23ef9ec1..5c1f03d877a6 100644 --- a/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java +++ b/services/core/java/com/android/server/display/brightness/StrategySelectionRequest.java @@ -40,15 +40,19 @@ public final class StrategySelectionRequest { private DisplayManagerInternal.DisplayOffloadSession mDisplayOffloadSession; + private boolean mIsStylusBeingUsed; + public StrategySelectionRequest(DisplayManagerInternal.DisplayPowerRequest displayPowerRequest, int targetDisplayState, float lastUserSetScreenBrightness, boolean userSetBrightnessChanged, - DisplayManagerInternal.DisplayOffloadSession displayOffloadSession) { + DisplayManagerInternal.DisplayOffloadSession displayOffloadSession, + boolean isStylusBeingUsed) { mDisplayPowerRequest = displayPowerRequest; mTargetDisplayState = targetDisplayState; mLastUserSetScreenBrightness = lastUserSetScreenBrightness; mUserSetBrightnessChanged = userSetBrightnessChanged; mDisplayOffloadSession = displayOffloadSession; + mIsStylusBeingUsed = isStylusBeingUsed; } public DisplayManagerInternal.DisplayPowerRequest getDisplayPowerRequest() { @@ -72,6 +76,10 @@ public final class StrategySelectionRequest { return mDisplayOffloadSession; } + public boolean isStylusBeingUsed() { + return mIsStylusBeingUsed; + } + @Override public boolean equals(Object obj) { if (!(obj instanceof StrategySelectionRequest)) { @@ -82,12 +90,14 @@ public final class StrategySelectionRequest { && mTargetDisplayState == other.getTargetDisplayState() && mLastUserSetScreenBrightness == other.getLastUserSetScreenBrightness() && mUserSetBrightnessChanged == other.isUserSetBrightnessChanged() - && mDisplayOffloadSession.equals(other.getDisplayOffloadSession()); + && mDisplayOffloadSession.equals(other.getDisplayOffloadSession()) + && mIsStylusBeingUsed == other.isStylusBeingUsed(); } @Override public int hashCode() { return Objects.hash(mDisplayPowerRequest, mTargetDisplayState, - mLastUserSetScreenBrightness, mUserSetBrightnessChanged, mDisplayOffloadSession); + mLastUserSetScreenBrightness, mUserSetBrightnessChanged, mDisplayOffloadSession, + mIsStylusBeingUsed); } } diff --git a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java index 7c0c9312888e..b9de34a80bda 100644 --- a/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java +++ b/services/core/java/com/android/server/display/brightness/strategy/FallbackBrightnessStrategy.java @@ -37,6 +37,9 @@ public class FallbackBrightnessStrategy implements DisplayBrightnessStrategy{ StrategyExecutionRequest strategyExecutionRequest) { BrightnessReason brightnessReason = new BrightnessReason(); brightnessReason.setReason(BrightnessReason.REASON_MANUAL); + if (strategyExecutionRequest.isStylusBeingUsed()) { + brightnessReason.setModifier(BrightnessReason.MODIFIER_STYLUS_UNDER_USE); + } return new DisplayBrightnessState.Builder() .setBrightness(strategyExecutionRequest.getCurrentScreenBrightness()) .setBrightnessReason(brightnessReason) diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 939aad469bd8..83044c202a04 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -1536,7 +1536,16 @@ public final class InputMethodManagerService implements IInputMethodManagerImpl. Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); } final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); - return settings.getMethodMap().get(settings.getSelectedInputMethod()); + final String selectedImeId; + if (Flags.consistentGetCurrentInputMethodInfo()) { + final var bindingController = getInputMethodBindingController(userId); + synchronized (ImfLock.class) { + selectedImeId = bindingController.getSelectedMethodId(); + } + } else { + selectedImeId = settings.getSelectedInputMethod(); + } + return settings.getMethodMap().get(selectedImeId); } @BinderThread diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 636854b85ee4..d1576c5cca4f 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -17,118 +17,63 @@ package com.android.server.integrity; import static android.content.Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION; -import static android.content.Intent.EXTRA_LONG_VERSION_CODE; -import static android.content.Intent.EXTRA_ORIGINATING_UID; -import static android.content.Intent.EXTRA_PACKAGE_NAME; import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS; import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE; import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS; -import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED; import static android.content.integrity.IntegrityUtils.getHexDigest; import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID; import android.annotation.BinderThread; -import android.annotation.NonNull; -import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.IntentSender; -import android.content.integrity.AppInstallMetadata; import android.content.integrity.IAppIntegrityManager; import android.content.integrity.Rule; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ParceledListSlice; -import android.content.pm.Signature; -import android.content.pm.SigningDetails; -import android.content.pm.parsing.result.ParseResult; -import android.content.pm.parsing.result.ParseTypeImpl; import android.net.Uri; import android.os.Binder; -import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.provider.Settings; import android.util.Pair; import android.util.Slog; -import android.util.apk.SourceStampVerificationResult; -import android.util.apk.SourceStampVerifier; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.pm.parsing.PackageParser2; -import com.android.internal.pm.pkg.parsing.ParsingPackageUtils; -import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; -import com.android.server.integrity.model.IntegrityCheckResult; import com.android.server.integrity.model.RuleMetadata; -import com.android.server.pm.PackageManagerServiceUtils; -import com.android.server.pm.parsing.PackageParserUtils; -import java.io.ByteArrayInputStream; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import java.nio.file.Path; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.security.cert.CertificateEncodingException; -import java.security.cert.CertificateException; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; /** Implementation of {@link AppIntegrityManagerService}. */ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { - /** - * This string will be used as the "installer" for formula evaluation when the app's installer - * cannot be determined. - * - * <p>This may happen for various reasons. e.g., the installing app's package name may not match - * its UID. - */ - private static final String UNKNOWN_INSTALLER = ""; - /** - * This string will be used as the "installer" for formula evaluation when the app is being - * installed via ADB. - */ - public static final String ADB_INSTALLER = "adb"; private static final String TAG = "AppIntegrityManagerServiceImpl"; private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive"; - private static final String BASE_APK_FILE = "base.apk"; - private static final String ALLOWED_INSTALLERS_METADATA_NAME = "allowed-installers"; - private static final String ALLOWED_INSTALLER_DELIMITER = ","; - private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|"; public static final boolean DEBUG_INTEGRITY_COMPONENT = false; - private static final Set<String> PACKAGE_INSTALLER = - new HashSet<>( - Arrays.asList( - "com.google.android.packageinstaller", "com.android.packageinstaller")); - // Access to files inside mRulesDir is protected by mRulesLock; private final Context mContext; private final Handler mHandler; private final PackageManagerInternal mPackageManagerInternal; - private final Supplier<PackageParser2> mParserSupplier; private final IntegrityFileManager mIntegrityFileManager; /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */ @@ -139,7 +84,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return new AppIntegrityManagerServiceImpl( context, LocalServices.getService(PackageManagerInternal.class), - PackageParserUtils::forParsingFileWithDefaults, IntegrityFileManager.getInstance(), handlerThread.getThreadHandler()); } @@ -148,12 +92,10 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { AppIntegrityManagerServiceImpl( Context context, PackageManagerInternal packageManagerInternal, - Supplier<PackageParser2> parserSupplier, IntegrityFileManager integrityFileManager, Handler handler) { mContext = context; mPackageManagerInternal = packageManagerInternal; - mParserSupplier = parserSupplier; mIntegrityFileManager = integrityFileManager; mHandler = handler; @@ -263,148 +205,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { private void handleIntegrityVerification(Intent intent) { int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1); - - try { - if (DEBUG_INTEGRITY_COMPONENT) { - Slog.d(TAG, "Received integrity verification intent " + intent.toString()); - Slog.d(TAG, "Extras " + intent.getExtras()); - } - - String installerPackageName = getInstallerPackageName(intent); - - // Skip integrity verification if the verifier is doing the install. - if (!integrityCheckIncludesRuleProvider() && isRuleProvider(installerPackageName)) { - if (DEBUG_INTEGRITY_COMPONENT) { - Slog.i(TAG, "Verifier doing the install. Skipping integrity check."); - } - mPackageManagerInternal.setIntegrityVerificationResult( - verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); - return; - } - - String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME); - - Pair<SigningDetails, Bundle> packageSigningAndMetadata = - getPackageSigningAndMetadata(intent.getData()); - if (packageSigningAndMetadata == null) { - Slog.w(TAG, "Cannot parse package " + packageName); - // We can't parse the package. - mPackageManagerInternal.setIntegrityVerificationResult( - verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); - return; - } - - var signingDetails = packageSigningAndMetadata.first; - List<String> appCertificates = getCertificateFingerprint(packageName, signingDetails); - List<String> appCertificateLineage = getCertificateLineage(packageName, signingDetails); - List<String> installerCertificates = - getInstallerCertificateFingerprint(installerPackageName); - - AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder(); - - builder.setPackageName(getPackageNameNormalized(packageName)); - builder.setAppCertificates(appCertificates); - builder.setAppCertificateLineage(appCertificateLineage); - builder.setVersionCode(intent.getLongExtra(EXTRA_LONG_VERSION_CODE, -1)); - builder.setInstallerName(getPackageNameNormalized(installerPackageName)); - builder.setInstallerCertificates(installerCertificates); - builder.setIsPreInstalled(isSystemApp(packageName)); - - Map<String, String> allowedInstallers = - getAllowedInstallers(packageSigningAndMetadata.second); - builder.setAllowedInstallersAndCert(allowedInstallers); - extractSourceStamp(intent.getData(), builder); - - AppInstallMetadata appInstallMetadata = builder.build(); - - if (DEBUG_INTEGRITY_COMPONENT) { - Slog.i( - TAG, - "To be verified: " - + appInstallMetadata - + " installers " - + allowedInstallers); - } - IntegrityCheckResult result = IntegrityCheckResult.allow(); - if (!result.getMatchedRules().isEmpty() || DEBUG_INTEGRITY_COMPONENT) { - Slog.i( - TAG, - String.format( - "Integrity check of %s result: %s due to %s", - packageName, result.getEffect(), result.getMatchedRules())); - } - - mPackageManagerInternal.setIntegrityVerificationResult( - verificationId, - result.getEffect() == IntegrityCheckResult.Effect.ALLOW - ? PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW - : PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT); - } catch (IllegalArgumentException e) { - // This exception indicates something is wrong with the input passed by package manager. - // e.g., someone trying to trick the system. We block installs in this case. - Slog.e(TAG, "Invalid input to integrity verification", e); - mPackageManagerInternal.setIntegrityVerificationResult( - verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT); - } catch (Exception e) { - // Other exceptions indicate an error within the integrity component implementation and - // we allow them. - Slog.e(TAG, "Error handling integrity verification", e); - mPackageManagerInternal.setIntegrityVerificationResult( - verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); - } - } - - /** - * Verify the UID and return the installer package name. - * - * @return the package name of the installer, or null if it cannot be determined or it is - * installed via adb. - */ - @Nullable - private String getInstallerPackageName(Intent intent) { - String installer = - intent.getStringExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE); - if (PackageManagerServiceUtils.isInstalledByAdb(installer)) { - return ADB_INSTALLER; - } - int installerUid = intent.getIntExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID, -1); - if (installerUid < 0) { - Slog.e( - TAG, - "Installer cannot be determined: installer: " - + installer - + " installer UID: " - + installerUid); - return UNKNOWN_INSTALLER; - } - - // Verify that the installer UID actually contains the package. Note that comparing UIDs - // is not safe since context's uid can change in different settings; e.g. Android Auto. - if (!getPackageListForUid(installerUid).contains(installer)) { - return UNKNOWN_INSTALLER; - } - - // At this time we can trust "installer". - - // A common way for apps to install packages is to send an intent to PackageInstaller. In - // that case, the installer will always show up as PackageInstaller which is not what we - // want. - if (PACKAGE_INSTALLER.contains(installer)) { - int originatingUid = intent.getIntExtra(EXTRA_ORIGINATING_UID, -1); - if (originatingUid < 0) { - Slog.e(TAG, "Installer is package installer but originating UID not found."); - return UNKNOWN_INSTALLER; - } - List<String> installerPackages = getPackageListForUid(originatingUid); - if (installerPackages.isEmpty()) { - Slog.e(TAG, "No package found associated with originating UID " + originatingUid); - return UNKNOWN_INSTALLER; - } - // In the case of multiple package sharing a UID, we just return the first one. - return installerPackages.get(0); - } - - return installer; + mPackageManagerInternal.setIntegrityVerificationResult( + verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); } /** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */ @@ -422,264 +224,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } } - private List<String> getInstallerCertificateFingerprint(String installer) { - if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) { - return Collections.emptyList(); - } - var installerPkg = mPackageManagerInternal.getPackage(installer); - if (installerPkg == null) { - Slog.w(TAG, "Installer package " + installer + " not found."); - return Collections.emptyList(); - } - return getCertificateFingerprint(installerPkg.getPackageName(), - installerPkg.getSigningDetails()); - } - - private List<String> getCertificateFingerprint(@NonNull String packageName, - @NonNull SigningDetails signingDetails) { - ArrayList<String> certificateFingerprints = new ArrayList(); - for (Signature signature : getSignatures(packageName, signingDetails)) { - certificateFingerprints.add(getFingerprint(signature)); - } - return certificateFingerprints; - } - - private List<String> getCertificateLineage(@NonNull String packageName, - @NonNull SigningDetails signingDetails) { - ArrayList<String> certificateLineage = new ArrayList(); - for (Signature signature : getSignatureLineage(packageName, signingDetails)) { - certificateLineage.add(getFingerprint(signature)); - } - return certificateLineage; - } - - /** Get the allowed installers and their associated certificate hashes from <meta-data> tag. */ - private Map<String, String> getAllowedInstallers(@Nullable Bundle metaData) { - Map<String, String> packageCertMap = new HashMap<>(); - if (metaData != null) { - String allowedInstallers = metaData.getString(ALLOWED_INSTALLERS_METADATA_NAME); - if (allowedInstallers != null) { - // parse the metadata for certs. - String[] installerCertPairs = allowedInstallers.split(ALLOWED_INSTALLER_DELIMITER); - for (String packageCertPair : installerCertPairs) { - String[] packageAndCert = - packageCertPair.split(INSTALLER_PACKAGE_CERT_DELIMITER); - if (packageAndCert.length == 2) { - String packageName = getPackageNameNormalized(packageAndCert[0]); - String cert = packageAndCert[1]; - packageCertMap.put(packageName, cert); - } else if (packageAndCert.length == 1) { - packageCertMap.put( - getPackageNameNormalized(packageAndCert[0]), - INSTALLER_CERTIFICATE_NOT_EVALUATED); - } - } - } - } - - return packageCertMap; - } - - /** Extract the source stamp embedded in the APK, if present. */ - private void extractSourceStamp(Uri dataUri, AppInstallMetadata.Builder appInstallMetadata) { - File installationPath = getInstallationPath(dataUri); - if (installationPath == null) { - throw new IllegalArgumentException("Installation path is null, package not found"); - } - - SourceStampVerificationResult sourceStampVerificationResult; - if (installationPath.isDirectory()) { - try (Stream<Path> filesList = Files.list(installationPath.toPath())) { - List<String> apkFiles = - filesList - .map(path -> path.toAbsolutePath().toString()) - .filter(str -> str.endsWith(".apk")) - .collect(Collectors.toList()); - sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles); - } catch (IOException e) { - throw new IllegalArgumentException("Could not read APK directory"); - } - } else { - sourceStampVerificationResult = - SourceStampVerifier.verify(installationPath.getAbsolutePath()); - } - - appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent()); - appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified()); - // A verified stamp is set to be trusted. - appInstallMetadata.setIsStampTrusted(sourceStampVerificationResult.isVerified()); - if (sourceStampVerificationResult.isVerified()) { - X509Certificate sourceStampCertificate = - (X509Certificate) sourceStampVerificationResult.getCertificate(); - // Sets source stamp certificate digest. - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] certificateDigest = digest.digest(sourceStampCertificate.getEncoded()); - appInstallMetadata.setStampCertificateHash(getHexDigest(certificateDigest)); - } catch (NoSuchAlgorithmException | CertificateEncodingException e) { - throw new IllegalArgumentException( - "Error computing source stamp certificate digest", e); - } - } - } - - private static Signature[] getSignatures(@NonNull String packageName, - @NonNull SigningDetails signingDetails) { - Signature[] signatures = signingDetails.getSignatures(); - if (signatures == null || signatures.length < 1) { - throw new IllegalArgumentException("Package signature not found in " + packageName); - } - - // We are only interested in evaluating the active signatures. - return signatures; - } - - private static Signature[] getSignatureLineage(@NonNull String packageName, - @NonNull SigningDetails signingDetails) { - // Obtain the active signatures of the package. - Signature[] signatureLineage = getSignatures(packageName, signingDetails); - - var pastSignatures = signingDetails.getPastSigningCertificates(); - // Obtain the past signatures of the package. - if (signatureLineage.length == 1 && !ArrayUtils.isEmpty(pastSignatures)) { - // Merge the signatures and return. - Signature[] allSignatures = - new Signature[signatureLineage.length + pastSignatures.length]; - int i; - for (i = 0; i < signatureLineage.length; i++) { - allSignatures[i] = signatureLineage[i]; - } - for (int j = 0; j < pastSignatures.length; j++) { - allSignatures[i] = pastSignatures[j]; - i++; - } - signatureLineage = allSignatures; - } - - return signatureLineage; - } - - private static String getFingerprint(Signature cert) { - InputStream input = new ByteArrayInputStream(cert.toByteArray()); - - CertificateFactory factory; - try { - factory = CertificateFactory.getInstance("X509"); - } catch (CertificateException e) { - throw new RuntimeException("Error getting CertificateFactory", e); - } - X509Certificate certificate = null; - try { - if (factory != null) { - certificate = (X509Certificate) factory.generateCertificate(input); - } - } catch (CertificateException e) { - throw new RuntimeException("Error getting X509Certificate", e); - } - - if (certificate == null) { - throw new RuntimeException("X509 Certificate not found"); - } - - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - byte[] publicKey = digest.digest(certificate.getEncoded()); - return getHexDigest(publicKey); - } catch (NoSuchAlgorithmException | CertificateEncodingException e) { - throw new IllegalArgumentException("Error error computing fingerprint", e); - } - } - - @Nullable - private Pair<SigningDetails, Bundle> getPackageSigningAndMetadata(Uri dataUri) { - File installationPath = getInstallationPath(dataUri); - if (installationPath == null) { - throw new IllegalArgumentException("Installation path is null, package not found"); - } - - try (PackageParser2 parser = mParserSupplier.get()) { - var pkg = parser.parsePackage(installationPath, 0, false); - // APK signatures is already verified elsewhere in PackageManager. We do not need to - // verify it again since it could cause a timeout for large APKs. - final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing(); - final ParseResult<SigningDetails> result = ParsingPackageUtils.getSigningDetails( - input, pkg, /* skipVerify= */ true); - if (result.isError()) { - Slog.w(TAG, result.getErrorMessage(), result.getException()); - return null; - } - return Pair.create(result.getResult(), pkg.getMetaData()); - } catch (Exception e) { - Slog.w(TAG, "Exception reading " + dataUri, e); - return null; - } - } - - private PackageInfo getMultiApkInfo(File multiApkDirectory) { - // The base apk will normally be called base.apk - File baseFile = new File(multiApkDirectory, BASE_APK_FILE); - PackageInfo basePackageInfo = - mContext.getPackageManager() - .getPackageArchiveInfo( - baseFile.getAbsolutePath(), - PackageManager.GET_SIGNING_CERTIFICATES - | PackageManager.GET_META_DATA); - - if (basePackageInfo == null) { - for (File apkFile : multiApkDirectory.listFiles()) { - if (apkFile.isDirectory()) { - continue; - } - - // If we didn't find a base.apk, then try to parse each apk until we find the one - // that succeeds. - try { - basePackageInfo = - mContext.getPackageManager() - .getPackageArchiveInfo( - apkFile.getAbsolutePath(), - PackageManager.GET_SIGNING_CERTIFICATES - | PackageManager.GET_META_DATA); - } catch (Exception e) { - // Some of the splits may not contain a valid android manifest. It is an - // expected exception. We still log it nonetheless but we should keep looking. - Slog.w(TAG, "Exception reading " + apkFile, e); - } - if (basePackageInfo != null) { - Slog.i(TAG, "Found package info from " + apkFile); - break; - } - } - } - - if (basePackageInfo == null) { - throw new IllegalArgumentException( - "Base package info cannot be found from installation directory"); - } - - return basePackageInfo; - } - - private File getInstallationPath(Uri dataUri) { - if (dataUri == null) { - throw new IllegalArgumentException("Null data uri"); - } - - String scheme = dataUri.getScheme(); - if (!"file".equalsIgnoreCase(scheme)) { - throw new IllegalArgumentException("Unsupported scheme for " + dataUri); - } - - File installationPath = new File(dataUri.getPath()); - if (!installationPath.exists()) { - throw new IllegalArgumentException("Cannot find file for " + dataUri); - } - if (!installationPath.canRead()) { - throw new IllegalArgumentException("Cannot read file for " + dataUri); - } - return installationPath; - } - private String getCallerPackageNameOrThrow(int callingUid) { String callerPackageName = getCallingRulePusherPackageName(callingUid); if (callerPackageName == null) { @@ -715,15 +259,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return allowedCallingPackages.isEmpty() ? null : allowedCallingPackages.get(0); } - private boolean isRuleProvider(String installerPackageName) { - for (String ruleProvider : getAllowedRuleProviderSystemApps()) { - if (ruleProvider.matches(installerPackageName)) { - return true; - } - } - return false; - } - private List<String> getAllowedRuleProviderSystemApps() { List<String> integrityRuleProviders = Arrays.asList( @@ -751,14 +286,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } } - private boolean integrityCheckIncludesRuleProvider() { - return Settings.Global.getInt( - mContext.getContentResolver(), - Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, - 0) - == 1; - } - private List<String> getPackageListForUid(int uid) { try { return Arrays.asList(mContext.getPackageManager().getPackagesForUid(uid)); diff --git a/services/core/java/com/android/server/media/AudioManagerRouteController.java b/services/core/java/com/android/server/media/AudioManagerRouteController.java index c79f41d32b29..25e1c94aa689 100644 --- a/services/core/java/com/android/server/media/AudioManagerRouteController.java +++ b/services/core/java/com/android/server/media/AudioManagerRouteController.java @@ -420,7 +420,7 @@ import java.util.Objects; // to derive a name ourselves from the type instead. String deviceName = audioDeviceInfo.getPort().name(); - if (!TextUtils.isEmpty(address)) { + if (mBluetoothRouteController.containsBondedDeviceWithAddress(address)) { routeId = mBluetoothRouteController.getRouteIdForBluetoothAddress(address); deviceName = mBluetoothRouteController.getNameForBluetoothAddress(address); } diff --git a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java index 8b65ea305ad8..c79d6e5400cd 100644 --- a/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java +++ b/services/core/java/com/android/server/media/BluetoothDeviceRoutesManager.java @@ -141,6 +141,11 @@ import java.util.stream.Collectors; mContext.unregisterReceiver(mDeviceStateChangedReceiver); } + /** Returns true if the given address corresponds to a currently-bonded Bluetooth device. */ + public synchronized boolean containsBondedDeviceWithAddress(@Nullable String address) { + return mAddressToBondedDevice.containsKey(address); + } + @Nullable public synchronized String getRouteIdForBluetoothAddress(@Nullable String address) { BluetoothDevice bluetoothDevice = mAddressToBondedDevice.get(address); diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index d1d5d4868c6f..27bc1cf3e631 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -677,24 +677,15 @@ class MediaRouter2ServiceImpl { @NonNull IMediaRouter2Manager manager, int requestId, @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + @NonNull MediaRoute2Info route) { Objects.requireNonNull(manager, "manager must not be null"); Objects.requireNonNull(oldSession, "oldSession must not be null"); Objects.requireNonNull(route, "route must not be null"); - Objects.requireNonNull(transferInitiatorUserHandle); final long token = Binder.clearCallingIdentity(); try { synchronized (mLock) { - requestCreateSessionWithManagerLocked( - requestId, - manager, - oldSession, - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route); } } finally { Binder.restoreCallingIdentity(token); @@ -1738,9 +1729,7 @@ class MediaRouter2ServiceImpl { int requestId, @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession, - @NonNull MediaRoute2Info route, - @NonNull UserHandle transferInitiatorUserHandle, - @NonNull String transferInitiatorPackageName) { + @NonNull MediaRoute2Info route) { ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder()); if (managerRecord == null) { return; diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 363b8e4228b0..68e195d7f079 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -607,16 +607,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub IMediaRouter2Manager manager, int requestId, RoutingSessionInfo oldSession, - MediaRoute2Info route, - UserHandle transferInitiatorUserHandle, - String transferInitiatorPackageName) { - mService2.requestCreateSessionWithManager( - manager, - requestId, - oldSession, - route, - transferInitiatorUserHandle, - transferInitiatorPackageName); + MediaRoute2Info route) { + mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route); } // Binder call diff --git a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java index a6f4c0e597d1..2a3be1e119bf 100644 --- a/services/core/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/core/java/com/android/server/net/NetworkPolicyManagerService.java @@ -3100,11 +3100,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } synchronized (mUidRulesFirstLock) { - final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE); - policy |= oldPolicy; - if (oldPolicy != policy) { - setUidPolicyUncheckedUL(uid, oldPolicy, policy, true); - mLogger.uidPolicyChanged(uid, oldPolicy, policy); + final long token = Binder.clearCallingIdentity(); + try { + final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE); + policy |= oldPolicy; + if (oldPolicy != policy) { + setUidPolicyUncheckedUL(uid, oldPolicy, policy, true); + mLogger.uidPolicyChanged(uid, oldPolicy, policy); + } + } finally { + Binder.restoreCallingIdentity(token); } } } @@ -3119,11 +3124,16 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { } synchronized (mUidRulesFirstLock) { - final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE); - policy = oldPolicy & ~policy; - if (oldPolicy != policy) { - setUidPolicyUncheckedUL(uid, oldPolicy, policy, true); - mLogger.uidPolicyChanged(uid, oldPolicy, policy); + final long token = Binder.clearCallingIdentity(); + try { + final int oldPolicy = mUidPolicy.get(uid, POLICY_NONE); + policy = oldPolicy & ~policy; + if (oldPolicy != policy) { + setUidPolicyUncheckedUL(uid, oldPolicy, policy, true); + mLogger.uidPolicyChanged(uid, oldPolicy, policy); + } + } finally { + Binder.restoreCallingIdentity(token); } } } diff --git a/services/core/java/com/android/server/notification/NotificationManagerInternal.java b/services/core/java/com/android/server/notification/NotificationManagerInternal.java index f3d6a2dd0a75..d5d4070ee4c3 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerInternal.java +++ b/services/core/java/com/android/server/notification/NotificationManagerInternal.java @@ -44,7 +44,7 @@ public interface NotificationManagerInternal { void onConversationRemoved(String pkg, int uid, Set<String> shortcuts); - /** Get the number of notification channels for a given package */ + /** Get the number of app created notification channels for a given package */ int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted); /** Does the specified package/uid have permission to post notifications? */ diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 828e02ce3edc..62d762244617 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -4392,8 +4392,9 @@ public class NotificationManagerService extends SystemService { List<NotificationChannel> channels = channelsList.getList(); final int channelsSize = channels.size(); ParceledListSlice<NotificationChannel> oldChannels = - mPreferencesHelper.getNotificationChannels(pkg, uid, true); - final boolean hadChannel = oldChannels != null && !oldChannels.getList().isEmpty(); + mPreferencesHelper.getNotificationChannels(pkg, uid, true, false); + final boolean hadNonBundleChannel = + oldChannels != null && !oldChannels.getList().isEmpty(); boolean needsPolicyFileChange = false; boolean hasRequestedNotificationPermission = false; for (int i = 0; i < channelsSize; i++) { @@ -4410,13 +4411,18 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper.getNotificationChannel(pkg, uid, channel.getId(), false), NOTIFICATION_CHANNEL_OR_GROUP_ADDED); - boolean hasChannel = hadChannel || hasRequestedNotificationPermission; - if (!hasChannel) { + boolean hasNonBundleChannel = + hadNonBundleChannel || hasRequestedNotificationPermission; + if (!hasNonBundleChannel) { ParceledListSlice<NotificationChannel> currChannels = - mPreferencesHelper.getNotificationChannels(pkg, uid, true); - hasChannel = currChannels != null && !currChannels.getList().isEmpty(); - } - if (!hadChannel && hasChannel && !hasRequestedNotificationPermission + mPreferencesHelper.getNotificationChannels(pkg, uid, true, false); + hasNonBundleChannel = + currChannels != null && !currChannels.getList().isEmpty(); + } + // show perm prompt if new non-bundle channel added and the user has not + // seen the prompt + if (!hadNonBundleChannel && hasNonBundleChannel + && !hasRequestedNotificationPermission && startingTaskId != ActivityTaskManager.INVALID_TASK_ID) { hasRequestedNotificationPermission = true; if (mPermissionPolicyInternal == null) { @@ -4651,7 +4657,7 @@ public class NotificationManagerService extends SystemService { public ParceledListSlice<NotificationChannel> getNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) { enforceSystemOrSystemUI("getNotificationChannelsForPackage"); - return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted); + return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted, true); } @Override @@ -4783,7 +4789,7 @@ public class NotificationManagerService extends SystemService { /* ignore */ } return mPreferencesHelper.getNotificationChannels( - targetPkg, targetUid, false /* includeDeleted */); + targetPkg, targetUid, false /* includeDeleted */, true); } throw new SecurityException("Pkg " + callingPkg + " cannot read channels for " + targetPkg + " in " + userId); @@ -6652,7 +6658,7 @@ public class NotificationManagerService extends SystemService { verifyPrivilegedListener(token, user, true); return mPreferencesHelper.getNotificationChannels(pkg, - getUidForPackageAndUser(pkg, user), false /* includeDeleted */); + getUidForPackageAndUser(pkg, user), false /* includeDeleted */, true); } @Override @@ -7693,8 +7699,9 @@ public class NotificationManagerService extends SystemService { } int getNumNotificationChannelsForPackage(String pkg, int uid, boolean includeDeleted) { - return mPreferencesHelper.getNotificationChannels(pkg, uid, includeDeleted).getList() - .size(); + // don't show perm prompt if the only channels are bundle channels + return mPreferencesHelper.getNotificationChannels( + pkg, uid, includeDeleted, false).getList().size(); } void cancelNotificationInternal(String pkg, String opPkg, int callingUid, int callingPid, @@ -8005,11 +8012,16 @@ public class NotificationManagerService extends SystemService { } /** - * Returns a channel, if exists, and restores deleted conversation channels. + * Returns a channel, if exists and is not a bundle channel, and restores deleted + * conversation channels. */ @Nullable private NotificationChannel getNotificationChannelRestoreDeleted(String pkg, int callingUid, int notificationUid, String channelId, String conversationId) { + if (SYSTEM_RESERVED_IDS.contains(channelId)) { + // apps cannot post to these channels directly, in case they post incorrect content + return null; + } // Restore a deleted conversation channel, if exists. Otherwise use the parent channel. NotificationChannel channel = mPreferencesHelper.getConversationNotificationChannel( pkg, notificationUid, channelId, conversationId, diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index 3349b1308c3f..c9edc4106943 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -1870,7 +1870,7 @@ public class PreferencesHelper implements RankingConfig { @Override public ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, - boolean includeDeleted) { + boolean includeDeleted, boolean includeBundles) { Objects.requireNonNull(pkg); List<NotificationChannel> channels = new ArrayList<>(); synchronized (mLock) { @@ -1882,7 +1882,9 @@ public class PreferencesHelper implements RankingConfig { for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (includeDeleted || !nc.isDeleted()) { - channels.add(nc); + if (includeBundles || !SYSTEM_RESERVED_IDS.contains(nc.getId())) { + channels.add(nc); + } } } return new ParceledListSlice<>(channels); diff --git a/services/core/java/com/android/server/notification/RankingConfig.java b/services/core/java/com/android/server/notification/RankingConfig.java index 8df24c9911a6..001e91cbea12 100644 --- a/services/core/java/com/android/server/notification/RankingConfig.java +++ b/services/core/java/com/android/server/notification/RankingConfig.java @@ -55,5 +55,5 @@ public interface RankingConfig { void permanentlyDeleteNotificationChannel(String pkg, int uid, String channelId); void permanentlyDeleteNotificationChannels(String pkg, int uid); ParceledListSlice<NotificationChannel> getNotificationChannels(String pkg, int uid, - boolean includeDeleted); + boolean includeDeleted, boolean includeBundles); } diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig index a24c743929b5..f79d9ef174ea 100644 --- a/services/core/java/com/android/server/notification/flags.aconfig +++ b/services/core/java/com/android/server/notification/flags.aconfig @@ -165,6 +165,13 @@ flag { } flag { + name: "notification_lock_screen_settings" + namespace: "systemui" + description: "This flag enables the new settings page for the notifications on lock screen." + bug: "367455695" +} + +flag { name: "notification_vibration_in_sound_uri" namespace: "systemui" description: "This flag enables sound uri with vibration source" diff --git a/services/core/java/com/android/server/pinner/PinnerService.java b/services/core/java/com/android/server/pinner/PinnerService.java index d7ac5203ff53..2c75926c4943 100644 --- a/services/core/java/com/android/server/pinner/PinnerService.java +++ b/services/core/java/com/android/server/pinner/PinnerService.java @@ -121,9 +121,6 @@ public final class PinnerService extends SystemService { private static boolean PROP_PIN_PINLIST = SystemProperties.getBoolean("pinner.use_pinlist", true); - private static final int MAX_CAMERA_PIN_SIZE = 80 * (1 << 20); // 80MB max for camera app. - private static final int MAX_ASSISTANT_PIN_SIZE = 60 * (1 << 20); // 60MB max for assistant app. - public static final String ANON_REGION_STAT_NAME = "[anon]"; private static final String SYSTEM_GROUP_NAME = "system"; @@ -179,8 +176,10 @@ public final class PinnerService extends SystemService { // Resource-configured pinner flags; private final boolean mConfiguredToPinCamera; + private final int mConfiguredCameraPinBytes; private final int mConfiguredHomePinBytes; private final boolean mConfiguredToPinAssistant; + private final int mConfiguredAssistantPinBytes; private final int mConfiguredWebviewPinBytes; // This is the percentage of total device memory that will be used to set the global quota. @@ -250,6 +249,10 @@ public final class PinnerService extends SystemService { mDeviceConfigInterface = mInjector.getDeviceConfigInterface(); mConfiguredToPinCamera = context.getResources().getBoolean( com.android.internal.R.bool.config_pinnerCameraApp); + mConfiguredCameraPinBytes = context.getResources().getInteger( + com.android.internal.R.integer.config_pinnerCameraPinBytes); + mConfiguredAssistantPinBytes = context.getResources().getInteger( + com.android.internal.R.integer.config_pinnerAssistantPinBytes); mConfiguredHomePinBytes = context.getResources().getInteger( com.android.internal.R.integer.config_pinnerHomePinBytes); mConfiguredToPinAssistant = context.getResources().getBoolean( @@ -812,11 +815,11 @@ public final class PinnerService extends SystemService { private int getSizeLimitForKey(@AppKey int key) { switch (key) { case KEY_CAMERA: - return MAX_CAMERA_PIN_SIZE; + return mConfiguredCameraPinBytes; case KEY_HOME: return mConfiguredHomePinBytes; case KEY_ASSISTANT: - return MAX_ASSISTANT_PIN_SIZE; + return mConfiguredAssistantPinBytes; default: return 0; } @@ -1303,6 +1306,10 @@ public final class PinnerService extends SystemService { pw.format(" Maximum Pinner quota: %d bytes (%.2f MB)\n", mConfiguredMaxPinnedMemory, mConfiguredMaxPinnedMemory / bytesPerMB); pw.format(" Max Home App Pin Bytes (without deps): %d\n", mConfiguredHomePinBytes); + pw.format(" Max Assistant App Pin Bytes (without deps): %d\n", + mConfiguredAssistantPinBytes); + pw.format( + " Max Camera App Pin Bytes (without deps): %d\n", mConfiguredCameraPinBytes); pw.format("\nPinned Files:\n"); synchronized (PinnerService.this) { long totalSize = 0; diff --git a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java index 49a6ffde6783..a221152222ee 100644 --- a/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java +++ b/services/core/java/com/android/server/pm/BackgroundUserSoundNotifier.java @@ -55,6 +55,8 @@ public class BackgroundUserSoundNotifier { private static final String ACTION_SWITCH_USER = "com.android.server.ACTION_SWITCH_TO_USER"; private static final String ACTION_DISMISS_NOTIFICATION = "com.android.server.ACTION_DISMISS_NOTIFICATION"; + private static final String EXTRA_NOTIFICATION_CLIENT_UID = + "com.android.server.EXTRA_CLIENT_UID"; /** * The clientUid from the AudioFocusInfo of the background user, * for which an active notification is currently displayed. @@ -101,7 +103,7 @@ public class BackgroundUserSoundNotifier { ActivityManager am = mSystemUserContext.getSystemService(ActivityManager.class); registerReceiver(am); - mBgUserListener = new BackgroundUserListener(mSystemUserContext); + mBgUserListener = new BackgroundUserListener(); AudioPolicy.Builder focusControlPolicyBuilder = new AudioPolicy.Builder(mSystemUserContext); focusControlPolicyBuilder.setLooper(Looper.getMainLooper()); @@ -119,26 +121,16 @@ public class BackgroundUserSoundNotifier { final class BackgroundUserListener extends AudioPolicy.AudioPolicyFocusListener { - Context mSystemContext; - - BackgroundUserListener(Context systemContext) { - mSystemContext = systemContext; - } - - @SuppressLint("MissingPermission") public void onAudioFocusGrant(AudioFocusInfo afi, int requestResult) { try { - BackgroundUserSoundNotifier.this.notifyForegroundUserAboutSoundIfNecessary(afi, - mSystemContext.createContextAsUser( - UserHandle.of(ActivityManager.getCurrentUser()), 0)); + BackgroundUserSoundNotifier.this.notifyForegroundUserAboutSoundIfNecessary(afi); } catch (RemoteException e) { throw new RuntimeException(e); } } - @SuppressLint("MissingPermission") public void onAudioFocusLoss(AudioFocusInfo afi, boolean wasNotified) { - BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(); + BackgroundUserSoundNotifier.this.dismissNotificationIfNecessary(afi.getClientUid()); } } @@ -160,7 +152,7 @@ public class BackgroundUserSoundNotifier { if (mNotificationClientUid == -1) { return; } - dismissNotification(); + dismissNotification(mNotificationClientUid); if (DEBUG) { final int actionIndex = intent.getAction().lastIndexOf(".") + 1; @@ -171,7 +163,7 @@ public class BackgroundUserSoundNotifier { } if (ACTION_MUTE_SOUND.equals(intent.getAction())) { - muteAlarmSounds(mSystemUserContext); + muteAlarmSounds(mNotificationClientUid); } else if (ACTION_SWITCH_USER.equals(intent.getAction())) { activityManager.switchUser(UserHandle.getUserId(mNotificationClientUid)); } @@ -193,17 +185,17 @@ public class BackgroundUserSoundNotifier { */ @SuppressLint("MissingPermission") @VisibleForTesting - void muteAlarmSounds(Context context) { - AudioManager audioManager = context.getSystemService(AudioManager.class); + void muteAlarmSounds(int notificationClientUid) { + AudioManager audioManager = mSystemUserContext.getSystemService(AudioManager.class); if (audioManager != null) { for (AudioPlaybackConfiguration apc : audioManager.getActivePlaybackConfigurations()) { - if (apc.getClientUid() == mNotificationClientUid && apc.getPlayerProxy() != null) { + if (apc.getClientUid() == notificationClientUid && apc.getPlayerProxy() != null) { apc.getPlayerProxy().stop(); } } } - AudioFocusInfo currentAfi = getAudioFocusInfoForNotification(); + AudioFocusInfo currentAfi = getAudioFocusInfoForNotification(notificationClientUid); if (currentAfi != null) { mFocusControlAudioPolicy.sendFocusLossAndUpdate(currentAfi); } @@ -212,9 +204,14 @@ public class BackgroundUserSoundNotifier { /** * Check if sound is coming from background user and show notification is required. */ + @SuppressLint("MissingPermission") @VisibleForTesting - void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi, Context foregroundContext) - throws RemoteException { + void notifyForegroundUserAboutSoundIfNecessary(AudioFocusInfo afi) throws RemoteException { + if (afi == null) { + return; + } + Context foregroundContext = mSystemUserContext.createContextAsUser( + UserHandle.of(ActivityManager.getCurrentUser()), 0); final int userId = UserHandle.getUserId(afi.getClientUid()); final int usage = afi.getAttributes().getUsage(); UserInfo userInfo = mUserManager.getUserInfo(userId); @@ -232,8 +229,8 @@ public class BackgroundUserSoundNotifier { mNotificationClientUid = afi.getClientUid(); - mNotificationManager.notifyAsUser(LOG_TAG, mNotificationClientUid, - createNotification(userInfo.name, foregroundContext), + mNotificationManager.notifyAsUser(LOG_TAG, afi.getClientUid(), + createNotification(userInfo.name, foregroundContext, afi.getClientUid()), foregroundContext.getUser()); } } @@ -245,14 +242,15 @@ public class BackgroundUserSoundNotifier { * focus ownership. */ @VisibleForTesting - void dismissNotificationIfNecessary() { - if (getAudioFocusInfoForNotification() == null && mNotificationClientUid >= 0) { + void dismissNotificationIfNecessary(int notificationClientUid) { + if (getAudioFocusInfoForNotification(notificationClientUid) == null + && mNotificationClientUid >= 0) { if (DEBUG) { Log.d(LOG_TAG, "Alarm ringing on background user " - + UserHandle.getUserHandleForUid(mNotificationClientUid).getIdentifier() + + UserHandle.getUserHandleForUid(notificationClientUid).getIdentifier() + " left focus stack, dismissing notification"); } - dismissNotification(); + dismissNotification(notificationClientUid); mNotificationClientUid = -1; } } @@ -262,8 +260,8 @@ public class BackgroundUserSoundNotifier { * shown. */ @SuppressLint("MissingPermission") - private void dismissNotification() { - mNotificationManager.cancelAsUser(LOG_TAG, mNotificationClientUid, UserHandle.ALL); + private void dismissNotification(int notificationClientUid) { + mNotificationManager.cancelAsUser(LOG_TAG, notificationClientUid, UserHandle.ALL); } /** @@ -272,11 +270,11 @@ public class BackgroundUserSoundNotifier { @SuppressLint("MissingPermission") @VisibleForTesting @Nullable - AudioFocusInfo getAudioFocusInfoForNotification() { - if (mNotificationClientUid >= 0) { + AudioFocusInfo getAudioFocusInfoForNotification(int notificationClientUid) { + if (notificationClientUid >= 0) { List<AudioFocusInfo> stack = mFocusControlAudioPolicy.getFocusStack(); for (int i = stack.size() - 1; i >= 0; i--) { - if (stack.get(i).getClientUid() == mNotificationClientUid) { + if (stack.get(i).getClientUid() == notificationClientUid) { return stack.get(i); } } @@ -284,22 +282,24 @@ public class BackgroundUserSoundNotifier { return null; } - private PendingIntent createPendingIntent(String intentAction) { + private PendingIntent createPendingIntent(String intentAction, int notificationClientUid) { final Intent intent = new Intent(intentAction); - PendingIntent resultPI = PendingIntent.getBroadcast(mSystemUserContext, 0, intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - return resultPI; + intent.putExtra(EXTRA_NOTIFICATION_CLIENT_UID, notificationClientUid); + return PendingIntent.getBroadcast(mSystemUserContext, notificationClientUid, intent, + PendingIntent.FLAG_IMMUTABLE); } + @SuppressLint("MissingPermission") @VisibleForTesting - Notification createNotification(String userName, Context fgContext) { + Notification createNotification(String userName, Context fgContext, int notificationClientUid) { final String title = fgContext.getString(R.string.bg_user_sound_notification_title_alarm, userName); final int icon = R.drawable.ic_audio_alarm; - PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND); - PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER); - PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION); + PendingIntent mutePI = createPendingIntent(ACTION_MUTE_SOUND, notificationClientUid); + PendingIntent switchPI = createPendingIntent(ACTION_SWITCH_USER, notificationClientUid); + PendingIntent dismissNotificationPI = createPendingIntent(ACTION_DISMISS_NOTIFICATION, + notificationClientUid); final Notification.Action mute = new Notification.Action.Builder(null, fgContext.getString(R.string.bg_user_sound_notification_button_mute), diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java index 369029adac59..84a5f2b0e8bc 100644 --- a/services/core/java/com/android/server/pm/BroadcastHelper.java +++ b/services/core/java/com/android/server/pm/BroadcastHelper.java @@ -80,11 +80,6 @@ import java.util.function.BiFunction; */ public final class BroadcastHelper { private static final boolean DEBUG_BROADCASTS = false; - /** - * Permissions required in order to receive instant application lifecycle broadcasts. - */ - private static final String[] INSTANT_APP_BROADCAST_PERMISSION = - new String[]{android.Manifest.permission.ACCESS_INSTANT_APPS}; private final UserManagerInternal mUmInternal; private final ActivityManagerInternal mAmInternal; @@ -115,7 +110,7 @@ public final class BroadcastHelper { SparseArray<int[]> broadcastAllowList = new SparseArray<>(); broadcastAllowList.put(userId, visibilityAllowList); broadcastIntent(intent, finishedReceiver, isInstantApp, userId, broadcastAllowList, - filterExtrasForReceiver, bOptions); + filterExtrasForReceiver, bOptions, null /* requiredPermissions */); } void sendPackageBroadcast(final String action, final String pkg, final Bundle extras, @@ -123,7 +118,7 @@ public final class BroadcastHelper { final int[] userIds, int[] instantUserIds, @Nullable SparseArray<int[]> broadcastAllowList, @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, - @Nullable Bundle bOptions) { + @Nullable Bundle bOptions, @Nullable String[] requiredPermissions) { try { final IActivityManager am = ActivityManager.getService(); if (am == null) return; @@ -137,12 +132,12 @@ public final class BroadcastHelper { if (ArrayUtils.isEmpty(instantUserIds)) { doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver, resolvedUserIds, false /* isInstantApp */, broadcastAllowList, - filterExtrasForReceiver, bOptions); + filterExtrasForReceiver, bOptions, requiredPermissions); } else { // send restricted broadcasts for instant apps doSendBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver, - instantUserIds, true /* isInstantApp */, null, - null /* filterExtrasForReceiver */, bOptions); + instantUserIds, true /* isInstantApp */, null /* broadcastAllowList */, + null /* filterExtrasForReceiver */, bOptions, requiredPermissions); } } catch (RemoteException ex) { } @@ -166,7 +161,8 @@ public final class BroadcastHelper { boolean isInstantApp, @Nullable SparseArray<int[]> broadcastAllowList, @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, - @Nullable Bundle bOptions) { + @Nullable Bundle bOptions, + @Nullable String[] requiredPermissions) { for (int userId : userIds) { final Intent intent = new Intent(action, pkg != null ? Uri.fromParts(PACKAGE_SCHEME, pkg, null) : null); @@ -189,17 +185,18 @@ public final class BroadcastHelper { intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT | flags); broadcastIntent(intent, finishedReceiver, isInstantApp, userId, broadcastAllowList, - filterExtrasForReceiver, bOptions); + filterExtrasForReceiver, bOptions, requiredPermissions); } } - private void broadcastIntent(Intent intent, IIntentReceiver finishedReceiver, boolean isInstantApp, int userId, @Nullable SparseArray<int[]> broadcastAllowList, @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver, - @Nullable Bundle bOptions) { - final String[] requiredPermissions = - isInstantApp ? INSTANT_APP_BROADCAST_PERMISSION : null; + @Nullable Bundle bOptions, @Nullable String[] requiredPermissions) { + if (isInstantApp) { + requiredPermissions = ArrayUtils.appendElement(String.class, requiredPermissions, + android.Manifest.permission.ACCESS_INSTANT_APPS); + } if (DEBUG_BROADCASTS) { RuntimeException here = new RuntimeException("here"); here.fillInStackTrace(); @@ -234,7 +231,7 @@ public final class BroadcastHelper { null /* instantUserIds */, null /* broadcastAllowList */, (callingUid, intentExtras) -> filterExtrasChangedPackageList( snapshot, callingUid, intentExtras), - null /* bOptions */); + null /* bOptions */, null /* requiredPermissions */); } /** @@ -294,14 +291,29 @@ public final class BroadcastHelper { return bOptions; } - private void sendPackageChangedBroadcast(@NonNull String packageName, - boolean dontKillApp, - @NonNull ArrayList<String> componentNames, - int packageUid, - @Nullable String reason, - @Nullable int[] userIds, - @Nullable int[] instantUserIds, - @Nullable SparseArray<int[]> broadcastAllowList) { + private void sendPackageChangedBroadcastInternal(@NonNull String packageName, + boolean dontKillApp, + @NonNull ArrayList<String> componentNames, + int packageUid, + @Nullable String reason, + @Nullable int[] userIds, + @Nullable int[] instantUserIds, + @Nullable SparseArray<int[]> broadcastAllowList) { + sendPackageChangedBroadcastWithPermissions(packageName, dontKillApp, componentNames, + packageUid, reason, userIds, instantUserIds, broadcastAllowList, + null /* targetPackageName */, null /* requiredPermissions */); + } + + private void sendPackageChangedBroadcastWithPermissions(@NonNull String packageName, + boolean dontKillApp, + @NonNull ArrayList<String> componentNames, + int packageUid, + @Nullable String reason, + @Nullable int[] userIds, + @Nullable int[] instantUserIds, + @Nullable SparseArray<int[]> broadcastAllowList, + @Nullable String targetPackageName, + @Nullable String[] requiredPermissions) { if (DEBUG_INSTALL) { Log.v(TAG, "Sending package changed: package=" + packageName + " components=" + componentNames); @@ -321,9 +333,10 @@ public final class BroadcastHelper { // little component state change. final int flags = !componentNames.contains(packageName) ? Intent.FLAG_RECEIVER_REGISTERED_ONLY : 0; - sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, null, null, - userIds, instantUserIds, broadcastAllowList, null /* filterExtrasForReceiver */, - null /* bOptions */); + sendPackageBroadcast(Intent.ACTION_PACKAGE_CHANGED, packageName, extras, flags, + targetPackageName, null /* finishedReceiver */, userIds, instantUserIds, + broadcastAllowList, null /* filterExtrasForReceiver */, null /* bOptions */, + requiredPermissions); } static void sendDeviceCustomizationReadyBroadcast() { @@ -680,7 +693,8 @@ public final class BroadcastHelper { sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, packageName, extras, 0, null, null, userIds, instantUserIds, - broadcastAllowlist, null /* filterExtrasForReceiver */, null); + broadcastAllowlist, null /* filterExtrasForReceiver */, null /* bOptions */, + null /* requiredPermissions */); // Send to PermissionController for all new users, even if it may not be running for some // users if (isPrivacySafetyLabelChangeNotificationsEnabled(mContext)) { @@ -688,7 +702,8 @@ public final class BroadcastHelper { packageName, extras, 0, mContext.getPackageManager().getPermissionControllerPackageName(), null, userIds, instantUserIds, - broadcastAllowlist, null /* filterExtrasForReceiver */, null); + broadcastAllowlist, null /* filterExtrasForReceiver */, null /* bOptions */, + null /* requiredPermissions */); } } @@ -719,7 +734,8 @@ public final class BroadcastHelper { int[] userIds, int[] instantUserIds) { sendPackageBroadcast(Intent.ACTION_PACKAGE_FIRST_LAUNCH, pkgName, null, 0, installerPkg, null, userIds, instantUserIds, null /* broadcastAllowList */, - null /* filterExtrasForReceiver */, null); + null /* filterExtrasForReceiver */, null /* bOptions */, + null /* requiredPermissions */); } /** @@ -824,7 +840,7 @@ public final class BroadcastHelper { final int[] instantUserIds = isInstantApp ? new int[] { userId } : EMPTY_INT_ARRAY; final SparseArray<int[]> broadcastAllowList = isInstantApp ? null : snapshot.getVisibilityAllowLists(packageName, userIds); - mHandler.post(() -> sendPackageChangedBroadcast( + mHandler.post(() -> sendPackageChangedBroadcastInternal( packageName, dontKillApp, componentNames, packageUid, reason, userIds, instantUserIds, broadcastAllowList)); mPackageMonitorCallbackHelper.notifyPackageChanged(packageName, dontKillApp, componentNames, @@ -843,7 +859,7 @@ public final class BroadcastHelper { @Nullable Bundle bOptions) { mHandler.post(() -> sendPackageBroadcast(action, pkg, extras, flags, targetPkg, finishedReceiver, userIds, instantUserIds, broadcastAllowList, - null /* filterExtrasForReceiver */, bOptions)); + null /* filterExtrasForReceiver */, bOptions, null /* requiredPermissions */)); if (targetPkg == null) { // For some broadcast action, e.g. ACTION_PACKAGE_ADDED, this method will be called // many times to different targets, e.g. installer app, permission controller, other @@ -1014,7 +1030,7 @@ public final class BroadcastHelper { extras, flags, null /* targetPkg */, null /* finishedReceiver */, new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver, - options)); + options, null /* requiredPermissions */)); notifyPackageMonitor(intent, null /* pkg */, extras, new int[]{userId}, null /* instantUserIds */, null /* broadcastAllowList */, filterExtrasForReceiver); } @@ -1046,9 +1062,12 @@ public final class BroadcastHelper { } else { intentExtras = null; } - doSendBroadcast(action, null, intentExtras, - Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, null, - targetUserIds, false, null, null, null); + doSendBroadcast(action, null /* pkg */, intentExtras, + Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, packageName, + null /* finishedReceiver */, + targetUserIds, false /* isInstantApp */, null /* broadcastAllowList */, + null /* filterExtrasForReceiver */, null /* bOptions */, + null /* requiredPermissions */); } }); } @@ -1077,7 +1096,7 @@ public final class BroadcastHelper { null /* broadcastAllowList */, (callingUid, intentExtras) -> filterExtrasChangedPackageList( snapshot, callingUid, intentExtras), - null /* bOptions */)); + null /* bOptions */, null /* requiredPermissions */)); } void sendResourcesChangedBroadcastAndNotify(@NonNull Computer snapshot, diff --git a/services/core/java/com/android/server/pm/DexOptHelper.java b/services/core/java/com/android/server/pm/DexOptHelper.java index 9e8598a04a98..0b58c759b284 100644 --- a/services/core/java/com/android/server/pm/DexOptHelper.java +++ b/services/core/java/com/android/server/pm/DexOptHelper.java @@ -791,16 +791,6 @@ public final class DexOptHelper { } try { Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "dexopt"); - - // This mirrors logic from commitReconciledScanResultLocked, where the library - // files needed for dexopt are assigned. - PackageSetting realPkgSetting = installRequest.getRealPackageSetting(); - // Unfortunately, the updated system app flag is only tracked on this - // PackageSetting - boolean isUpdatedSystemApp = - installRequest.getScannedPackageSetting().isUpdatedSystemApp(); - realPkgSetting.getPkgState().setUpdatedSystemApp(isUpdatedSystemApp); - DexoptResult dexOptResult = DexOptHelper.dexoptPackageUsingArtService( installRequest, dexoptOptions); installRequest.onDexoptFinished(dexOptResult); diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java index 34d939b07187..f6a808b6c33e 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerService.java +++ b/services/core/java/com/android/server/pm/PackageInstallerService.java @@ -25,12 +25,14 @@ import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_NO_CONNECTIVI import static android.content.pm.PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED; import static android.content.pm.PackageInstaller.UNARCHIVAL_GENERIC_ERROR; import static android.content.pm.PackageInstaller.UNARCHIVAL_OK; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_WARN; import static android.content.pm.PackageManager.DELETE_ARCHIVE; import static android.content.pm.PackageManager.INSTALL_UNARCHIVE_DRAFT; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; import static com.android.server.pm.PackageArchiver.isArchivingEnabled; +import static com.android.server.pm.PackageInstallerSession.isValidVerificationPolicy; import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME; import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; @@ -150,6 +152,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.IntPredicate; import java.util.function.Supplier; @@ -275,6 +278,13 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } }; + /** + * Default verification policy for incoming installation sessions. + * TODO(b/360129657): update the default policy. + */ + private final AtomicInteger mVerificationPolicy = new AtomicInteger( + VERIFICATION_POLICY_BLOCK_FAIL_WARN); + private static final class Lifecycle extends SystemService { private final PackageInstallerService mPackageInstallerService; @@ -1042,7 +1052,7 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements userId, callingUid, installSource, params, createdMillis, 0L, stageDir, stageCid, null, null, false, false, false, false, null, SessionInfo.INVALID_ID, false, false, false, PackageManager.INSTALL_UNKNOWN, "", null, - mVerifierController); + mVerifierController, mVerificationPolicy.get()); synchronized (mSessions) { mSessions.put(sessionId, session); @@ -1866,6 +1876,34 @@ public class PackageInstallerService extends IPackageInstaller.Stub implements } } + @Override + public @PackageInstaller.VerificationPolicy int getVerificationPolicy() { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need the " + + "com.android.permission.VERIFICATION_AGENT permission " + + "to get the verification policy"); + } + return mVerificationPolicy.get(); + } + + @Override + public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.VERIFICATION_AGENT) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("You need the " + + "com.android.permission.VERIFICATION_AGENT permission " + + "to set the verification policy"); + } + if (!isValidVerificationPolicy(policy)) { + return false; + } + if (policy != mVerificationPolicy.get()) { + mVerificationPolicy.set(policy); + } + return true; + } + private static int getSessionCount(SparseArray<PackageInstallerSession> sessions, int installerUid) { int count = 0; diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index 9e0ba8492ab9..04d0d182d3b2 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -21,9 +21,17 @@ import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_INSTA import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_UPDATED_BY_DO; import static android.content.pm.DataLoaderType.INCREMENTAL; import static android.content.pm.DataLoaderType.STREAMING; +import static android.content.pm.PackageInstaller.EXTRA_VERIFICATION_FAILURE_REASON; import static android.content.pm.PackageInstaller.LOCATION_DATA_APP; import static android.content.pm.PackageInstaller.UNARCHIVAL_OK; import static android.content.pm.PackageInstaller.UNARCHIVAL_STATUS_UNSET; +import static android.content.pm.PackageInstaller.VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE; +import static android.content.pm.PackageInstaller.VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED; +import static android.content.pm.PackageInstaller.VERIFICATION_FAILED_REASON_UNKNOWN; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_OPEN; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_WARN; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_NONE; import static android.content.pm.PackageItemInfo.MAX_SAFE_LABEL_LENGTH; import static android.content.pm.PackageManager.INSTALL_FAILED_ABORTED; import static android.content.pm.PackageManager.INSTALL_FAILED_BAD_SIGNATURE; @@ -38,7 +46,7 @@ import static android.content.pm.PackageManager.INSTALL_FAILED_VERIFICATION_FAIL import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_NO_CERTIFICATES; import static android.content.pm.PackageManager.INSTALL_STAGED; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; -import static android.content.pm.verify.pkg.VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN; +import static android.content.pm.verify.pkg.VerificationSession.VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE; import static android.os.Process.INVALID_UID; import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE; import static android.system.OsConstants.O_CREAT; @@ -313,6 +321,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private static final String ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT = "applicationEnabledSettingPersistent"; private static final String ATTR_DOMAIN = "domain"; + private static final String ATTR_VERIFICATION_POLICY = "verificationPolicy"; private static final String PROPERTY_NAME_INHERIT_NATIVE = "pi.inherit_native_on_dont_kill"; private static final int[] EMPTY_CHILD_SESSION_ARRAY = EmptyArray.INT; @@ -410,6 +419,11 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { private final PackageSessionProvider mSessionProvider; private final SilentUpdatePolicy mSilentUpdatePolicy; /** + * The verification policy applied to this session, which might be different from the default + * verification policy used by the system. + */ + private final AtomicInteger mVerificationPolicy; + /** * Note all calls must be done outside {@link #mLock} to prevent lock inversion. */ private final StagingManager mStagingManager; @@ -791,7 +805,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (errorMsg != null) { Slog.e(TAG, "verifySession error: " + errorMsg); setSessionFailed(INSTALL_FAILED_INTERNAL_ERROR, errorMsg); - onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR, errorMsg); + onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR, errorMsg, + /* extras= */ null); return false; } return true; @@ -1167,7 +1182,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Nullable int[] childSessionIds, int parentSessionId, boolean isReady, boolean isFailed, boolean isApplied, int sessionErrorCode, String sessionErrorMessage, DomainSet preVerifiedDomains, - @NonNull VerifierController verifierController) { + @NonNull VerifierController verifierController, + @PackageInstaller.VerificationPolicy int verificationPolicy) { mCallback = callback; mContext = context; mPm = pm; @@ -1177,6 +1193,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { mHandler = new Handler(looper, mHandlerCallback); mStagingManager = stagingManager; mVerifierController = verifierController; + mVerificationPolicy = new AtomicInteger(verificationPolicy); this.sessionId = sessionId; this.userId = userId; @@ -2580,10 +2597,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { dispatchSessionFinished(error, detailMessage, null); } - private void onSessionVerificationFailure(int error, String msg) { + private void onSessionVerificationFailure(int error, String msg, Bundle extras) { Slog.e(TAG, "Failed to verify session " + sessionId); // Dispatch message to remove session from PackageInstallerService. - dispatchSessionFinished(error, msg, null); + dispatchSessionFinished(error, msg, extras); maybeFinishChildSessions(error, msg); } @@ -2856,7 +2873,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String completeMsg = ExceptionUtils.getCompleteMessage(e); final String errorMsg = PackageManager.installStatusToString(e.error, completeMsg); setSessionFailed(e.error, errorMsg); - onSessionVerificationFailure(e.error, errorMsg); + onSessionVerificationFailure(e.error, errorMsg, /* extras= */ null); } if (Flags.verificationService()) { final Supplier<Computer> snapshotSupplier = mPm::snapshotComputer; @@ -2872,11 +2889,12 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { // the installation can proceed. if (!mVerifierController.startVerificationSession(snapshotSupplier, userId, sessionId, getPackageName(), Uri.fromFile(stageDir), signingInfo, - declaredLibraries, /* extensionParams= */ null, + declaredLibraries, mVerificationPolicy.get(), /* extensionParams= */ null, new VerifierCallback(), /* retry= */ false)) { // A verifier is installed but cannot be connected. Installation disallowed. onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR, - "A verifier agent is available on device but cannot be connected."); + "A verifier agent is available on device but cannot be connected.", + /* extras= */ null); } } else { // Verifier is not installed. Let the installation pass for now. @@ -2917,7 +2935,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final String completeMsg = ExceptionUtils.getCompleteMessage(e); final String errorMsg = PackageManager.installStatusToString(e.error, completeMsg); setSessionFailed(e.error, errorMsg); - onSessionVerificationFailure(e.error, errorMsg); + onSessionVerificationFailure(e.error, errorMsg, /* extras= */ null); } } @@ -2926,24 +2944,57 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { */ public class VerifierCallback { /** + * Called by the VerifierController when the verifier requests to get the current + * verification policy for this session. + */ + public @PackageInstaller.VerificationPolicy int getVerificationPolicy() { + return mVerificationPolicy.get(); + } + /** + * Called by the VerifierController when the verifier requests to change the verification + * policy for this session. + */ + public boolean setVerificationPolicy(@PackageInstaller.VerificationPolicy int policy) { + if (!isValidVerificationPolicy(policy)) { + return false; + } + mVerificationPolicy.set(policy); + return true; + } + /** * Called by the VerifierController when the connection has failed. */ public void onConnectionFailed() { - mHandler.post(() -> { - onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE, - "A verifier agent is available on device but cannot be connected."); - }); + // TODO(b/360129657): prompt user on fail warning + handleNonPackageBlockedFailure( + /* onFailWarning= */ PackageInstallerSession.this::resumeVerify, + /* onFailClosed= */ () -> { + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_VERIFICATION_FAILURE_REASON, + VERIFICATION_FAILED_REASON_UNKNOWN); + onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE, + "A verifier agent is available on device but cannot be connected.", + bundle); + + }); } /** * Called by the VerifierController when the verification request has timed out. */ public void onTimeout() { - mHandler.post(() -> { - mVerifierController.notifyVerificationTimeout(sessionId); - onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE, - "Verification timed out; missing a response from the verifier within the" - + " time limit"); - }); + // Always notify the verifier, regardless of the policy. + mVerifierController.notifyVerificationTimeout(sessionId); + // TODO(b/360129657): prompt user on fail warning + handleNonPackageBlockedFailure( + /* onFailWarning= */ PackageInstallerSession.this::resumeVerify, + /* onFailClosed= */ () -> { + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_VERIFICATION_FAILURE_REASON, + VERIFICATION_FAILED_REASON_UNKNOWN); + onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE, + "Verification timed out; missing a response from the verifier" + + " within the time limit", bundle); + }); } /** * Called by the VerifierController when the verification request has received a complete @@ -2953,17 +3004,22 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { @Nullable PersistableBundle extensionResponse) { // TODO: handle extension response mHandler.post(() -> { - if (statusReceived.isVerified()) { + if (statusReceived.isVerified() + || mVerificationPolicy.get() == VERIFICATION_POLICY_NONE) { // Continue with the rest of the verification and installation. resumeVerify(); - } else { - StringBuilder sb = new StringBuilder("Verifier rejected the installation"); - if (!TextUtils.isEmpty(statusReceived.getFailureMessage())) { - sb.append(" with message: ").append(statusReceived.getFailureMessage()); - } - onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE, - sb.toString()); + return; + } + // Package is blocked. + StringBuilder sb = new StringBuilder("Verifier rejected the installation"); + if (!TextUtils.isEmpty(statusReceived.getFailureMessage())) { + sb.append(" with message: ").append(statusReceived.getFailureMessage()); } + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_VERIFICATION_FAILURE_REASON, + VERIFICATION_FAILED_REASON_PACKAGE_BLOCKED); + onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE, + sb.toString(), bundle); }); } /** @@ -2971,16 +3027,51 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * response. */ public void onVerificationIncompleteReceived(int incompleteReason) { - mHandler.post(() -> { - if (incompleteReason == VERIFICATION_INCOMPLETE_UNKNOWN) { - // TODO: change this to a user confirmation and handle other incomplete reasons - onSessionVerificationFailure(INSTALL_FAILED_INTERNAL_ERROR, - "Verification cannot be completed for unknown reasons."); + // TODO(b/360129657): prompt user on fail warning + handleNonPackageBlockedFailure( + /* onFailWarning= */ PackageInstallerSession.this::resumeVerify, + /* onFailClosed= */ () -> { + final int failureReason; + StringBuilder sb = new StringBuilder( + "Verification cannot be completed because of "); + if (incompleteReason == VERIFICATION_INCOMPLETE_NETWORK_UNAVAILABLE) { + failureReason = VERIFICATION_FAILED_REASON_NETWORK_UNAVAILABLE; + sb.append("unavailable network."); + } else { + failureReason = VERIFICATION_FAILED_REASON_UNKNOWN; + sb.append("unknown reasons."); + } + Bundle bundle = new Bundle(); + bundle.putInt(EXTRA_VERIFICATION_FAILURE_REASON, failureReason); + onSessionVerificationFailure(INSTALL_FAILED_VERIFICATION_FAILURE, + sb.toString(), bundle); + }); + } + + private void handleNonPackageBlockedFailure(Runnable onFailWarning, Runnable onFailClosed) { + final Runnable r = switch (mVerificationPolicy.get()) { + case VERIFICATION_POLICY_NONE, VERIFICATION_POLICY_BLOCK_FAIL_OPEN -> + PackageInstallerSession.this::resumeVerify; + case VERIFICATION_POLICY_BLOCK_FAIL_WARN -> onFailWarning; + case VERIFICATION_POLICY_BLOCK_FAIL_CLOSED -> onFailClosed; + default -> { + Log.wtf(TAG, "Unknown verification policy: " + mVerificationPolicy.get()); + yield onFailClosed; } - }); + }; + mHandler.post(r); } } + /** + * Returns whether a policy is a valid verification policy. + */ + public static boolean isValidVerificationPolicy( + @PackageInstaller.VerificationPolicy int policy) { + return policy >= VERIFICATION_POLICY_NONE + && policy <= VERIFICATION_POLICY_BLOCK_FAIL_CLOSED; + } + private IntentSender getRemoteStatusReceiver() { synchronized (mLock) { return mRemoteStatusReceiver; @@ -3156,7 +3247,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (error == INSTALL_SUCCEEDED) { onVerificationComplete(); } else { - onSessionVerificationFailure(error, msg); + onSessionVerificationFailure(error, msg, /* extras= */ null); } }); }); @@ -5328,6 +5419,14 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { } } + /** + * @return the current policy for the verification request associated with this session. + */ + @VisibleForTesting + public @PackageInstaller.VerificationPolicy int getVerificationPolicy() { + assertCallerIsOwnerOrRoot(); + return mVerificationPolicy.get(); + } void setSessionReady() { synchronized (mLock) { @@ -5631,6 +5730,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!ArrayUtils.isEmpty(warnings)) { fillIn.putStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS, warnings); } + if (extras.containsKey(EXTRA_VERIFICATION_FAILURE_REASON)) { + fillIn.putExtra(EXTRA_VERIFICATION_FAILURE_REASON, + extras.getInt(EXTRA_VERIFICATION_FAILURE_REASON)); + } } try { final BroadcastOptions options = BroadcastOptions.makeBasic(); @@ -5786,6 +5889,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { out.attributeInt(null, ATTR_INSTALL_REASON, params.installReason); writeBooleanAttribute(out, ATTR_APPLICATION_ENABLED_SETTING_PERSISTENT, params.applicationEnabledSettingPersistent); + out.attributeInt(null, ATTR_VERIFICATION_POLICY, mVerificationPolicy.get()); final boolean isDataLoader = params.dataLoaderParams != null; writeBooleanAttribute(out, ATTR_IS_DATALOADER, isDataLoader); @@ -5936,6 +6040,8 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { final boolean sealed = in.getAttributeBoolean(null, ATTR_SEALED, false); final int parentSessionId = in.getAttributeInt(null, ATTR_PARENT_SESSION_ID, SessionInfo.INVALID_ID); + final int verificationPolicy = in.getAttributeInt(null, ATTR_VERIFICATION_POLICY, + VERIFICATION_POLICY_NONE); final SessionParams params = new SessionParams( SessionParams.MODE_INVALID); @@ -6110,6 +6216,7 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { installerUid, installSource, params, createdMillis, committedMillis, stageDir, stageCid, fileArray, checksumsMap, prepared, committed, destroyed, sealed, childSessionIdsArray, parentSessionId, isReady, isFailed, isApplied, - sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController); + sessionErrorCode, sessionErrorMessage, preVerifiedDomains, verifierController, + verificationPolicy); } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 455776993c56..d78f12217271 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -4709,10 +4709,11 @@ public class PackageManagerService implements PackageSender, TestUtilityService extras.putLong(Intent.EXTRA_TIME, SystemClock.elapsedRealtime()); mHandler.post(() -> { mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_UNSTOPPED, - packageName, extras, - Intent.FLAG_RECEIVER_REGISTERED_ONLY, null, null, - userIds, null, broadcastAllowList, null, - null); + packageName, extras, Intent.FLAG_RECEIVER_REGISTERED_ONLY, + null /* targetPkg */, null /* finishedReceiver */, userIds, + null /* instantUserIds */, broadcastAllowList, + null /* filterExtrasForReceiver */, null /* bOptions */, + null /* requiredPermissions */); }); mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_UNSTOPPED, packageName, extras, userIds, null /* instantUserIds */, @@ -7169,17 +7170,17 @@ public class PackageManagerService implements PackageSender, TestUtilityService // Sent async using the PM handler, to maintain ordering with PACKAGE_UNSTOPPED mHandler.post(() -> { mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED, - packageName, extras, - flags, null, null, - userIds, null, broadcastAllowList, null, - null); + packageName, extras, flags, null /* targetPkg */, + null /* finishedReceiver */, userIds, null /* instantUserIds */, + broadcastAllowList, null /* filterExtrasForReceiver */, + null /* bOptions */, null /* requiredPermissions */); }); } else { mBroadcastHelper.sendPackageBroadcast(Intent.ACTION_PACKAGE_RESTARTED, - packageName, extras, - flags, null, null, - userIds, null, broadcastAllowList, null, - null); + packageName, extras, flags, null /* targetPkg */, + null /* finishedReceiver */, userIds, null /* instantUserIds */, + broadcastAllowList, null /* filterExtrasForReceiver */, null /* bOptions */, + null /* requiredPermissions */); } mPackageMonitorCallbackHelper.notifyPackageMonitor(Intent.ACTION_PACKAGE_RESTARTED, packageName, extras, userIds, null /* instantUserIds */, diff --git a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java index 7eac940933c2..b7cc7ccead89 100644 --- a/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java +++ b/services/core/java/com/android/server/pm/verify/pkg/VerifierController.java @@ -30,6 +30,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.SharedLibraryInfo; @@ -94,9 +95,22 @@ public class VerifierController { // Max duration allowed to wait for a verifier to respond to a verification request. private static final long DEFAULT_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS = TimeUnit.MINUTES.toMillis(10); + /** + * Configurable maximum amount of time in milliseconds for the system to wait from the moment + * when the installation session requires a verification, till when the request is delivered to + * the verifier, pending the connection to be established. If the request has not been delivered + * to the verifier within this amount of time, e.g., because the verifier has crashed or ANR'd, + * the controller then sends a failure status back to the installation session. + * Flag type: {@code long} + * Namespace: NAMESPACE_PACKAGE_MANAGER_SERVICE + */ + private static final String PROPERTY_VERIFIER_CONNECTION_TIMEOUT_MILLIS = + "verifier_connection_timeout_millis"; // The maximum amount of time to wait from the moment when the session requires a verification, // till when the request is delivered to the verifier, pending the connection to be established. - private static final long CONNECTION_TIMEOUT_SECONDS = 10; + private static final long DEFAULT_VERIFIER_CONNECTION_TIMEOUT_MILLIS = + TimeUnit.SECONDS.toMillis(10); + // The maximum amount of time to wait before the system unbinds from the verifier. private static final long UNBIND_TIMEOUT_MILLIS = TimeUnit.HOURS.toMillis(6); @@ -271,6 +285,7 @@ public class VerifierController { int installationSessionId, String packageName, Uri stagedPackageUri, SigningInfo signingInfo, List<SharedLibraryInfo> declaredLibraries, + @PackageInstaller.VerificationPolicy int verificationPolicy, PersistableBundle extensionParams, PackageInstallerSession.VerifierCallback callback, boolean retry) { // Try connecting to the verifier if not already connected @@ -292,7 +307,7 @@ public class VerifierController { /* id= */ verificationId, /* installSessionId= */ installationSessionId, packageName, stagedPackageUri, signingInfo, declaredLibraries, extensionParams, - new VerificationSessionInterface(), + verificationPolicy, new VerificationSessionInterface(callback), new VerificationSessionCallback(callback)); AndroidFuture<Void> unusedFuture = mRemoteService.post(service -> { if (!retry) { @@ -306,7 +321,8 @@ public class VerifierController { } service.onVerificationRetry(session); } - }).orTimeout(CONNECTION_TIMEOUT_SECONDS, TimeUnit.SECONDS).whenComplete((res, err) -> { + }).orTimeout(mInjector.getVerifierConnectionTimeoutMillis(), TimeUnit.MILLISECONDS) + .whenComplete((res, err) -> { if (err != null) { Slog.e(TAG, "Error notifying verification request for session " + verificationId, err); @@ -407,6 +423,12 @@ public class VerifierController { // This class handles requests from the remote verifier private class VerificationSessionInterface extends IVerificationSessionInterface.Stub { + private final PackageInstallerSession.VerifierCallback mCallback; + + VerificationSessionInterface(PackageInstallerSession.VerifierCallback callback) { + mCallback = callback; + } + @Override public long getTimeoutTime(int verificationId) { checkCallerPermission(); @@ -432,6 +454,20 @@ public class VerifierController { return tracker.extendTimeRemaining(additionalMs); } } + + @Override + public boolean setVerificationPolicy(int verificationId, + @PackageInstaller.VerificationPolicy int policy) { + checkCallerPermission(); + synchronized (mVerificationStatus) { + final VerificationStatusTracker tracker = mVerificationStatus.get(verificationId); + if (tracker == null) { + throw new IllegalStateException("Verification session " + verificationId + + " doesn't exist or has finished"); + } + } + return mCallback.setVerificationPolicy(policy); + } } private class VerificationSessionCallback extends IVerificationSessionCallback.Stub { @@ -451,8 +487,8 @@ public class VerifierController { throw new IllegalStateException("Verification session " + id + " doesn't exist or has finished"); } - mCallback.onVerificationIncompleteReceived(reason); } + mCallback.onVerificationIncompleteReceived(reason); // Remove status tracking and stop the timeout countdown removeStatusTracker(id); } @@ -630,6 +666,14 @@ public class VerifierController { return getMaxVerificationExtendedTimeoutMillisFromDeviceConfig(); } + /** + * This is added so that we can mock the maximum connection timeout duration without + * calling into DeviceConfig. + */ + public long getVerifierConnectionTimeoutMillis() { + return getVerifierConnectionTimeoutMillisFromDeviceConfig(); + } + private static long getVerificationRequestTimeoutMillisFromDeviceConfig() { return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE, PROPERTY_VERIFICATION_REQUEST_TIMEOUT_MILLIS, @@ -641,5 +685,11 @@ public class VerifierController { PROPERTY_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS, DEFAULT_MAX_VERIFICATION_REQUEST_EXTENDED_TIMEOUT_MILLIS); } + + private static long getVerifierConnectionTimeoutMillisFromDeviceConfig() { + return DeviceConfig.getLong(NAMESPACE_PACKAGE_MANAGER_SERVICE, + PROPERTY_VERIFIER_CONNECTION_TIMEOUT_MILLIS, + DEFAULT_VERIFIER_CONNECTION_TIMEOUT_MILLIS); + } } } diff --git a/services/core/java/com/android/server/power/Notifier.java b/services/core/java/com/android/server/power/Notifier.java index eb62b5631c43..8ba56c5320f2 100644 --- a/services/core/java/com/android/server/power/Notifier.java +++ b/services/core/java/com/android/server/power/Notifier.java @@ -771,6 +771,10 @@ public class Notifier { public void onGroupRemoved(int groupId) { mInteractivityByGroupId.remove(groupId); mWakefulnessSessionObserver.removePowerGroup(groupId); + if (mFlags.isPerDisplayWakeByTouchEnabled()) { + resetDisplayInteractivities(); + mInputManagerInternal.setDisplayInteractivities(mDisplayInteractivities); + } } /** diff --git a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java index 8f603fc34b32..075a31f3b24c 100644 --- a/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java +++ b/services/core/java/com/android/server/rotationresolver/RotationResolverManagerService.java @@ -191,8 +191,7 @@ public class RotationResolverManagerService extends SensorPrivacyManager.Sensors.CAMERA); if (mIsServiceEnabled && isCameraAvailable) { final RotationResolverManagerPerUserService service = - getServiceForUserLocked( - UserHandle.getCallingUserId()); + getServiceForUserLocked(UserHandle.USER_CURRENT); final RotationResolutionRequest request; if (packageName == null) { request = new RotationResolutionRequest(/* packageName */ "", diff --git a/services/core/java/com/android/server/security/forensic/ForensicService.java b/services/core/java/com/android/server/security/forensic/ForensicService.java new file mode 100644 index 000000000000..07639d1a3945 --- /dev/null +++ b/services/core/java/com/android/server/security/forensic/ForensicService.java @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2024 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.security.forensic; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.security.forensic.IForensicService; +import android.security.forensic.IForensicServiceCommandCallback; +import android.security.forensic.IForensicServiceStateCallback; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.ServiceThread; +import com.android.server.SystemService; + +import java.util.ArrayList; + +/** + * @hide + */ +public class ForensicService extends SystemService { + private static final String TAG = "ForensicService"; + + private static final int MSG_MONITOR_STATE = 0; + private static final int MSG_MAKE_VISIBLE = 1; + private static final int MSG_MAKE_INVISIBLE = 2; + private static final int MSG_ENABLE = 3; + private static final int MSG_DISABLE = 4; + private static final int MSG_BACKUP = 5; + + private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN; + private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE; + private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE; + private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED; + + private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN; + private static final int ERROR_PERMISSION_DENIED = + IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED; + private static final int ERROR_INVALID_STATE_TRANSITION = + IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION; + private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE; + private static final int ERROR_DATA_SOURCE_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE; + + private final Context mContext; + private final Handler mHandler; + private final BinderService mBinderService; + + private final ArrayList<IForensicServiceStateCallback> mStateMonitors = new ArrayList<>(); + private volatile int mState = STATE_INVISIBLE; + + public ForensicService(@NonNull Context context) { + this(new InjectorImpl(context)); + } + + @VisibleForTesting + ForensicService(@NonNull Injector injector) { + super(injector.getContext()); + mContext = injector.getContext(); + mHandler = new EventHandler(injector.getLooper(), this); + mBinderService = new BinderService(this); + } + + @VisibleForTesting + protected void setState(int state) { + mState = state; + } + + private static final class BinderService extends IForensicService.Stub { + final ForensicService mService; + + BinderService(ForensicService service) { + mService = service; + } + + @Override + public void monitorState(IForensicServiceStateCallback callback) { + mService.mHandler.obtainMessage(MSG_MONITOR_STATE, callback).sendToTarget(); + } + + @Override + public void makeVisible(IForensicServiceCommandCallback callback) { + mService.mHandler.obtainMessage(MSG_MAKE_VISIBLE, callback).sendToTarget(); + } + + @Override + public void makeInvisible(IForensicServiceCommandCallback callback) { + mService.mHandler.obtainMessage(MSG_MAKE_INVISIBLE, callback).sendToTarget(); + } + + @Override + public void enable(IForensicServiceCommandCallback callback) { + mService.mHandler.obtainMessage(MSG_ENABLE, callback).sendToTarget(); + } + + @Override + public void disable(IForensicServiceCommandCallback callback) { + mService.mHandler.obtainMessage(MSG_DISABLE, callback).sendToTarget(); + } + } + + private static class EventHandler extends Handler { + private final ForensicService mService; + + EventHandler(Looper looper, ForensicService service) { + super(looper); + mService = service; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_MONITOR_STATE: + try { + mService.monitorState( + (IForensicServiceStateCallback) msg.obj); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + break; + case MSG_MAKE_VISIBLE: + try { + mService.makeVisible((IForensicServiceCommandCallback) msg.obj); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + break; + case MSG_MAKE_INVISIBLE: + try { + mService.makeInvisible((IForensicServiceCommandCallback) msg.obj); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + break; + case MSG_ENABLE: + try { + mService.enable((IForensicServiceCommandCallback) msg.obj); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + break; + case MSG_DISABLE: + try { + mService.disable((IForensicServiceCommandCallback) msg.obj); + } catch (RemoteException e) { + Slog.e(TAG, "RemoteException", e); + } + break; + default: + Slog.w(TAG, "Unknown message: " + msg.what); + } + } + } + + private void monitorState(IForensicServiceStateCallback callback) throws RemoteException { + for (int i = 0; i < mStateMonitors.size(); i++) { + if (mStateMonitors.get(i).asBinder() == callback.asBinder()) { + return; + } + } + mStateMonitors.add(callback); + callback.onStateChange(mState); + } + + private void notifyStateMonitors() throws RemoteException { + for (int i = 0; i < mStateMonitors.size(); i++) { + mStateMonitors.get(i).onStateChange(mState); + } + } + + private void makeVisible(IForensicServiceCommandCallback callback) throws RemoteException { + switch (mState) { + case STATE_INVISIBLE: + mState = STATE_VISIBLE; + notifyStateMonitors(); + callback.onSuccess(); + break; + case STATE_VISIBLE: + callback.onSuccess(); + break; + default: + callback.onFailure(ERROR_INVALID_STATE_TRANSITION); + } + } + + private void makeInvisible(IForensicServiceCommandCallback callback) throws RemoteException { + switch (mState) { + case STATE_VISIBLE: + case STATE_ENABLED: + mState = STATE_INVISIBLE; + notifyStateMonitors(); + callback.onSuccess(); + break; + case STATE_INVISIBLE: + callback.onSuccess(); + break; + default: + callback.onFailure(ERROR_INVALID_STATE_TRANSITION); + } + } + + private void enable(IForensicServiceCommandCallback callback) throws RemoteException { + switch (mState) { + case STATE_VISIBLE: + mState = STATE_ENABLED; + notifyStateMonitors(); + callback.onSuccess(); + break; + case STATE_ENABLED: + callback.onSuccess(); + break; + default: + callback.onFailure(ERROR_INVALID_STATE_TRANSITION); + } + } + + private void disable(IForensicServiceCommandCallback callback) throws RemoteException { + switch (mState) { + case STATE_ENABLED: + mState = STATE_VISIBLE; + notifyStateMonitors(); + callback.onSuccess(); + break; + case STATE_VISIBLE: + callback.onSuccess(); + break; + default: + callback.onFailure(ERROR_INVALID_STATE_TRANSITION); + } + } + + @Override + public void onStart() { + try { + publishBinderService(Context.FORENSIC_SERVICE, mBinderService); + } catch (Throwable t) { + Slog.e(TAG, "Could not start the ForensicService.", t); + } + } + + @VisibleForTesting + IForensicService getBinderService() { + return mBinderService; + } + + interface Injector { + Context getContext(); + + Looper getLooper(); + } + + private static final class InjectorImpl implements Injector { + private final Context mContext; + + InjectorImpl(Context context) { + mContext = context; + } + + @Override + public Context getContext() { + return mContext; + } + + + @Override + public Looper getLooper() { + ServiceThread serviceThread = + new ServiceThread( + TAG, android.os.Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); + serviceThread.start(); + return serviceThread.getLooper(); + } + } +} + diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java index 91a17a9e1c31..4589d26261dc 100644 --- a/services/core/java/com/android/server/tv/TvInputManagerService.java +++ b/services/core/java/com/android/server/tv/TvInputManagerService.java @@ -381,10 +381,12 @@ public final class TvInputManagerService extends SystemService { // service to populate the hardware list. serviceState = new ServiceState(component, userId); userState.serviceStateMap.put(component, serviceState); - updateServiceConnectionLocked(component, userId); } else { inputList.addAll(serviceState.hardwareInputMap.values()); } + if (serviceState.needInit) { + updateServiceConnectionLocked(component, userId); + } } else { try { TvInputInfo info = new TvInputInfo.Builder(mContext, ri).build(); @@ -489,6 +491,27 @@ public final class TvInputManagerService extends SystemService { } } + @GuardedBy("mLock") + private void cleanUpHdmiDevices(int userId) { + if (DEBUG) { + Slog.d(TAG, "cleanUpHdmiDevices: user " + userId); + } + UserState userState = getOrCreateUserStateLocked(userId); + for (ServiceState serviceState : userState.serviceStateMap.values()) { + for (HdmiDeviceInfo device : mTvInputHardwareManager.getHdmiDeviceList()) { + try { + if (serviceState.service != null) { + serviceState.service.notifyHdmiDeviceRemoved(device); + } else { + serviceState.hdmiDeviceRemovedBuffer.add(device); + } + } catch (RemoteException e) { + Slog.e(TAG, "error in notifyHdmiDeviceRemoved", e); + } + } + } + } + private void startUser(int userId) { synchronized (mLock) { if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) { @@ -500,9 +523,13 @@ public final class TvInputManagerService extends SystemService { if (userInfo.isProfile() && parentInfo != null && parentInfo.id == mCurrentUserId) { - // only the children of the current user can be started in background + int prevUserId = mCurrentUserId; mCurrentUserId = userId; - startProfileLocked(userId); + // only the children of the current user can be started in background + releaseSessionOfUserLocked(prevUserId); + cleanUpHdmiDevices(prevUserId); + unbindServiceOfUserLocked(prevUserId); + startProfileLocked(mCurrentUserId); } } } @@ -515,6 +542,7 @@ public final class TvInputManagerService extends SystemService { } releaseSessionOfUserLocked(userId); + cleanUpHdmiDevices(userId); unbindServiceOfUserLocked(userId); mRunningProfiles.remove(userId); } @@ -543,15 +571,19 @@ public final class TvInputManagerService extends SystemService { unbindServiceOfUserLocked(runningId); } mRunningProfiles.clear(); - releaseSessionOfUserLocked(mCurrentUserId); - unbindServiceOfUserLocked(mCurrentUserId); + int prevUserId = mCurrentUserId; mCurrentUserId = userId; - buildTvInputListLocked(userId, null); - buildTvContentRatingSystemListLocked(userId); + + releaseSessionOfUserLocked(prevUserId); + cleanUpHdmiDevices(prevUserId); + unbindServiceOfUserLocked(prevUserId); + + buildTvInputListLocked(mCurrentUserId, null); + buildTvContentRatingSystemListLocked(mCurrentUserId); mMessageHandler .obtainMessage(MessageHandler.MSG_SWITCH_CONTENT_RESOLVER, - getContentResolverForUser(userId)) + getContentResolverForUser(mCurrentUserId)) .sendToTarget(); } } @@ -590,6 +622,9 @@ public final class TvInputManagerService extends SystemService { @GuardedBy("mLock") private void unbindServiceOfUserLocked(int userId) { + if (DEBUG) { + Slog.d(TAG, "unbindServiceOfUserLocked: user " + userId); + } UserState userState = getUserStateLocked(userId); if (userState == null) { return; @@ -600,7 +635,12 @@ public final class TvInputManagerService extends SystemService { ServiceState serviceState = userState.serviceStateMap.get(component); if (serviceState != null && serviceState.sessionTokens.isEmpty()) { unbindService(serviceState); - it.remove(); + if (!serviceState.isHardware) { + it.remove(); + } else { + serviceState.hardwareInputMap.clear(); + serviceState.needInit = true; + } } } } @@ -774,7 +814,7 @@ public final class TvInputManagerService extends SystemService { boolean shouldBind; if (userId == mCurrentUserId || mRunningProfiles.contains(userId)) { shouldBind = !serviceState.sessionTokens.isEmpty() - || (serviceState.isHardware && serviceState.neverConnected); + || (serviceState.isHardware && serviceState.needInit); } else { // For a non-current user, // if sessionTokens is not empty, it contains recording sessions only @@ -3404,13 +3444,13 @@ public final class TvInputManagerService extends SystemService { private ServiceCallback callback; private boolean bound; private boolean reconnecting; - private boolean neverConnected; + private boolean needInit; private ServiceState(ComponentName component, int userId) { this.component = component; this.connection = new InputServiceConnection(component, userId); this.isHardware = hasHardwarePermission(mContext.getPackageManager(), component); - this.neverConnected = true; + this.needInit = true; } } @@ -3618,11 +3658,9 @@ public final class TvInputManagerService extends SystemService { } ComponentName component = mTvInputHardwareManager.getInputMap().get(inputId).getComponent(); ServiceState serviceState = getServiceStateLocked(component, userId); - boolean removed = serviceState.hardwareInputMap.remove(inputId) != null; - if (removed) { - buildTvInputListLocked(userId, null); - mTvInputHardwareManager.removeHardwareInput(inputId); - } + serviceState.hardwareInputMap.remove(inputId); + buildTvInputListLocked(userId, null); + mTvInputHardwareManager.removeHardwareInput(inputId); } private final class InputServiceConnection implements ServiceConnection { @@ -3648,7 +3686,7 @@ public final class TvInputManagerService extends SystemService { } ServiceState serviceState = userState.serviceStateMap.get(mComponent); serviceState.service = ITvInputService.Stub.asInterface(service); - serviceState.neverConnected = false; + serviceState.needInit = false; // Register a callback, if we need to. if (serviceState.isHardware && serviceState.callback == null) { @@ -3841,9 +3879,12 @@ public final class TvInputManagerService extends SystemService { final long identity = Binder.clearCallingIdentity(); try { synchronized (mLock) { - Slog.d(TAG, "ServiceCallback: removeHardwareInput, inputId: " + inputId + - " by " + mComponent + ", userId: " + mUserId); - removeHardwareInputLocked(inputId, mUserId); + if (mUserId == mCurrentUserId) { + Slog.d(TAG, + "ServiceCallback: removeHardwareInput, inputId: " + inputId + " by " + + mComponent + ", userId: " + mUserId); + removeHardwareInputLocked(inputId, mUserId); + } } } finally { Binder.restoreCallingIdentity(identity); @@ -4578,6 +4619,11 @@ public final class TvInputManagerService extends SystemService { private final class HardwareListener implements TvInputHardwareManager.Listener { @Override public void onStateChanged(String inputId, int state) { + if (DEBUG) { + Slog.d(TAG, + "onStateChanged: inputId " + (inputId != null ? inputId : "null") + + ", state " + state); + } synchronized (mLock) { setStateLocked(inputId, state, mCurrentUserId); } @@ -4585,6 +4631,11 @@ public final class TvInputManagerService extends SystemService { @Override public void onHardwareDeviceAdded(TvInputHardwareInfo info) { + if (DEBUG) { + Slog.d(TAG, + "onHardwareDeviceAdded: TvInputHardwareInfo " + + (info != null ? info.toString() : "null")); + } synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. @@ -4607,6 +4658,11 @@ public final class TvInputManagerService extends SystemService { @Override public void onHardwareDeviceRemoved(TvInputHardwareInfo info) { + if (DEBUG) { + Slog.d(TAG, + "onHardwareDeviceRemoved: TvInputHardwareInfo " + + (info != null ? info.toString() : "null")); + } synchronized (mLock) { String relatedInputId = mTvInputHardwareManager.getHardwareInputIdMap().get(info.getDeviceId()); @@ -4634,6 +4690,11 @@ public final class TvInputManagerService extends SystemService { @Override public void onHdmiDeviceAdded(HdmiDeviceInfo deviceInfo) { + if (DEBUG) { + Slog.d(TAG, + "onHdmiDeviceAdded: HdmiDeviceInfo " + + (deviceInfo != null ? deviceInfo.toString() : "null")); + } synchronized (mLock) { UserState userState = getOrCreateUserStateLocked(mCurrentUserId); // Broadcast the event to all hardware inputs. @@ -4656,6 +4717,11 @@ public final class TvInputManagerService extends SystemService { @Override public void onHdmiDeviceRemoved(HdmiDeviceInfo deviceInfo) { + if (DEBUG) { + Slog.d(TAG, + "onHdmiDeviceRemoved: HdmiDeviceInfo " + + (deviceInfo != null ? deviceInfo.toString() : "null")); + } synchronized (mLock) { String relatedInputId = mTvInputHardwareManager.getHdmiInputIdMap().get(deviceInfo.getId()); @@ -4683,6 +4749,12 @@ public final class TvInputManagerService extends SystemService { @Override public void onHdmiDeviceUpdated(String inputId, HdmiDeviceInfo deviceInfo) { + if (DEBUG) { + Slog.d(TAG, + "onHdmiDeviceUpdated: inputId " + (inputId != null ? inputId : "null") + + ", deviceInfo: " + + (deviceInfo != null ? deviceInfo.toString() : "null")); + } synchronized (mLock) { Integer state; switch (deviceInfo.getDevicePowerStatus()) { diff --git a/services/core/java/com/android/server/uri/NeededUriGrants.java b/services/core/java/com/android/server/uri/NeededUriGrants.java index 8c8f55304fbb..2fe61e00c97e 100644 --- a/services/core/java/com/android/server/uri/NeededUriGrants.java +++ b/services/core/java/com/android/server/uri/NeededUriGrants.java @@ -17,10 +17,13 @@ package com.android.server.uri; import android.util.ArraySet; +import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.server.am.NeededUriGrantsProto; +import java.util.Objects; + /** List of {@link GrantUri} a process needs. */ public class NeededUriGrants { final String targetPkg; @@ -35,6 +38,20 @@ public class NeededUriGrants { this.uris = new ArraySet<>(); } + public void merge(NeededUriGrants other) { + if (other == null) return; + if (!Objects.equals(this.targetPkg, other.targetPkg) + || this.targetUid != other.targetUid || this.flags != other.flags) { + Slog.wtf("NeededUriGrants", + "The other NeededUriGrants does not share the same targetUid, targetPkg or " + + "flags. It cannot be merged into this NeededUriGrants. This " + + "NeededUriGrants: " + this.toStringWithoutUri() + + ". Other NeededUriGrants: " + other.toStringWithoutUri()); + } else { + this.uris.addAll(other.uris); + } + } + public void dumpDebug(ProtoOutputStream proto, long fieldId) { long token = proto.start(fieldId); proto.write(NeededUriGrantsProto.TARGET_PACKAGE, targetPkg); @@ -47,4 +64,12 @@ public class NeededUriGrants { } proto.end(token); } + + public String toStringWithoutUri() { + return "NeededUriGrants{" + + "targetPkg='" + targetPkg + '\'' + + ", targetUid=" + targetUid + + ", flags=" + flags + + '}'; + } } diff --git a/services/core/java/com/android/server/vibrator/HalVibration.java b/services/core/java/com/android/server/vibrator/HalVibration.java index fbcc856d0974..d192e64c897f 100644 --- a/services/core/java/com/android/server/vibrator/HalVibration.java +++ b/services/core/java/com/android/server/vibrator/HalVibration.java @@ -21,6 +21,8 @@ import android.annotation.Nullable; import android.os.CombinedVibration; 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.util.SparseArray; @@ -145,19 +147,30 @@ final class HalVibration extends Vibration { originalEffect, mScaleLevel, mAdaptiveScale); } - /** - * Returns true if this vibration can pipeline with the specified one. - * - * <p>Note that currently, repeating vibrations can't pipeline with following vibrations, - * because the cancel() call to stop the repetition will cancel a pending vibration too. This - * can be changed if we have a use-case to reason around behavior for. It may also be nice to - * pipeline very short vibrations together, regardless of the flag. - */ - public boolean canPipelineWith(HalVibration vib) { - return callerInfo.uid == vib.callerInfo.uid && callerInfo.attrs.isFlagSet( - VibrationAttributes.FLAG_PIPELINED_EFFECT) - && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) - && (mOriginalEffect.getDuration() != Long.MAX_VALUE); + /** Returns true if this vibration can pipeline with the specified one. */ + public boolean canPipelineWith(HalVibration vib, + @Nullable SparseArray<VibratorInfo> vibratorInfos, int durationThresholdMs) { + long effectDuration = Flags.vibrationPipelineEnabled() && (vibratorInfos != null) + ? mEffectToPlay.getDuration(vibratorInfos) + : mEffectToPlay.getDuration(); + if (effectDuration == Long.MAX_VALUE) { + // Repeating vibrations can't pipeline with following vibrations, because the cancel() + // call to stop the repetition will cancel a pending vibration too. This can be changed + // if we have a use-case, requiring changes to how pipelined vibrations are cancelled. + return false; + } + if (Flags.vibrationPipelineEnabled() + && (effectDuration > 0) && (effectDuration < durationThresholdMs)) { + // Duration is known and it's less than the pipeline threshold, so allow it. + // No need to check UID, as we want to avoid cancelling any short effect and let the + // vibrator hardware gracefully finish the vibration. + return true; + } + // Check the same app is requesting multiple vibrations with the pipeline flag, + // independently of the effect durations. + return callerInfo.uid == vib.callerInfo.uid + && callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT) + && vib.callerInfo.attrs.isFlagSet(VibrationAttributes.FLAG_PIPELINED_EFFECT); } private void fillFallbacksForEffect(CombinedVibration effect, diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java index 9b7bdece69f9..7d5d34dbf7ab 100644 --- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java +++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java @@ -168,12 +168,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { @VisibleForTesting final VibrationSettings mVibrationSettings; + private final VibrationConfig mVibrationConfig; private final VibrationScaler mVibrationScaler; private final VibratorControlService mVibratorControlService; private final InputDeviceDelegate mInputDeviceDelegate; private final DeviceAdapter mDeviceAdapter; @GuardedBy("mLock") + @Nullable private SparseArray<VibratorInfo> mVibratorInfos; + @GuardedBy("mLock") @Nullable private VibratorInfo mCombinedVibratorInfo; @GuardedBy("mLock") @Nullable private HapticFeedbackVibrationProvider mHapticFeedbackVibrationProvider; @@ -247,9 +250,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mHandler = injector.createHandler(Looper.myLooper()); mFrameworkStatsLogger = injector.getFrameworkStatsLogger(mHandler); - VibrationConfig vibrationConfig = new VibrationConfig(context.getResources()); - mVibrationSettings = new VibrationSettings(mContext, mHandler, vibrationConfig); - mVibrationScaler = new VibrationScaler(vibrationConfig, mVibrationSettings); + mVibrationConfig = new VibrationConfig(context.getResources()); + mVibrationSettings = new VibrationSettings(mContext, mHandler, mVibrationConfig); + mVibrationScaler = new VibrationScaler(mVibrationConfig, mVibrationSettings); mVibratorControlService = new VibratorControlService(mContext, injector.createVibratorControllerHolder(), mVibrationScaler, mVibrationSettings, mFrameworkStatsLogger, mLock); @@ -295,7 +298,9 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibratorIds = vibratorIds; mVibrators = new SparseArray<>(mVibratorIds.length); for (int vibratorId : vibratorIds) { - mVibrators.put(vibratorId, injector.createVibratorController(vibratorId, listener)); + VibratorController vibratorController = + injector.createVibratorController(vibratorId, listener); + mVibrators.put(vibratorId, vibratorController); } } @@ -334,6 +339,15 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { mVibrators.valueAt(i).reloadVibratorInfoIfNeeded(); } + synchronized (mLock) { + mVibratorInfos = transformAllVibratorsLocked(VibratorController::getVibratorInfo); + VibratorInfo[] infos = new VibratorInfo[mVibratorInfos.size()]; + for (int i = 0; i < mVibratorInfos.size(); i++) { + infos[i] = mVibratorInfos.valueAt(i); + } + mCombinedVibratorInfo = VibratorInfoFactory.create(/* id= */ -1, infos); + } + mVibrationSettings.onSystemReady(); mInputDeviceDelegate.onSystemReady(); @@ -633,7 +647,8 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { endExternalVibrateLocked(Status.CANCELLED_SUPERSEDED, callerInfo, /* continueExternalControl= */ false); } else if (mCurrentVibration != null) { - if (mCurrentVibration.getVibration().canPipelineWith(vib)) { + if (mCurrentVibration.getVibration().canPipelineWith(vib, mVibratorInfos, + mVibrationConfig.getVibrationPipelineMaxDurationMs())) { // Don't cancel the current vibration if it's pipeline-able. // Note that if there is a pending next vibration that can't be // pipelined, it will have already cancelled the current one, so we @@ -1871,33 +1886,11 @@ public class VibratorManagerService extends IVibratorManagerService.Stub { } } + @Nullable private VibratorInfo getCombinedVibratorInfo() { synchronized (mLock) { - // Used a cached resolving vibrator if one exists. - if (mCombinedVibratorInfo != null) { - return mCombinedVibratorInfo; - } - - // Return an empty resolving vibrator if the service has no vibrator. - if (mVibratorIds.length == 0) { - return mCombinedVibratorInfo = VibratorInfo.EMPTY_VIBRATOR_INFO; - } - - // Combine the vibrator infos of all the service's vibrator to create a single resolving - // vibrator that is based on the combined info. - VibratorInfo[] infos = new VibratorInfo[mVibratorIds.length]; - for (int i = 0; i < mVibratorIds.length; i++) { - VibratorInfo info = getVibratorInfo(mVibratorIds[i]); - // If any one of the service's vibrator does not have a valid vibrator info, stop - // trying to create and cache a combined resolving vibrator. Combine the infos only - // when infos for all vibrators are available. - if (info == null) { - return null; - } - infos[i] = info; - } - - return mCombinedVibratorInfo = VibratorInfoFactory.create(/* id= */ -1, infos); + // This is only initialized at system ready, when all vibrator infos are fully loaded. + return mCombinedVibratorInfo; } } diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index ae30fcde39e1..d119a08b0c85 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -442,8 +442,6 @@ class ActivityClientController extends IActivityClientController.Stub { throw new IllegalArgumentException("File descriptors passed in Intent"); } - mService.mAmInternal.addCreatorToken(resultData); - final ActivityRecord r; synchronized (mGlobalLock) { r = ActivityRecord.isInRootTaskLocked(token); @@ -502,6 +500,8 @@ class ActivityClientController extends IActivityClientController.Stub { r.app.setLastActivityFinishTimeIfNeeded(SystemClock.uptimeMillis()); } + mService.mAmInternal.addCreatorToken(resultData, r.packageName); + final long origId = Binder.clearCallingIdentity(); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "finishActivity"); try { diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index a44eb48d7836..460de01a7d1d 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -285,9 +285,6 @@ import android.app.servertransaction.StopActivityItem; import android.app.servertransaction.TopResumedActivityChangeItem; import android.app.servertransaction.TransferSplashScreenViewStateItem; import android.app.usage.UsageEvents.Event; -import android.compat.annotation.ChangeId; -import android.compat.annotation.EnabledAfter; -import android.compat.annotation.Overridable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -467,11 +464,6 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A // finished destroying itself. private static final int DESTROY_TIMEOUT = 10 * 1000; - @ChangeId - @Overridable - @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM) - static final long UNIVERSAL_RESIZABLE_BY_DEFAULT = 357141415; - final ActivityTaskManagerService mAtmService; final ActivityCallerState mCallerState; @NonNull @@ -3192,7 +3184,7 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A && mDisplayContent != null && mDisplayContent.getConfiguration() .smallestScreenWidthDp >= WindowManager.LARGE_SCREEN_SMALLEST_SCREEN_WIDTH_DP && mDisplayContent.getIgnoreOrientationRequest() - && info.isChangeEnabled(UNIVERSAL_RESIZABLE_BY_DEFAULT); + && info.isChangeEnabled(ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT); if (!compatEnabled && !mWmService.mConstants.mIgnoreActivityOrientationRequest) { return false; } diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java index 0580d4a5a4a3..c1f5a27b81e7 100644 --- a/services/core/java/com/android/server/wm/ActivityStartController.java +++ b/services/core/java/com/android/server/wm/ActivityStartController.java @@ -25,6 +25,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; import static android.os.FactoryTest.FACTORY_TEST_LOW_LEVEL; +import static com.android.server.wm.ActivityStarter.Request.DEFAULT_INTENT_CREATOR_UID; 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.ActivityTaskSupervisor.ON_TOP; @@ -441,6 +442,17 @@ public class ActivityStartController { 0 /* startFlags */, null /* profilerInfo */, userId, filterCallingUid, callingPid); aInfo = mService.mAmInternal.getActivityInfoForUser(aInfo, userId); + int creatorUid = DEFAULT_INTENT_CREATOR_UID; + String creatorPackage = null; + if (ActivityManagerService.IntentCreatorToken.isValid(intent)) { + ActivityManagerService.IntentCreatorToken creatorToken = + (ActivityManagerService.IntentCreatorToken) intent.getCreatorToken(); + if (creatorToken.getCreatorUid() != filterCallingUid) { + creatorUid = creatorToken.getCreatorUid(); + creatorPackage = creatorToken.getCreatorPackage(); + } + // leave creatorUid as -1 if the intent creator is the same as the launcher + } if (aInfo != null) { try { @@ -454,6 +466,24 @@ public class ActivityStartController { return START_CANCELED; } + if (creatorUid != DEFAULT_INTENT_CREATOR_UID) { + try { + NeededUriGrants creatorIntentGrants = mSupervisor.mService.mUgmInternal + .checkGrantUriPermissionFromIntent(intent, creatorUid, + aInfo.applicationInfo.packageName, + UserHandle.getUserId(aInfo.applicationInfo.uid)); + if (intentGrants == null) { + intentGrants = creatorIntentGrants; + } else { + intentGrants.merge(creatorIntentGrants); + } + } catch (SecurityException securityException) { + ActivityStarter.logForIntentRedirect( + "Creator URI Grant Caused Exception.", intent, creatorUid, + creatorPackage, filterCallingUid, callingPackage); + // TODO b/368559093 - rethrow the securityException. + } + } if ((aInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE) != 0) { throw new IllegalArgumentException( @@ -477,6 +507,8 @@ public class ActivityStartController { .setCallingUid(callingUid) .setCallingPackage(callingPackage) .setCallingFeatureId(callingFeatureId) + .setIntentCreatorUid(creatorUid) + .setIntentCreatorPackage(creatorPackage) .setRealCallingPid(realCallingPid) .setRealCallingUid(realCallingUid) .setActivityOptions(checkedOptions) diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java index 87fa62ac0e3b..5d3ae54f0934 100644 --- a/services/core/java/com/android/server/wm/ActivityStarter.java +++ b/services/core/java/com/android/server/wm/ActivityStarter.java @@ -86,6 +86,7 @@ import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_MIN_DIMENS import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_NEW_TASK; import static com.android.server.wm.TaskFragment.EMBEDDING_DISALLOWED_UNTRUSTED_HOST; import static com.android.server.wm.WindowContainer.POSITION_TOP; +import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM; import static com.android.window.flags.Flags.balDontBringExistingBackgroundTaskStackToFg; import android.annotation.IntDef; @@ -131,6 +132,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.app.HeavyWeightSwitcherActivity; import com.android.internal.app.IVoiceInteractor; import com.android.internal.protolog.ProtoLog; +import com.android.server.am.ActivityManagerService.IntentCreatorToken; import com.android.server.am.PendingIntentRecord; import com.android.server.pm.InstantAppResolver; import com.android.server.pm.PackageArchiver; @@ -383,6 +385,7 @@ class ActivityStarter { private static final int DEFAULT_CALLING_PID = 0; static final int DEFAULT_REAL_CALLING_UID = -1; static final int DEFAULT_REAL_CALLING_PID = 0; + static final int DEFAULT_INTENT_CREATOR_UID = -1; IApplicationThread caller; Intent intent; @@ -403,6 +406,8 @@ class ActivityStarter { @Nullable String callingFeatureId; int realCallingPid = DEFAULT_REAL_CALLING_PID; int realCallingUid = DEFAULT_REAL_CALLING_UID; + int intentCreatorUid = DEFAULT_INTENT_CREATOR_UID; + String intentCreatorPackage; int startFlags; SafeActivityOptions activityOptions; boolean ignoreTargetSecurity; @@ -463,6 +468,8 @@ class ActivityStarter { callingPid = DEFAULT_CALLING_PID; callingUid = DEFAULT_CALLING_UID; callingPackage = null; + intentCreatorUid = DEFAULT_INTENT_CREATOR_UID; + intentCreatorPackage = null; callingFeatureId = null; realCallingPid = DEFAULT_REAL_CALLING_PID; realCallingUid = DEFAULT_REAL_CALLING_UID; @@ -555,12 +562,14 @@ class ActivityStarter { // "resolved" calling UID, where we try our best to identify the // actual caller that is starting this activity int resolvedCallingUid = callingUid; + String resolvedCallingPackage = callingPackage; if (caller != null) { synchronized (supervisor.mService.mGlobalLock) { final WindowProcessController callerApp = supervisor.mService .getProcessController(caller); if (callerApp != null) { resolvedCallingUid = callerApp.mInfo.uid; + resolvedCallingPackage = callerApp.mInfo.packageName; } } } @@ -596,7 +605,23 @@ class ActivityStarter { // Collect information about the target of the Intent. activityInfo = supervisor.resolveActivity(intent, resolveInfo, startFlags, profilerInfo); - + // Check if the Intent was redirected + if ((intent.getExtendedFlags() & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN) + != 0) { + ActivityStarter.logForIntentRedirect( + "Unparceled intent does not have a creator token set.", intent, + intentCreatorUid, + intentCreatorPackage, resolvedCallingUid, resolvedCallingPackage); + // TODO b/368559093 - eventually ramp up to throw SecurityException + } + if (IntentCreatorToken.isValid(intent)) { + IntentCreatorToken creatorToken = (IntentCreatorToken) intent.getCreatorToken(); + if (creatorToken.getCreatorUid() != resolvedCallingUid) { + intentCreatorUid = creatorToken.getCreatorUid(); + intentCreatorPackage = creatorToken.getCreatorPackage(); + } + // leave intentCreatorUid as -1 if the intent creator is the same as the launcher + } // Carefully collect grants without holding lock if (activityInfo != null) { if (android.security.Flags.contentUriPermissionApis()) { @@ -606,11 +631,52 @@ class ActivityStarter { UserHandle.getUserId(activityInfo.applicationInfo.uid), activityInfo.requireContentUriPermissionFromCaller, /* requestHashCode */ this.hashCode()); + if (intentCreatorUid != DEFAULT_INTENT_CREATOR_UID) { + try { + NeededUriGrants creatorIntentGrants = supervisor.mService.mUgmInternal + .checkGrantUriPermissionFromIntent(intent, intentCreatorUid, + activityInfo.applicationInfo.packageName, + UserHandle.getUserId(activityInfo.applicationInfo.uid), + activityInfo.requireContentUriPermissionFromCaller, + /* requestHashCode */ this.hashCode()); + if (intentGrants == null) { + intentGrants = creatorIntentGrants; + } else { + intentGrants.merge(creatorIntentGrants); + } + } catch (SecurityException securityException) { + ActivityStarter.logForIntentRedirect( + "Creator URI Grant Caused Exception.", intent, intentCreatorUid, + intentCreatorPackage, resolvedCallingUid, + resolvedCallingPackage); + // TODO b/368559093 - rethrow the securityException. + } + } } else { intentGrants = supervisor.mService.mUgmInternal .checkGrantUriPermissionFromIntent(intent, resolvedCallingUid, activityInfo.applicationInfo.packageName, UserHandle.getUserId(activityInfo.applicationInfo.uid)); + if (intentCreatorUid != DEFAULT_INTENT_CREATOR_UID && intentGrants != null) { + try { + NeededUriGrants creatorIntentGrants = supervisor.mService.mUgmInternal + .checkGrantUriPermissionFromIntent(intent, intentCreatorUid, + activityInfo.applicationInfo.packageName, + UserHandle.getUserId( + activityInfo.applicationInfo.uid)); + if (intentGrants == null) { + intentGrants = creatorIntentGrants; + } else { + intentGrants.merge(creatorIntentGrants); + } + } catch (SecurityException securityException) { + ActivityStarter.logForIntentRedirect( + "Creator URI Grant Caused Exception.", intent, intentCreatorUid, + intentCreatorPackage, resolvedCallingUid, + resolvedCallingPackage); + // TODO b/368559093 - rethrow the securityException. + } + } } } } @@ -977,7 +1043,9 @@ class ActivityStarter { int requestCode = request.requestCode; int callingPid = request.callingPid; int callingUid = request.callingUid; - String callingPackage = request.callingPackage; + int intentCreatorUid = request.intentCreatorUid; + String intentCreatorPackage = request.intentCreatorPackage; + String intentCallingPackage = request.callingPackage; String callingFeatureId = request.callingFeatureId; final int realCallingPid = request.realCallingPid; final int realCallingUid = request.realCallingUid; @@ -1062,7 +1130,7 @@ class ActivityStarter { // launched in the app flow to redirect to an activity picked by the user, where // we want the final activity to consider it to have been launched by the // previous app activity. - callingPackage = sourceRecord.launchedFromPackage; + intentCallingPackage = sourceRecord.launchedFromPackage; callingFeatureId = sourceRecord.launchedFromFeatureId; } } @@ -1084,7 +1152,7 @@ class ActivityStarter { if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) { err = packageArchiver .requestUnarchiveOnActivityStart( - intent, callingPackage, mRequest.userId, realCallingUid); + intent, intentCallingPackage, mRequest.userId, realCallingUid); } } } @@ -1143,7 +1211,7 @@ class ActivityStarter { boolean abort; try { abort = !mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, - requestCode, callingPid, callingUid, callingPackage, callingFeatureId, + requestCode, callingPid, callingUid, intentCallingPackage, callingFeatureId, request.ignoreTargetSecurity, inTask != null, callerApp, resultRecord, resultRootTask); } catch (SecurityException e) { @@ -1171,7 +1239,47 @@ class ActivityStarter { abort |= !mService.mIntentFirewall.checkStartActivity(intent, callingUid, callingPid, resolvedType, aInfo.applicationInfo); abort |= !mService.getPermissionPolicyInternal().checkStartActivity(intent, callingUid, - callingPackage); + intentCallingPackage); + + if (intentCreatorUid != Request.DEFAULT_INTENT_CREATOR_UID) { + try { + if (!mSupervisor.checkStartAnyActivityPermission(intent, aInfo, resultWho, + requestCode, 0, intentCreatorUid, intentCreatorPackage, "", + request.ignoreTargetSecurity, inTask != null, null, resultRecord, + resultRootTask)) { + logForIntentRedirect("Creator checkStartAnyActivityPermission Caused abortion.", + intent, intentCreatorUid, intentCreatorPackage, callingUid, + intentCallingPackage); + // TODO b/368559093 - set abort to true. + // abort = true; + } + } catch (SecurityException e) { + logForIntentRedirect("Creator checkStartAnyActivityPermission Caused Exception.", + intent, intentCreatorUid, intentCreatorPackage, callingUid, + intentCallingPackage); + // TODO b/368559093 - rethrow the exception. + //throw e; + } + if (!mService.mIntentFirewall.checkStartActivity(intent, intentCreatorUid, 0, + resolvedType, aInfo.applicationInfo)) { + logForIntentRedirect("Creator IntentFirewall.checkStartActivity Caused abortion.", + intent, intentCreatorUid, intentCreatorPackage, callingUid, + intentCallingPackage); + // TODO b/368559093 - set abort to true. + // abort = true; + } + + if (!mService.getPermissionPolicyInternal().checkStartActivity(intent, intentCreatorUid, + intentCreatorPackage)) { + logForIntentRedirect( + "Creator PermissionPolicyService.checkStartActivity Caused abortion.", + intent, intentCreatorUid, intentCreatorPackage, callingUid, + intentCallingPackage); + // TODO b/368559093 - set abort to true. + // abort = true; + } + intent.removeCreatorTokenInfo(); + } // Merge the two options bundles, while realCallerOptions takes precedence. ActivityOptions checkedOptions = options != null @@ -1188,7 +1296,7 @@ class ActivityStarter { balController.checkBackgroundActivityStart( callingUid, callingPid, - callingPackage, + intentCallingPackage, realCallingUid, realCallingPid, callerApp, @@ -1209,7 +1317,7 @@ class ActivityStarter { if (request.allowPendingRemoteAnimationRegistryLookup) { checkedOptions = mService.getActivityStartController() .getPendingRemoteAnimationRegistry() - .overrideOptionsIfNeeded(callingPackage, checkedOptions); + .overrideOptionsIfNeeded(intentCallingPackage, checkedOptions); } if (mService.mController != null) { try { @@ -1225,7 +1333,8 @@ class ActivityStarter { final TaskDisplayArea suggestedLaunchDisplayArea = computeSuggestedLaunchDisplayArea(inTask, sourceRecord, checkedOptions); - mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, callingPackage, + mInterceptor.setStates(userId, realCallingPid, realCallingUid, startFlags, + intentCallingPackage, callingFeatureId); if (mInterceptor.intercept(intent, rInfo, aInfo, resolvedType, inTask, inTaskFragment, callingPid, callingUid, checkedOptions, suggestedLaunchDisplayArea)) { @@ -1263,7 +1372,8 @@ class ActivityStarter { if (mService.getPackageManagerInternalLocked().isPermissionsReviewRequired( aInfo.packageName, userId)) { final IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, callingPackage, callingFeatureId, + ActivityManager.INTENT_SENDER_ACTIVITY, intentCallingPackage, + callingFeatureId, callingUid, userId, null, null, 0, new Intent[]{intent}, new String[]{resolvedType}, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT, null); @@ -1326,7 +1436,8 @@ class ActivityStarter { // app [on install success]. if (rInfo != null && rInfo.auxiliaryInfo != null) { intent = createLaunchIntent(rInfo.auxiliaryInfo, request.ephemeralIntent, - callingPackage, callingFeatureId, verificationBundle, resolvedType, userId); + intentCallingPackage, callingFeatureId, verificationBundle, resolvedType, + userId); resolvedType = null; callingUid = realCallingUid; callingPid = realCallingPid; @@ -1349,7 +1460,7 @@ class ActivityStarter { .setCaller(callerApp) .setLaunchedFromPid(callingPid) .setLaunchedFromUid(callingUid) - .setLaunchedFromPackage(callingPackage) + .setLaunchedFromPackage(intentCallingPackage) .setLaunchedFromFeature(callingFeatureId) .setIntent(intent) .setResolvedType(resolvedType) @@ -2786,10 +2897,22 @@ class ActivityStarter { } } - if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 - && ((mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == 0 || mSourceRecord == null)) { - // ignore the flag if there is no the sourceRecord or without new_task flag - mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + if ((mLaunchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0) { + final boolean hasNewTaskFlag = (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) != 0; + if (!hasNewTaskFlag || mSourceRecord == null) { + // ignore the flag if there is no the sourceRecord or without new_task flag + Slog.w(TAG_WM, !hasNewTaskFlag + ? "Launch adjacent ignored due to missing NEW_TASK" + : "Launch adjacent ignored due to missing source activity"); + mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + } + // Ensure that the source task or its parents has not disabled launch-adjacent + if (mSourceRecord != null && mSourceRecord.getTask() != null && + mSourceRecord.getTask().isLaunchAdjacentDisabled()) { + Slog.w(TAG_WM, "Launch adjacent blocked by source task or ancestor"); + mLaunchFlags &= ~FLAG_ACTIVITY_LAUNCH_ADJACENT; + } + } } @@ -3295,6 +3418,16 @@ class ActivityStarter { return this; } + ActivityStarter setIntentCreatorUid(int uid) { + mRequest.intentCreatorUid = uid; + return this; + } + + ActivityStarter setIntentCreatorPackage(String intentCreatorPackage) { + mRequest.intentCreatorPackage = intentCreatorPackage; + return this; + } + /** * Sets the pid of the caller who requested to launch the activity. * @@ -3454,4 +3587,19 @@ class ActivityStarter { pw.print(" mInTaskFragment="); pw.println(mInTaskFragment); } + + static void logForIntentRedirect(String message, Intent intent, int intentCreatorUid, + String intentCreatorPackage, int callingUid, String callingPackage) { + String msg = getIntentRedirectPreventedLogMessage(message, intent, intentCreatorUid, + intentCreatorPackage, callingUid, callingPackage); + Slog.wtf(TAG, msg); + } + + private static String getIntentRedirectPreventedLogMessage(String message, Intent intent, + int intentCreatorUid, String intentCreatorPackage, int callingUid, + String callingPackage) { + return "[IntentRedirect]" + message + " intentCreatorUid: " + intentCreatorUid + + "; intentCreatorPackage: " + intentCreatorPackage + "; callingUid: " + callingUid + + "; callingPackage: " + callingPackage + "; intent: " + intent; + } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java index 3d6b64b2e536..3560565ce9cd 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerInternal.java @@ -589,7 +589,7 @@ public abstract class ActivityTaskManagerInternal { * sensitive environment. */ public abstract TaskSnapshot getTaskSnapshotBlocking(int taskId, - boolean isLowResolution); + boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage); /** Returns true if uid is considered foreground for activity start purposes. */ public abstract boolean isUidForeground(int uid); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index 3dfc8f4e5bf9..5339753624d8 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -1228,7 +1228,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { - mAmInternal.addCreatorToken(intent); + mAmInternal.addCreatorToken(intent, callingPackage); return startActivityAsUser(caller, callingPackage, callingFeatureId, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions, UserHandle.getCallingUserId()); @@ -1243,7 +1243,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { enforceNotIsolatedCaller(reason); if (intents != null) { for (Intent intent : intents) { - mAmInternal.addCreatorToken(intent); + mAmInternal.addCreatorToken(intent, callingPackage); } } userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, reason); @@ -1275,7 +1275,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Nullable String callingFeatureId, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) { - mAmInternal.addCreatorToken(intent); + mAmInternal.addCreatorToken(intent, callingPackage); final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions); assertPackageMatchesCallingUid(callingPackage); @@ -1330,7 +1330,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } // Remove existing mismatch flag so it can be properly updated later fillInIntent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); - mAmInternal.addCreatorToken(fillInIntent); } if (!(target instanceof PendingIntentRecord)) { @@ -1339,6 +1338,10 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { PendingIntentRecord pir = (PendingIntentRecord) target; + if (fillInIntent != null) { + mAmInternal.addCreatorToken(fillInIntent, pir.getPackageName()); + } + synchronized (mGlobalLock) { // If this is coming from the currently resumed activity, it is // effectively saying that app switches are allowed at this point. @@ -1349,6 +1352,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { mAppSwitchesState = APP_SWITCH_ALLOW; } } + return pir.sendInner(caller, 0, fillInIntent, resolvedType, allowlistToken, null, null, resultTo, resultWho, requestCode, flagsMask, flagsValues, bOptions); } @@ -1361,8 +1365,6 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { throw new IllegalArgumentException("File descriptors passed in Intent"); } - mAmInternal.addCreatorToken(intent); - SafeActivityOptions options = SafeActivityOptions.fromBundle(bOptions); synchronized (mGlobalLock) { @@ -1376,6 +1378,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { SafeActivityOptions.abort(options); return false; } + + mAmInternal.addCreatorToken(intent, r.packageName); + intent = new Intent(intent); // Remove existing mismatch flag so it can be properly updated later intent.removeExtendedFlags(Intent.EXTENDED_FLAG_FILTER_MISMATCH); @@ -3946,6 +3951,28 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + private TaskSnapshot getTaskSnapshotInner(int taskId, boolean isLowResolution, + @TaskSnapshot.ReferenceFlags int usage) { + final Task task; + synchronized (mGlobalLock) { + task = mRootWindowContainer.anyTaskForId(taskId, MATCH_ATTACHED_TASK_OR_RECENT_TASKS); + if (task == null) { + Slog.w(TAG, "getTaskSnapshot: taskId=" + taskId + " not found"); + return null; + } + // Try to load snapshot from cache first, and add reference if the snapshot is in cache. + final TaskSnapshot snapshot = mWindowManager.mTaskSnapshotController.getSnapshot(taskId, + task.mUserId, false /* restoreFromDisk */, isLowResolution); + if (snapshot != null) { + snapshot.addReference(usage); + return snapshot; + } + } + // Don't call this while holding the lock as this operation might hit the disk. + return mWindowManager.mTaskSnapshotController.getSnapshot(taskId, + task.mUserId, true /* restoreFromDisk */, isLowResolution); + } + @Override public TaskSnapshot getTaskSnapshot(int taskId, boolean isLowResolution) { mAmInternal.enforceCallingPermission(READ_FRAME_BUFFER, "getTaskSnapshot()"); @@ -7274,8 +7301,9 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { @Override public TaskSnapshot getTaskSnapshotBlocking( - int taskId, boolean isLowResolution) { - return ActivityTaskManagerService.this.getTaskSnapshot(taskId, isLowResolution); + int taskId, boolean isLowResolution, @TaskSnapshot.ReferenceFlags int usage) { + return ActivityTaskManagerService.this.getTaskSnapshotInner( + taskId, isLowResolution, usage); } @Override diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java index 4d17ed24e734..eee4c86bc483 100644 --- a/services/core/java/com/android/server/wm/AppTaskImpl.java +++ b/services/core/java/com/android/server/wm/AppTaskImpl.java @@ -160,7 +160,7 @@ class AppTaskImpl extends IAppTask.Stub { Intent intent, String resolvedType, Bundle bOptions) { checkCallerOrSystemOrRoot(); mService.assertPackageMatchesCallingUid(callingPackage); - mService.mAmInternal.addCreatorToken(intent); + mService.mAmInternal.addCreatorToken(intent, callingPackage); int callingUser = UserHandle.getCallingUserId(); Task task; diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index 94cd2e64b057..70f9ebb0e61e 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -34,6 +34,7 @@ import static com.android.server.wm.BackNavigationProto.LAST_BACK_TYPE; import static com.android.server.wm.BackNavigationProto.MAIN_OPEN_ACTIVITY; import static com.android.server.wm.BackNavigationProto.SHOW_WALLPAPER; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK; +import static com.android.server.wm.WindowContainer.SYNC_STATE_NONE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -1237,8 +1238,9 @@ class BackNavigationController { } allWindowDrawn &= next.mAppWindowDrawn; } - // Do not remove until transition ready. - if (!activity.isVisible()) { + // Do not remove windowless surfaces if the transaction has not been applied. + if (activity.getSyncTransactionCommitCallbackDepth() > 0 + || activity.mSyncState != SYNC_STATE_NONE) { return; } if (allWindowDrawn) { diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java index a380ba1a6f11..f9902cf0db9c 100644 --- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java +++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java @@ -66,6 +66,7 @@ import android.app.BackgroundStartPrivileges; import android.app.compat.CompatChanges; import android.compat.annotation.ChangeId; import android.compat.annotation.EnabledAfter; +import android.compat.annotation.Overridable; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; @@ -129,6 +130,7 @@ public class BackgroundActivityStartController { /** If enabled the creator will not allow BAL on its behalf by default. */ @ChangeId @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE) + @Overridable private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR = 296478951; public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED = diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 6a23aaa663ef..e9c6e93891df 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -274,34 +274,7 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (caller != controlTarget) { if (Flags.refactorInsetsController()) { if (isImeInputTarget(caller)) { - // In case of the multi window mode, update the requestedVisibleTypes from - // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController. - // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with - // its new requested visibility for the IME - boolean imeVisible = caller.isRequestedVisible(WindowInsets.Type.ime()); - if (controlTarget != null) { - ImeTracker.forLogging().onProgress(statsToken, - ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); - controlTarget.setImeInputTargetRequestedVisibility(imeVisible); - } else if (caller instanceof InsetsControlTarget) { - // In case of a virtual display that cannot show the IME, the - // controlTarget will be null here, as no controlTarget was set yet. In - // that case, proceed similar to the multi window mode (fallback = - // RemoteInsetsControlTarget of the default display) - controlTarget = mDisplayContent.getImeHostOrFallback( - ((InsetsControlTarget) caller).getWindow()); - - if (controlTarget != caller) { - ImeTracker.forLogging().onProgress(statsToken, - ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); - controlTarget.setImeInputTargetRequestedVisibility(imeVisible); - } else { - ImeTracker.forLogging().onFailed(statsToken, - ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); - } - } - - invokeOnImeRequestedChangedListener(caller, statsToken); + reportImeInputTargetStateToControlTarget(caller, controlTarget, statsToken); } else { // TODO(b/353463205) add ImeTracker? } @@ -332,14 +305,42 @@ final class ImeInsetsSourceProvider extends InsetsSourceProvider { if (Flags.refactorInsetsController() && target != null) { InsetsControlTarget imeControlTarget = getControlTarget(); if (target != imeControlTarget) { - // If the targetWin is not the imeControlTarget (=RemoteInsetsControlTarget) let it - // know about the new requestedVisibleTypes for the IME. - if (imeControlTarget != null) { - imeControlTarget.setImeInputTargetRequestedVisibility( - (target.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); - } + // TODO(b/353463205): start new request here? + reportImeInputTargetStateToControlTarget(target, imeControlTarget, + null /* statsToken */); + } + } + } + + private void reportImeInputTargetStateToControlTarget(@NonNull InsetsTarget imeInsetsTarget, + InsetsControlTarget controlTarget, @Nullable ImeTracker.Token statsToken) { + // In case of the multi window mode, update the requestedVisibleTypes from + // the controlTarget (=RemoteInsetsControlTarget) via DisplayImeController. + // Then, trigger onRequestedVisibleTypesChanged for the controlTarget with + // its new requested visibility for the IME + boolean imeVisible = imeInsetsTarget.isRequestedVisible(WindowInsets.Type.ime()); + if (controlTarget != null) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); + controlTarget.setImeInputTargetRequestedVisibility(imeVisible); + } else if (imeInsetsTarget instanceof InsetsControlTarget) { + // In case of a virtual display that cannot show the IME, the + // controlTarget will be null here, as no controlTarget was set yet. In + // that case, proceed similar to the multi window mode (fallback = + // RemoteInsetsControlTarget of the default display) + controlTarget = mDisplayContent.getImeHostOrFallback( + ((InsetsControlTarget) imeInsetsTarget).getWindow()); + + if (controlTarget != imeInsetsTarget) { + ImeTracker.forLogging().onProgress(statsToken, + ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); + controlTarget.setImeInputTargetRequestedVisibility(imeVisible); + } else { + ImeTracker.forLogging().onFailed(statsToken, + ImeTracker.PHASE_WM_SET_REMOTE_TARGET_IME_VISIBILITY); } } + invokeOnImeRequestedChangedListener(imeInsetsTarget, statsToken); } // TODO(b/353463205) check callers to see if we can make statsToken @NonNull diff --git a/services/core/java/com/android/server/wm/InputManagerCallback.java b/services/core/java/com/android/server/wm/InputManagerCallback.java index a4fe0647ea79..9d21183c6c03 100644 --- a/services/core/java/com/android/server/wm/InputManagerCallback.java +++ b/services/core/java/com/android/server/wm/InputManagerCallback.java @@ -131,7 +131,9 @@ final class InputManagerCallback implements InputManagerService.WindowManagerCal final boolean changed = !com.android.window.flags.Flags.filterIrrelevantInputDeviceChange() || updateLastInputConfigurationSources(); - if (changed) { + // Even if the input devices are not changed, there could be other pending changes + // during booting. It's fine to apply earlier. + if (changed || !mService.mDisplayEnabled) { synchronized (mService.mGlobalLock) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "inputDeviceConfigChanged"); mService.mRoot.forAllDisplays(DisplayContent::sendNewConfiguration); diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java index 6067a9972bbe..1d4d6eb82c44 100644 --- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java @@ -409,7 +409,7 @@ class InsetsSourceProvider { } final Point position = getWindowFrameSurfacePosition(); if (!mPosition.equals(position)) { - mPosition.set(position.x, position.y); + mPosition.set(position); if (windowState != null && windowState.getWindowFrames().didFrameSizeChange() && windowState.mWinAnimator.getShown() && mWindowContainer.okToDisplay()) { mHasPendingPosition = true; @@ -553,6 +553,7 @@ class InsetsSourceProvider { } boolean initiallyVisible = mClientVisible; final Point surfacePosition = getWindowFrameSurfacePosition(); + mPosition.set(surfacePosition); mAdapter = new ControlAdapter(surfacePosition); if (mSource.getType() == WindowInsets.Type.ime()) { if (android.view.inputmethod.Flags.refactorInsetsController()) { diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java index 00704b3525c5..4861341f830a 100644 --- a/services/core/java/com/android/server/wm/Task.java +++ b/services/core/java/com/android/server/wm/Task.java @@ -503,6 +503,11 @@ class Task extends TaskFragment { boolean mIsTrimmableFromRecents; /** + * Sets whether the launch-adjacent flag is respected or not for this task or its child tasks. + */ + private boolean mLaunchAdjacentDisabled; + + /** * Bounds offset should be applied when calculating compatible configuration for apps targeting * SDK level 34 or before. */ @@ -3802,6 +3807,9 @@ class Task extends TaskFragment { pw.print(prefix); pw.print("lastActiveTime="); pw.print(lastActiveTime); pw.println(" (inactive for " + (getInactiveDuration() / 1000) + "s)"); pw.print(prefix); pw.println(" isTrimmable=" + mIsTrimmableFromRecents); + if (mLaunchAdjacentDisabled) { + pw.println(prefix + "mLaunchAdjacentDisabled=true"); + } } @Override @@ -5734,9 +5742,9 @@ class Task extends TaskFragment { } private boolean canMoveTaskToBack(Task task) { - // Checks whether a task is a child of this task because it can be reparetned when + // Checks whether a task is a child of this task because it can be reparented when // transition is deferred. - if (task != this && task.getParent() != this) { + if (task != this && !task.isDescendantOf(this)) { return false; } @@ -6274,6 +6282,28 @@ class Task extends TaskFragment { } /** + * Sets this task and its children to disable respecting launch-adjacent. + */ + void setLaunchAdjacentDisabled(boolean disabled) { + mLaunchAdjacentDisabled = disabled; + } + + /** + * Returns whether this task or any of its ancestors have disabled respecting the + * launch-adjacent flag. + */ + boolean isLaunchAdjacentDisabled() { + Task t = this; + while (t != null) { + if (t.mLaunchAdjacentDisabled) { + return true; + } + t = t.getParent().asTask(); + } + return false; + } + + /** * Return true if the activityInfo has the same requiredDisplayCategory as this task. */ boolean isSameRequiredDisplayCategory(@NonNull ActivityInfo info) { diff --git a/services/core/java/com/android/server/wm/TaskDisplayArea.java b/services/core/java/com/android/server/wm/TaskDisplayArea.java index 5dd3bbce4e96..2c71c1a1f4f3 100644 --- a/services/core/java/com/android/server/wm/TaskDisplayArea.java +++ b/services/core/java/com/android/server/wm/TaskDisplayArea.java @@ -1074,6 +1074,8 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { final Task launchRootTask = Task.fromWindowContainerToken(options.getLaunchRootTask()); // We only allow this for created by organizer tasks. if (launchRootTask != null && launchRootTask.mCreatedByOrganizer) { + Slog.i(TAG_WM, "Using launch root task from activity options: taskId=" + + launchRootTask.mTaskId); return launchRootTask; } } @@ -1081,19 +1083,25 @@ final class TaskDisplayArea extends DisplayArea<WindowContainer> { // Use launch-adjacent-flag-root if launching with launch-adjacent flag. if ((launchFlags & FLAG_ACTIVITY_LAUNCH_ADJACENT) != 0 && mLaunchAdjacentFlagRootTask != null) { + final Task launchAdjacentRootAdjacentTask = + mLaunchAdjacentFlagRootTask.getAdjacentTask(); if (sourceTask != null && (sourceTask == candidateTask || sourceTask.topRunningActivity() == null)) { // Do nothing when task that is getting opened is same as the source or when // the source is no-longer valid. Slog.w(TAG_WM, "Ignoring LAUNCH_ADJACENT because adjacent source is gone."); } else if (sourceTask != null - && mLaunchAdjacentFlagRootTask.getAdjacentTask() != null + && launchAdjacentRootAdjacentTask != null && (sourceTask == mLaunchAdjacentFlagRootTask || sourceTask.isDescendantOf(mLaunchAdjacentFlagRootTask))) { - // If the adjacent launch is coming from the same root, launch to - // adjacent root instead. - return mLaunchAdjacentFlagRootTask.getAdjacentTask(); + // If the adjacent launch is coming from the same root that was specified as the + // launch-adjacent task, so instead we launch to its adjacent root instead. + Slog.i(TAG_WM, "Using adjacent-to specified launch-adjacent task: taskId=" + + launchAdjacentRootAdjacentTask.mTaskId); + return launchAdjacentRootAdjacentTask; } else { + Slog.i(TAG_WM, "Using specified launch-adjacent task: taskId=" + + mLaunchAdjacentFlagRootTask.mTaskId); return mLaunchAdjacentFlagRootTask; } } diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 82c7a9350eca..166d74b132bd 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -69,6 +69,7 @@ import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ADJACENT_ROOTS; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_ALWAYS_ON_TOP; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_EXCLUDE_INSETS_TYPES; +import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_IS_TRIMMABLE; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ADJACENT_FLAG_ROOT; import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_SET_LAUNCH_ROOT; @@ -1450,6 +1451,17 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub task.setTrimmableFromRecents(hop.isTrimmableFromRecents()); break; } + case HIERARCHY_OP_TYPE_SET_DISABLE_LAUNCH_ADJACENT: { + final WindowContainer container = WindowContainer.fromBinder(hop.getContainer()); + final Task task = container != null ? container.asTask() : null; + if (task == null || !task.isAttached()) { + Slog.e(TAG, "Attempt to operate on unknown or detached container: " + + container); + break; + } + task.setLaunchAdjacentDisabled(hop.isLaunchAdjacentDisabled()); + break; + } case HIERARCHY_OP_TYPE_RESTORE_BACK_NAVIGATION: { if (mService.mBackNavigationController.restoreBackNavigation()) { effects |= TRANSACT_EFFECTS_LIFECYCLE; diff --git a/services/core/java/com/android/server/wm/WindowProcessController.java b/services/core/java/com/android/server/wm/WindowProcessController.java index 86adc1944371..1a107c24a16a 100644 --- a/services/core/java/com/android/server/wm/WindowProcessController.java +++ b/services/core/java/com/android/server/wm/WindowProcessController.java @@ -133,7 +133,7 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio private int mRapidActivityLaunchCount; // all about the first app in the process - final ApplicationInfo mInfo; + volatile ApplicationInfo mInfo; final String mName; final int mUid; @@ -1805,12 +1805,17 @@ public class WindowProcessController extends ConfigurationContainer<Configuratio Configuration overrideConfig = new Configuration(r.getRequestedOverrideConfiguration()); overrideConfig.assetsSeq = assetSeq; r.onRequestedOverrideConfigurationChanged(overrideConfig); + r.updateApplicationInfo(mInfo); if (r.isVisibleRequested()) { r.ensureActivityConfiguration(); } } } + public void updateApplicationInfo(ApplicationInfo aInfo) { + mInfo = aInfo; + } + /** * This is called for sending {@link android.app.servertransaction.LaunchActivityItem}. * The caller must call {@link #setLastReportedConfiguration} if the delivered configuration diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index 7e450dd965d6..aca6f7235714 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -16815,6 +16815,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } EnforcingAdmin enforcingAdmin; + + // TODO(b/370472975): enable when we stop policy enforecer callback from blocking the main + // thread if (Flags.setPermissionGrantStateCoexistence()) { enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( admin, @@ -16840,6 +16843,31 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { callback.sendResult(null); return; } + + // TODO(b/266924257): decide how to handle the internal state if the package doesn't + // exist, or the permission isn't requested by the app, because we could end up with + // inconsistent state between the policy engine and package manager. Also a package + // might get removed or has it's permission updated after we've set the policy. + if (grantState == PERMISSION_GRANT_STATE_DEFAULT) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.PERMISSION_GRANT(packageName, permission), + enforcingAdmin, + caller.getUserId()); + } else { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.PERMISSION_GRANT(packageName, permission), + enforcingAdmin, + new IntegerPolicyValue(grantState), + caller.getUserId()); + } + int newState = mInjector.binderWithCleanCallingIdentity(() -> + getPermissionGrantStateForUser( + packageName, permission, caller, caller.getUserId())); + if (newState == grantState) { + callback.sendResult(Bundle.EMPTY); + } else { + callback.sendResult(null); + } } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller) @@ -16862,9 +16890,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } synchronized (getLockObject()) { long ident = mInjector.binderClearCallingIdentity(); + boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) + >= android.os.Build.VERSION_CODES.Q; + try { - boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) - >= android.os.Build.VERSION_CODES.Q; if (!isPostQAdmin) { // Legacy admins assume that they cannot control pre-M apps if (getTargetSdk(packageName, caller.getUserId()) @@ -16877,47 +16906,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { callback.sendResult(null); return; } - } catch (SecurityException e) { - Slogf.e(LOG_TAG, "Could not set permission grant state", e); - callback.sendResult(null); - } finally { - mInjector.binderRestoreCallingIdentity(ident); - } - } - } - // TODO(b/278710449): enable when we stop policy enforecer callback from blocking the main - // thread - if (false) { - // TODO(b/266924257): decide how to handle the internal state if the package doesn't - // exist, or the permission isn't requested by the app, because we could end up with - // inconsistent state between the policy engine and package manager. Also a package - // might get removed or has it's permission updated after we've set the policy. - if (grantState == PERMISSION_GRANT_STATE_DEFAULT) { - mDevicePolicyEngine.removeLocalPolicy( - PolicyDefinition.PERMISSION_GRANT(packageName, permission), - enforcingAdmin, - caller.getUserId()); - } else { - mDevicePolicyEngine.setLocalPolicy( - PolicyDefinition.PERMISSION_GRANT(packageName, permission), - enforcingAdmin, - new IntegerPolicyValue(grantState), - caller.getUserId()); - } - int newState = mInjector.binderWithCleanCallingIdentity(() -> - getPermissionGrantStateForUser( - packageName, permission, caller, caller.getUserId())); - if (newState == grantState) { - callback.sendResult(Bundle.EMPTY); - } else { - callback.sendResult(null); - } - } else { - synchronized (getLockObject()) { - long ident = mInjector.binderClearCallingIdentity(); - try { - boolean isPostQAdmin = getTargetSdk(caller.getPackageName(), caller.getUserId()) - >= android.os.Build.VERSION_CODES.Q; if (grantState == PERMISSION_GRANT_STATE_GRANTED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DENIED || grantState == DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT) { @@ -16939,7 +16927,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } catch (SecurityException e) { Slogf.e(LOG_TAG, "Could not set permission grant state", e); - callback.sendResult(null); } finally { mInjector.binderRestoreCallingIdentity(ident); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index b9727f9f3970..4103c477a8ae 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -327,8 +327,6 @@ public final class SystemServer implements Dumpable { * Implementation class names for services in the {@code SYSTEMSERVERCLASSPATH} * from {@code PRODUCT_SYSTEM_SERVER_JARS} that are *not* in {@code services.jar}. */ - private static final String ARC_NETWORK_SERVICE_CLASS = - "com.android.server.arc.net.ArcNetworkService"; private static final String ARC_PERSISTENT_DATA_BLOCK_SERVICE_CLASS = "com.android.server.arc.persistent_data_block.ArcPersistentDataBlockService"; private static final String ARC_SYSTEM_HEALTH_SERVICE = @@ -2101,24 +2099,13 @@ public final class SystemServer implements Dumpable { if (context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_WIFI)) { // Wifi Service must be started first for wifi-related services. - if (!isArc) { - t.traceBegin("StartWifi"); - mSystemServiceManager.startServiceFromJar( - WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); - t.traceEnd(); - t.traceBegin("StartWifiScanning"); - mSystemServiceManager.startServiceFromJar( - WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); - t.traceEnd(); - } - } - - // ARC - ArcNetworkService registers the ARC network stack and replaces the - // stock WiFi service in both ARC++ container and ARCVM. Always starts the ARC network - // stack regardless of whether FEATURE_WIFI is enabled/disabled (b/254755875). - if (isArc) { - t.traceBegin("StartArcNetworking"); - mSystemServiceManager.startService(ARC_NETWORK_SERVICE_CLASS); + t.traceBegin("StartWifi"); + mSystemServiceManager.startServiceFromJar( + WIFI_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); + t.traceEnd(); + t.traceBegin("StartWifiScanning"); + mSystemServiceManager.startServiceFromJar( + WIFI_SCANNING_SERVICE_CLASS, WIFI_APEX_SERVICE_JAR_PATH); t.traceEnd(); } diff --git a/services/midi/java/com/android/server/midi/MidiService.java b/services/midi/java/com/android/server/midi/MidiService.java index cc340c0a5f79..891c3349a43f 100644 --- a/services/midi/java/com/android/server/midi/MidiService.java +++ b/services/midi/java/com/android/server/midi/MidiService.java @@ -58,6 +58,7 @@ import android.os.UserHandle; import android.os.UserManager; import android.util.EventLog; import android.util.Log; +import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.content.PackageMonitor; @@ -1737,6 +1738,11 @@ public class MidiService extends IMidiManager.Stub { pw.decreaseIndent(); } + @Override + protected void onUnhandledException(int code, int flags, Exception e) { + Slog.wtf(TAG, "Uncaught exception in AudioService: " + code + ", " + flags, e); + } + @GuardedBy("mUsbMidiLock") private boolean isUsbMidiDeviceInUseLocked(MidiDeviceInfo info) { String name = info.getProperties().getString(MidiDeviceInfo.PROPERTY_NAME); diff --git a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java index ff901af3defa..30df4c821134 100644 --- a/services/people/java/com/android/server/people/data/CallLogQueryHelper.java +++ b/services/people/java/com/android/server/people/data/CallLogQueryHelper.java @@ -96,6 +96,8 @@ class CallLogQueryHelper { } catch (SecurityException ex) { Slog.e(TAG, "Query call log failed: " + ex); return false; + } catch (Exception e) { + Slog.e(TAG, "Exception when querying call log.", e); } return hasResults; } diff --git a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java index 2505abf2d160..2bd9d87b0124 100644 --- a/services/people/java/com/android/server/people/data/ContactsQueryHelper.java +++ b/services/people/java/com/android/server/people/data/ContactsQueryHelper.java @@ -151,9 +151,11 @@ class ContactsQueryHelper { found = true; } } catch (SQLiteException exception) { - Slog.w("SQLite exception when querying contacts.", exception); + Slog.w(TAG, "SQLite exception when querying contacts.", exception); } catch (IllegalArgumentException exception) { - Slog.w("Illegal Argument exception when querying contacts.", exception); + Slog.w(TAG, "Illegal Argument exception when querying contacts.", exception); + } catch (Exception exception) { + Slog.e(TAG, "Exception when querying contacts.", exception); } if (found && lookupKey != null && hasPhoneNumber) { return queryPhoneNumber(lookupKey); @@ -181,6 +183,8 @@ class ContactsQueryHelper { mPhoneNumber = cursor.getString(phoneNumIdx); } } + } catch (Exception exception) { + Slog.e(TAG, "Exception when querying contact phone number.", exception); } return true; } diff --git a/services/people/java/com/android/server/people/data/MmsQueryHelper.java b/services/people/java/com/android/server/people/data/MmsQueryHelper.java index 39dba9c73ba2..414a523fb186 100644 --- a/services/people/java/com/android/server/people/data/MmsQueryHelper.java +++ b/services/people/java/com/android/server/people/data/MmsQueryHelper.java @@ -100,6 +100,8 @@ class MmsQueryHelper { } } } + } catch (Exception e) { + Slog.e(TAG, "Exception when querying MMS table.", e); } finally { Binder.defaultBlockingForCurrentThread(); } @@ -133,6 +135,8 @@ class MmsQueryHelper { address = cursor.getString(addrIndex); } } + } catch (Exception e) { + Slog.e(TAG, "Exception when querying MMS address table.", e); } if (!Mms.isPhoneNumber(address)) { return null; diff --git a/services/people/java/com/android/server/people/data/SmsQueryHelper.java b/services/people/java/com/android/server/people/data/SmsQueryHelper.java index a5eb3a581616..f8ff3abc8e4c 100644 --- a/services/people/java/com/android/server/people/data/SmsQueryHelper.java +++ b/services/people/java/com/android/server/people/data/SmsQueryHelper.java @@ -98,6 +98,8 @@ class SmsQueryHelper { } } } + } catch (Exception e) { + Slog.e(TAG, "Exception when querying SMS table.", e); } finally { Binder.defaultBlockingForCurrentThread(); } diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt index cbca434a6bb6..8af5b2081b81 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageInstallerSessionTest.kt @@ -21,6 +21,7 @@ import android.content.pm.PackageInstaller.SessionParams import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DEFAULT import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_DENIED import android.content.pm.PackageInstaller.SessionParams.PERMISSION_STATE_GRANTED +import android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED import android.content.pm.PackageManager import android.content.pm.verify.domain.DomainSet import android.os.Parcel @@ -33,6 +34,11 @@ import com.android.internal.os.BackgroundThread import com.android.server.pm.verify.pkg.VerifierController import com.android.server.testutils.whenever import com.google.common.truth.Truth.assertThat +import java.io.File +import java.io.FileInputStream +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException import libcore.io.IoUtils import org.junit.Before import org.junit.Rule @@ -46,11 +52,6 @@ import org.mockito.Mockito.mock import org.mockito.MockitoAnnotations import org.xmlpull.v1.XmlPullParser import org.xmlpull.v1.XmlPullParserException -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException @Presubmit class PackageInstallerSessionTest { @@ -197,7 +198,8 @@ class PackageInstallerSessionTest { /* stagedSessionErrorCode */ PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE, /* stagedSessionErrorMessage */ "some error", /* preVerifiedDomains */ DomainSet(setOf("com.foo", "com.bar")), - /* VerifierController */ mock(VerifierController::class.java) + /* VerifierController */ mock(VerifierController::class.java), + VERIFICATION_POLICY_BLOCK_FAIL_CLOSED ) } @@ -339,6 +341,7 @@ class PackageInstallerSessionTest { assertThat(expected.childSessionIds).asList() .containsExactlyElementsIn(actual.childSessionIds.toList()) assertThat(expected.preVerifiedDomains).isEqualTo(actual.preVerifiedDomains) + assertThat(expected.verificationPolicy).isEqualTo(actual.verificationPolicy) } private fun assertInstallSourcesEquivalent(expected: InstallSource, actual: InstallSource) { diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java index be094b0152bc..37b23b107ecd 100644 --- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java +++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/verify/pkg/VerifierControllerTest.java @@ -16,6 +16,9 @@ package com.android.server.pm.verify.pkg; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED; +import static android.content.pm.PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_OPEN; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -92,6 +95,9 @@ public class VerifierControllerTest { private static final long TEST_TIMEOUT_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(1); private static final long TEST_MAX_TIMEOUT_DURATION_MILLIS = TimeUnit.MINUTES.toMillis(10); + private static final long TEST_VERIFIER_CONNECTION_TIMEOUT_DURATION_MILLIS = + TimeUnit.SECONDS.toMillis(10); + private static final int TEST_POLICY = VERIFICATION_POLICY_BLOCK_FAIL_CLOSED; private final ArrayList<SharedLibraryInfo> mTestDeclaredLibraries = new ArrayList<>(); private final PersistableBundle mTestExtensionParams = new PersistableBundle(); @@ -124,6 +130,9 @@ public class VerifierControllerTest { TEST_TIMEOUT_DURATION_MILLIS); when(mInjector.getMaxVerificationExtendedTimeoutMillis()).thenReturn( TEST_MAX_TIMEOUT_DURATION_MILLIS); + when(mInjector.getVerifierConnectionTimeoutMillis()).thenReturn( + TEST_VERIFIER_CONNECTION_TIMEOUT_DURATION_MILLIS + ); // Mock time forward as the code continues to check for the current time when(mInjector.getCurrentTimeMillis()) .thenReturn(TEST_REQUEST_START_TIME) @@ -159,12 +168,12 @@ public class VerifierControllerTest { .isFalse(); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isFalse(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isFalse(); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ true)).isFalse(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ true)).isFalse(); verifyZeroInteractions(mSessionCallback); } @@ -200,8 +209,8 @@ public class VerifierControllerTest { ServiceConnector.ServiceLifecycleCallbacks<IVerifierService> callbacks = captor.getValue(); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); verify(mMockService, times(1)).onVerificationRequired(any(VerificationSession.class)); callbacks.onBinderDied(); // Test that nothing crashes if the service connection is lost @@ -212,12 +221,12 @@ public class VerifierControllerTest { verifyNoMoreInteractions(mMockService); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ true)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ true)).isTrue(); mVerifierController.notifyVerificationTimeout(TEST_ID); verify(mMockService, times(1)).onVerificationTimeout(eq(TEST_ID)); } @@ -226,8 +235,8 @@ public class VerifierControllerTest { public void testNotifyPackageNameAvailable() throws Exception { assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); mVerifierController.notifyPackageNameAvailable(TEST_PACKAGE_NAME); verify(mMockService).onPackageNameAvailable(eq(TEST_PACKAGE_NAME)); } @@ -236,8 +245,8 @@ public class VerifierControllerTest { public void testNotifyVerificationCancelled() throws Exception { assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); mVerifierController.notifyVerificationCancelled(TEST_PACKAGE_NAME); verify(mMockService).onVerificationCancelled(eq(TEST_PACKAGE_NAME)); } @@ -248,8 +257,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); verify(mMockService).onVerificationRequired(captor.capture()); VerificationSession session = captor.getValue(); assertThat(session.getId()).isEqualTo(TEST_ID); @@ -276,8 +285,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ true)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ true)).isTrue(); verify(mMockService).onVerificationRetry(captor.capture()); VerificationSession session = captor.getValue(); assertThat(session.getId()).isEqualTo(TEST_ID); @@ -302,8 +311,8 @@ public class VerifierControllerTest { public void testNotifyVerificationTimeout() throws Exception { assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ true)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ true)).isTrue(); mVerifierController.notifyVerificationTimeout(TEST_ID); verify(mMockService).onVerificationTimeout(eq(TEST_ID)); } @@ -320,8 +329,8 @@ public class VerifierControllerTest { ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); verify(mHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong()); verify(mSessionCallback, times(1)).onTimeout(); verify(mInjector, times(2)).getCurrentTimeMillis(); @@ -339,8 +348,8 @@ public class VerifierControllerTest { .thenAnswer(i -> true); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); verify(mHandler, times(1)).sendMessageAtTime(any(Message.class), anyLong()); verify(mSessionCallback, times(1)).onTimeout(); verify(mInjector, times(2)).getCurrentTimeMillis(); @@ -350,8 +359,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ true)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ true)).isTrue(); verify(mMockService).onVerificationRetry(captor.capture()); VerificationSession session = captor.getValue(); VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build(); @@ -367,8 +376,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); verify(mMockService).onVerificationRequired(captor.capture()); VerificationSession session = captor.getValue(); session.reportVerificationIncomplete(VerificationSession.VERIFICATION_INCOMPLETE_UNKNOWN); @@ -383,8 +392,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); verify(mMockService).onVerificationRequired(captor.capture()); VerificationSession session = captor.getValue(); VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build(); @@ -401,8 +410,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); verify(mMockService).onVerificationRequired(captor.capture()); VerificationSession session = captor.getValue(); VerificationStatus status = new VerificationStatus.Builder() @@ -421,8 +430,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); assertThat(mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false)).isTrue(); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false)).isTrue(); verify(mMockService).onVerificationRequired(captor.capture()); VerificationSession session = captor.getValue(); VerificationStatus status = new VerificationStatus.Builder().setVerified(true).build(); @@ -439,8 +448,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false); verify(mMockService).onVerificationRequired(captor.capture()); VerificationSession session = captor.getValue(); final long initialTimeoutTime = TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS; @@ -456,8 +465,8 @@ public class VerifierControllerTest { ArgumentCaptor.forClass(VerificationSession.class); mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false); verify(mMockService).onVerificationRequired(captor.capture()); VerificationSession session = captor.getValue(); final long initialTimeoutTime = TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS; @@ -493,10 +502,27 @@ public class VerifierControllerTest { .thenReturn(TEST_REQUEST_START_TIME + TEST_TIMEOUT_DURATION_MILLIS + 1); mVerifierController.startVerificationSession( mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, - TEST_SIGNING_INFO, mTestDeclaredLibraries, mTestExtensionParams, mSessionCallback, - /* retry= */ false); + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false); verify(mHandler, times(3)).sendMessageAtTime(any(Message.class), anyLong()); verify(mInjector, times(6)).getCurrentTimeMillis(); verify(mSessionCallback, times(1)).onTimeout(); } + + @Test + public void testPolicyOverride() throws Exception { + ArgumentCaptor<VerificationSession> captor = + ArgumentCaptor.forClass(VerificationSession.class); + mVerifierController.startVerificationSession( + mSnapshotSupplier, 0, TEST_ID, TEST_PACKAGE_NAME, TEST_PACKAGE_URI, + TEST_SIGNING_INFO, mTestDeclaredLibraries, TEST_POLICY, mTestExtensionParams, + mSessionCallback, /* retry= */ false); + verify(mMockService).onVerificationRequired(captor.capture()); + VerificationSession session = captor.getValue(); + final int policy = VERIFICATION_POLICY_BLOCK_FAIL_OPEN; + when(mSessionCallback.setVerificationPolicy(eq(policy))).thenReturn(true); + assertThat(session.setVerificationPolicy(policy)).isTrue(); + assertThat(session.getVerificationPolicy()).isEqualTo(policy); + verify(mSessionCallback, times(1)).setVerificationPolicy(eq(policy)); + } } diff --git a/services/tests/appfunctions/Android.bp b/services/tests/appfunctions/Android.bp index c841643c6654..836f90b992d6 100644 --- a/services/tests/appfunctions/Android.bp +++ b/services/tests/appfunctions/Android.bp @@ -36,7 +36,9 @@ android_test { "androidx.test.core", "androidx.test.runner", "androidx.test.ext.truth", + "androidx.core_core-ktx", "kotlin-test", + "kotlinx_coroutines_test", "platform-test-annotations", "services.appfunctions", "servicestests-core-utils", diff --git a/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt new file mode 100644 index 000000000000..a69e9025bfa0 --- /dev/null +++ b/services/tests/appfunctions/src/com/android/server/appfunctions/AppFunctionManagerServiceImplTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2024 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.appfunctions + +import android.app.appfunctions.flags.Flags +import android.content.Context +import android.platform.test.annotations.RequiresFlagsEnabled +import android.platform.test.flag.junit.CheckFlagsRule +import android.platform.test.flag.junit.DeviceFlagsValueProvider +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +@RequiresFlagsEnabled(Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER) +class AppFunctionManagerServiceImplTest { + @get:Rule + val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() + + private val context: Context + get() = ApplicationProvider.getApplicationContext() + + private val serviceImpl = AppFunctionManagerServiceImpl(context) + + @Test + fun testGetLockForPackage_samePackage() { + val packageName = "com.example.app" + val lock1 = serviceImpl.getLockForPackage(packageName) + val lock2 = serviceImpl.getLockForPackage(packageName) + + // Assert that the same lock object is returned for the same package name + assertThat(lock1).isEqualTo(lock2) + } + + @Test + fun testGetLockForPackage_differentPackages() { + val packageName1 = "com.example.app1" + val packageName2 = "com.example.app2" + val lock1 = serviceImpl.getLockForPackage(packageName1) + val lock2 = serviceImpl.getLockForPackage(packageName2) + + // Assert that different lock objects are returned for different package names + assertThat(lock1).isNotEqualTo(lock2) + } + + @Ignore("Hard to deterministically trigger the garbage collector.") + @Test + fun testWeakReference_garbageCollected_differentLockAfterGC() = runTest { + // Create a large number of temporary objects to put pressure on the GC + val tempObjects = MutableList<Any?>(10000000) { Any() } + var callingPackage: String? = "com.example.app" + var lock1: Any? = serviceImpl.getLockForPackage(callingPackage) + callingPackage = null // Set the key to null + val lock1Hash = lock1.hashCode() + lock1 = null + + // Create memory pressure + repeat(3) { + for (i in 1..100) { + "a".repeat(10000) + } + System.gc() // Suggest garbage collection + System.runFinalization() + } + // Get the lock again - it should be a different object now + val lock2 = serviceImpl.getLockForPackage("com.example.app") + // Assert that the lock objects are different + assertThat(lock1Hash).isNotEqualTo(lock2.hashCode()) + } +} diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java index 3b5a386dd303..b917af4d796e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java @@ -1658,6 +1658,70 @@ public class DisplayManagerServiceTest { } @Test + public void testGetDisplayIdsByGroupsIds() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + DisplayManagerInternal localService = displayManager.new LocalService(); + LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper(); + // Create display 1 + FakeDisplayDevice displayDevice1 = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL); + LogicalDisplay display1 = logicalDisplayMapper.getDisplayLocked(displayDevice1); + final int groupId1 = display1.getDisplayInfoLocked().displayGroupId; + // Create display 2 + FakeDisplayDevice displayDevice2 = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL); + LogicalDisplay display2 = logicalDisplayMapper.getDisplayLocked(displayDevice2); + final int groupId2 = display2.getDisplayInfoLocked().displayGroupId; + // Both displays should be in the same display group + assertEquals(groupId1, groupId2); + final int[] displayIds = new int[]{ + display1.getDisplayIdLocked(), display2.getDisplayIdLocked()}; + final SparseArray<int[]> expectedDisplayGroups = new SparseArray<>(); + expectedDisplayGroups.put(groupId1, displayIds); + + final SparseArray<int[]> displayGroups = localService.getDisplayIdsByGroupsIds(); + + for (int i = 0; i < expectedDisplayGroups.size(); i++) { + final int groupId = expectedDisplayGroups.keyAt(i); + assertTrue(displayGroups.contains(groupId)); + assertArrayEquals(expectedDisplayGroups.get(groupId), displayGroups.get(groupId)); + } + } + + @Test + public void testGetDisplayIdsByGroupsIds_multipleDisplayGroups() throws Exception { + DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector); + displayManager.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + DisplayManagerInternal localService = displayManager.new LocalService(); + LogicalDisplayMapper logicalDisplayMapper = displayManager.getLogicalDisplayMapper(); + // Create display 1 + FakeDisplayDevice displayDevice1 = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_INTERNAL); + LogicalDisplay display1 = logicalDisplayMapper.getDisplayLocked(displayDevice1); + final int groupId1 = display1.getDisplayInfoLocked().displayGroupId; + // Create display 2 + FakeDisplayDevice displayDevice2 = + createFakeDisplayDevice(displayManager, new float[]{60f}, Display.TYPE_EXTERNAL); + LogicalDisplay display2 = logicalDisplayMapper.getDisplayLocked(displayDevice2); + final int groupId2 = display2.getDisplayInfoLocked().displayGroupId; + // Both displays should be in different display groups + assertNotEquals(groupId1, groupId2); + final SparseArray<int[]> expectedDisplayGroups = new SparseArray<>(); + expectedDisplayGroups.put(groupId1, new int[]{display1.getDisplayIdLocked()}); + expectedDisplayGroups.put(groupId2, new int[]{display2.getDisplayIdLocked()}); + + final SparseArray<int[]> displayGroups = localService.getDisplayIdsByGroupsIds(); + + assertEquals(expectedDisplayGroups.size(), displayGroups.size()); + for (int i = 0; i < expectedDisplayGroups.size(); i++) { + final int groupId = expectedDisplayGroups.keyAt(i); + assertTrue(displayGroups.contains(groupId)); + assertArrayEquals(expectedDisplayGroups.get(groupId), displayGroups.get(groupId)); + } + } + + @Test public void testCreateVirtualDisplay_isValidProjection_notValid() throws RemoteException { when(mMockAppToken.asBinder()).thenReturn(mMockAppToken); diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java index 01b2d3e34bdc..fdf6b809fa85 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -2343,6 +2343,41 @@ public final class DisplayPowerControllerTest { } } + @Test + public void stylusUsageStarted_disablesAutomaticBrightnessStrategy() { + when(mDisplayManagerFlagsMock.isBlockAutobrightnessChangesOnStylusUsage()) + .thenReturn(true); + when(mDisplayManagerFlagsMock.isRefactorDisplayPowerControllerEnabled()) + .thenReturn(true); + mHolder = createDisplayPowerController(DISPLAY_ID, UNIQUE_ID); + mHolder.dpc.setDisplayOffloadSession(mDisplayOffloadSession); + Settings.System.putInt(mContext.getContentResolver(), + Settings.System.SCREEN_BRIGHTNESS_MODE, + Settings.System.SCREEN_BRIGHTNESS_MODE_AUTOMATIC); + + DisplayPowerRequest dpr = new DisplayPowerRequest(); + dpr.policy = DisplayPowerRequest.POLICY_BRIGHT; + when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON); + advanceTime(2); + clearInvocations(mHolder.automaticBrightnessController); + mHolder.dpc.stylusGestureStarted(2000000); + + mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false); + advanceTime(1); // Run updatePowerState + verify(mHolder.automaticBrightnessController, times(0)) + .getAutomaticScreenBrightness(any()); + + // Stylus usage timed out, hence autobrightness is now enabled back again + advanceTime(6); + verify(mHolder.automaticBrightnessController).getAutomaticScreenBrightness(null); + + // Ideally we should be able to assert against new BrightnessEvent(Display.DEFAULT_DISPLAY), + // but because brightnessEvent has the mTime field which refers to the current time, + // asserting against that is non-trivial + verify(mHolder.automaticBrightnessController).getAutomaticScreenBrightness( + any(BrightnessEvent.class)); + } + /** * Creates a mock and registers it to {@link LocalServices}. */ @@ -2406,6 +2441,7 @@ public final class DisplayPowerControllerTest { .thenReturn(new int[0]); when(displayDeviceConfigMock.getDefaultDozeBrightness()) .thenReturn(DEFAULT_DOZE_BRIGHTNESS); + when(displayDeviceConfigMock.getIdleStylusTimeoutMillis()).thenReturn(5); when(displayDeviceConfigMock.getBrightnessRampFastDecrease()) .thenReturn(BRIGHTNESS_RAMP_RATE_FAST_DECREASE); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java index 04b79b4f1761..d93ee84f4870 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/BrightnessReasonTest.java @@ -74,7 +74,7 @@ public final class BrightnessReasonTest { @Test public void setModifierDoesntSetIfModifierIsBeyondExtremes() { - int extremeModifier = 0x40; // equal to BrightnessReason.MODIFIER_MASK * 2 + int extremeModifier = 0x80; // reset modifier mBrightnessReason.setModifier(0); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java index c0698756a3d7..b3baa5deb4a7 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessControllerTest.java @@ -125,7 +125,7 @@ public final class DisplayBrightnessControllerTest { DisplayManagerInternal.DisplayOffloadSession.class)); verify(displayBrightnessStrategy).updateBrightness( eq(new StrategyExecutionRequest(displayPowerRequest, DEFAULT_BRIGHTNESS, - /* userSetBrightnessChanged= */ false))); + /* userSetBrightnessChanged= */ false, /* isStylusBeingUsed */ false))); assertEquals(mDisplayBrightnessController.getCurrentDisplayBrightnessStrategy(), displayBrightnessStrategy); } @@ -559,4 +559,11 @@ public final class DisplayBrightnessControllerTest { displayDeviceConfig, handler, brightnessMappingStrategy, isDisplayEnabled, leadDisplayId); } + + @Test + public void setStylusBeingUsed_setsStylusInUseState() { + assertFalse(mDisplayBrightnessController.isStylusBeingUsed()); + mDisplayBrightnessController.setStylusBeingUsed(true); + assertTrue(mDisplayBrightnessController.isStylusBeingUsed()); + } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java index a44c517ed9cf..fe1505162e24 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/DisplayBrightnessStrategySelectorTest.java @@ -68,6 +68,8 @@ import org.mockito.MockitoAnnotations; @RunWith(AndroidJUnit4.class) public final class DisplayBrightnessStrategySelectorTest { private static final boolean DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING = false; + private static final boolean STYLUS_IS_NOT_BEING_USED = false; + private static final boolean STYLUS_IS_BEING_USED = true; private static final int DISPLAY_ID = 1; @Mock @@ -196,7 +198,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -212,7 +215,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -226,7 +230,8 @@ public final class DisplayBrightnessStrategySelectorTest { DISALLOW_AUTO_BRIGHTNESS_WHILE_DOZING); assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -249,7 +254,8 @@ public final class DisplayBrightnessStrategySelectorTest { assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_DOZE, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mDozeBrightnessModeStrategy); } @@ -259,7 +265,8 @@ public final class DisplayBrightnessStrategySelectorTest { DisplayManagerInternal.DisplayPowerRequest.class); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_OFF, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mScreenOffBrightnessModeStrategy); } @@ -271,7 +278,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mOverrideBrightnessStrategy); } @@ -284,7 +292,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mTemporaryBrightnessStrategy); } @@ -298,7 +307,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mBoostBrightnessStrategy); } @@ -312,7 +322,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(Float.NaN); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mInvalidBrightnessStrategy); } @@ -323,7 +334,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mFollowerBrightnessStrategy); } @@ -341,7 +353,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mOffloadBrightnessStrategy.getOffloadScreenBrightness()).thenReturn(0.3f); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mOffloadBrightnessStrategy); } @@ -365,7 +378,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mAutomaticBrightnessStrategy); verify(mAutomaticBrightnessStrategy).setAutoBrightnessState(Display.STATE_ON, true, BrightnessReason.REASON_UNKNOWN, @@ -373,6 +387,32 @@ public final class DisplayBrightnessStrategySelectorTest { /* useNormalBrightnessForDoze= */ false, 0.1f, false); } + + @Test + public void selectStrategy_doesNotSelectAutomaticStrategyWhenStylusInUse() { + when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); + when(mDisplayManagerFlags.offloadControlsDozeAutoBrightness()).thenReturn(true); + when(mDisplayManagerFlags.isDisplayOffloadEnabled()).thenReturn(true); + when(mDisplayOffloadSession.allowAutoBrightnessInDoze()).thenReturn(true); + when(mResources.getBoolean(R.bool.config_allowAutoBrightnessWhileDozing)).thenReturn( + true); + mDisplayBrightnessStrategySelector = new DisplayBrightnessStrategySelector(mContext, + mInjector, DISPLAY_ID, mDisplayManagerFlags); + DisplayManagerInternal.DisplayPowerRequest displayPowerRequest = mock( + DisplayManagerInternal.DisplayPowerRequest.class); + displayPowerRequest.policy = DisplayManagerInternal.DisplayPowerRequest.POLICY_BRIGHT; + displayPowerRequest.screenBrightnessOverride = Float.NaN; + when(mFollowerBrightnessStrategy.getBrightnessToFollow()).thenReturn(Float.NaN); + when(mTemporaryBrightnessStrategy.getTemporaryScreenBrightness()).thenReturn(Float.NaN); + when(mAutomaticBrightnessStrategy.shouldUseAutoBrightness()).thenReturn(true); + when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(true); + assertNotEquals(mDisplayBrightnessStrategySelector.selectStrategy( + new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_BEING_USED)), + mAutomaticBrightnessStrategy); + } + @Test public void selectStrategy_selectsAutomaticFallbackStrategyWhenValid() { when(mDisplayManagerFlags.isRefactorDisplayPowerControllerEnabled()).thenReturn(true); @@ -389,7 +429,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutoBrightnessFallbackStrategy.isValid()).thenReturn(true); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mAutoBrightnessFallbackStrategy); } @@ -407,7 +448,8 @@ public final class DisplayBrightnessStrategySelectorTest { assertNotEquals(mOffloadBrightnessStrategy, mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession))); + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED))); } @Test @@ -425,7 +467,8 @@ public final class DisplayBrightnessStrategySelectorTest { when(mAutomaticBrightnessStrategy.isAutoBrightnessValid()).thenReturn(false); assertEquals(mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)), + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)), mFallbackBrightnessStrategy); } @@ -440,7 +483,8 @@ public final class DisplayBrightnessStrategySelectorTest { mDisplayBrightnessStrategySelector.selectStrategy( new StrategySelectionRequest(displayPowerRequest, Display.STATE_ON, - 0.1f, false, mDisplayOffloadSession)); + 0.1f, false, mDisplayOffloadSession, + STYLUS_IS_NOT_BEING_USED)); StrategySelectionNotifyRequest strategySelectionNotifyRequest = new StrategySelectionNotifyRequest(displayPowerRequest, Display.STATE_ON, diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java index 99dfa739fb80..2a71af06e0c2 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutoBrightnessFallbackStrategyTest.java @@ -129,7 +129,8 @@ public class AutoBrightnessFallbackStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mAutoBrightnessFallbackStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java index efa8b3ef775f..8a1f86093ecf 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/AutomaticBrightnessStrategyTest.java @@ -637,7 +637,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -686,7 +686,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -725,7 +725,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } @@ -764,7 +764,7 @@ public class AutomaticBrightnessStrategyTest { .build(); DisplayBrightnessState actualDisplayBrightnessState = mAutomaticBrightnessStrategy .updateBrightness(new StrategyExecutionRequest(displayPowerRequest, 0.6f, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, actualDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java index 275bb3efee8e..c03309e8b4a4 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/BoostBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class BoostBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mBoostBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java index 23e447c25245..e7f80b04e669 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/DozeBrightnessStrategyTest.java @@ -57,7 +57,8 @@ public class DozeBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mDozeBrightnessModeStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java index c4a579092d38..dcfa174a53f5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FallbackBrightnessStrategyTest.java @@ -61,7 +61,8 @@ public class FallbackBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mFallbackBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, currentBrightness, - /* userSetBrightnessChanged= */ true)); + /* userSetBrightnessChanged= */ true, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java index c01f96e800de..239cdb6002e9 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/FollowerBrightnessStrategyTest.java @@ -61,7 +61,8 @@ public class FollowerBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mFollowerBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(expectedDisplayBrightnessState, updatedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java index 9fb2afa26ed2..77302f8747c1 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OffloadBrightnessStrategyTest.java @@ -72,7 +72,8 @@ public class OffloadBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mOffloadBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); assertEquals(PowerManager.BRIGHTNESS_INVALID_FLOAT, mOffloadBrightnessStrategy .getOffloadScreenBrightness(), 0.0f); diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java index e8b4c06b9c89..cc21af19722b 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/OverrideBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class OverrideBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mOverrideBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java index 38709ece7007..652663e52a0a 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/ScreenOffBrightnessStrategyTest.java @@ -58,7 +58,8 @@ public final class ScreenOffBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mScreenOffBrightnessModeStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } } diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java index f523b6af426b..0022cab371e8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/strategy/TemporaryBrightnessStrategyTest.java @@ -60,7 +60,8 @@ public class TemporaryBrightnessStrategyTest { DisplayBrightnessState updatedDisplayBrightnessState = mTemporaryBrightnessStrategy.updateBrightness( new StrategyExecutionRequest(displayPowerRequest, 0.2f, - /* userSetBrightnessChanged= */ false)); + /* userSetBrightnessChanged= */ false, + /* isStylusBeingUsed */ false)); assertEquals(updatedDisplayBrightnessState, expectedDisplayBrightnessState); } diff --git a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java index 121145672d68..439243e85e75 100644 --- a/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java +++ b/services/tests/media/mediarouterservicetest/src/com/android/server/media/AudioManagerRouteControllerTest.java @@ -82,6 +82,11 @@ public class AudioManagerRouteControllerTest { private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET = createAudioDeviceInfo( AudioSystem.DEVICE_OUT_WIRED_HEADSET, "name_wired_hs", /* address= */ null); + private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS = + createAudioDeviceInfo( + AudioSystem.DEVICE_OUT_WIRED_HEADSET, + "name_wired_hs_with_address", + /* address= */ "card=1;device=0"); private static final AudioDeviceInfo FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP = createAudioDeviceInfo( AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP, "name_a2dp", /* address= */ "12:34:45"); @@ -304,6 +309,55 @@ public class AudioManagerRouteControllerTest { assertThat(selectedRoute.getName().toString()).isEqualTo(FAKE_ROUTE_NAME); } + @Test + public void getAvailableRoutes_whenAddressIsPopulatedForNonBluetoothDevice_usesCorrectName() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS, + FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP); + + List<MediaRoute2Info> availableRoutes = mControllerUnderTest.getAvailableRoutes(); + assertThat(availableRoutes.size()).isEqualTo(3); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET) + .getName() + .toString()) + .isEqualTo( + FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET_WITH_ADDRESS + .getProductName() + .toString()); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_BLUETOOTH_A2DP) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BLUETOOTH_A2DP.getProductName().toString()); + } + + @Test + public void + getAvailableRoutes_whenAddressIsNotPopulatedForNonBluetoothDevice_usesCorrectName() { + addAvailableAudioDeviceInfo( + /* newSelectedDevice= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET, + /* newAvailableDevices...= */ FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET); + + List<MediaRoute2Info> availableRoutes = mControllerUnderTest.getAvailableRoutes(); + assertThat(availableRoutes.size()).isEqualTo(2); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_BUILTIN_SPEAKER) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_BUILTIN_SPEAKER.getProductName().toString()); + + assertThat( + getAvailableRouteWithType(MediaRoute2Info.TYPE_WIRED_HEADSET) + .getName() + .toString()) + .isEqualTo(FAKE_AUDIO_DEVICE_INFO_WIRED_HEADSET.getProductName().toString()); + } + // Internal methods. @NonNull diff --git a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java index 6ccc03709b4f..2a825f35bf62 100644 --- a/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/am/ActivityManagerServiceTest.java @@ -107,6 +107,7 @@ import android.os.IBinder; import android.os.IProgressListener; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -1309,12 +1310,13 @@ public class ActivityManagerServiceTest { intent.putExtra("EXTRA_INTENT0", extraIntent); intent.collectExtraIntentKeys(); - mAms.addCreatorToken(intent); + mAms.addCreatorToken(intent, TEST_PACKAGE); ActivityManagerService.IntentCreatorToken token = (ActivityManagerService.IntentCreatorToken) extraIntent.getCreatorToken(); assertThat(token).isNotNull(); assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid()); + assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE); } @Test @@ -1330,7 +1332,7 @@ public class ActivityManagerServiceTest { fillinIntent.collectExtraIntentKeys(); intent.fillIn(fillinIntent, FILL_IN_ACTION); - mAms.addCreatorToken(fillinIntent); + mAms.addCreatorToken(fillinIntent, TEST_PACKAGE); fillinExtraIntent = intent.getParcelableExtra("FILLIN_EXTRA_INTENT0", Intent.class); @@ -1338,6 +1340,49 @@ public class ActivityManagerServiceTest { (ActivityManagerService.IntentCreatorToken) fillinExtraIntent.getCreatorToken(); assertThat(token).isNotNull(); assertThat(token.getCreatorUid()).isEqualTo(mInjector.getCallingUid()); + assertThat(token.getCreatorPackage()).isEqualTo(TEST_PACKAGE); + } + + @Test + @RequiresFlagsEnabled(android.security.Flags.FLAG_PREVENT_INTENT_REDIRECT) + public void testCheckCreatorToken() { + Intent intent = new Intent(); + Intent extraIntent = new Intent("EXTRA_INTENT_ACTION"); + intent.putExtra("EXTRA_INTENT", extraIntent); + + intent.collectExtraIntentKeys(); + + // mimic client hack and sneak in an extra intent without going thru collectExtraIntentKeys. + Intent extraIntent2 = new Intent("EXTRA_INTENT_ACTION2"); + intent.putExtra("EXTRA_INTENT2", extraIntent2); + + // mock parceling on the client side, unparcling on the system server side, then + // addCreatorToken on system server side. + final Parcel parcel = Parcel.obtain(); + intent.writeToParcel(parcel, 0); + parcel.setDataPosition(0); + Intent newIntent = new Intent(); + newIntent.readFromParcel(parcel); + intent = newIntent; + mAms.addCreatorToken(intent, TEST_PACKAGE); + // entering the target app's process. + intent.checkCreatorToken(); + + Intent extraIntent3 = new Intent("EXTRA_INTENT_ACTION3"); + intent.putExtra("EXTRA_INTENT3", extraIntent3); + + extraIntent = intent.getParcelableExtra("EXTRA_INTENT", Intent.class); + extraIntent2 = intent.getParcelableExtra("EXTRA_INTENT2", Intent.class); + extraIntent3 = intent.getParcelableExtra("EXTRA_INTENT3", Intent.class); + + assertThat(extraIntent.getExtendedFlags() + & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0); + // sneaked in intent should have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set. + assertThat(extraIntent2.getExtendedFlags() + & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isNotEqualTo(0); + // local created intent should not have EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN set. + assertThat(extraIntent3.getExtendedFlags() + & Intent.EXTENDED_FLAG_MISSING_CREATOR_OR_INVALID_TOKEN).isEqualTo(0); } private void verifyWaitingForNetworkStateUpdate(long curProcStateSeq, 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 31157f9e27dc..5c718d982476 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 @@ -25,7 +25,6 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSess import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.job.Flags.FLAG_COUNT_QUOTA_FIX; -import static com.android.server.job.Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS; import static com.android.server.job.JobSchedulerService.ACTIVE_INDEX; import static com.android.server.job.JobSchedulerService.EXEMPTED_INDEX; import static com.android.server.job.JobSchedulerService.FREQUENT_INDEX; @@ -77,9 +76,12 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.SystemClock; +import android.platform.test.annotations.DisableFlags; +import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.RequiresFlagsEnabled; import android.platform.test.flag.junit.CheckFlagsRule; import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.util.ArraySet; import android.util.SparseBooleanArray; @@ -90,12 +92,12 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.ArrayUtils; import com.android.server.LocalServices; import com.android.server.PowerAllowlistInternal; +import com.android.server.job.Flags; import com.android.server.job.JobSchedulerInternal; import com.android.server.job.JobSchedulerService; import com.android.server.job.JobStore; import com.android.server.job.controllers.QuotaController.ExecutionStats; import com.android.server.job.controllers.QuotaController.QcConstants; -import com.android.server.job.controllers.QuotaController.QuotaBump; import com.android.server.job.controllers.QuotaController.ShrinkableDebits; import com.android.server.job.controllers.QuotaController.TimedEvent; import com.android.server.job.controllers.QuotaController.TimingSession; @@ -132,11 +134,11 @@ public class QuotaControllerTest { private static final String SOURCE_PACKAGE = "com.android.frameworks.mockingservicestests"; private static final int SOURCE_USER_ID = 0; + @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private QuotaController mQuotaController; private QuotaController.QcConstants mQcConstants; private JobSchedulerService.Constants mConstants = new JobSchedulerService.Constants(); private int mSourceUid; - private AppStandbyInternal.AppIdleStateChangeListener mAppIdleStateChangeListener; private PowerAllowlistInternal.TempAllowlistChangeListener mTempAllowlistListener; private IUidObserver mUidObserver; private UsageStatsManagerInternal.UsageEventListener mUsageEventListener; @@ -191,8 +193,7 @@ public class QuotaControllerTest { when(mContext.getSystemService(AlarmManager.class)).thenReturn(mAlarmManager); doReturn(mActivityMangerInternal) .when(() -> LocalServices.getService(ActivityManagerInternal.class)); - final AppStandbyInternal appStandbyInternal = mock(AppStandbyInternal.class); - doReturn(appStandbyInternal) + doReturn(mock(AppStandbyInternal.class)) .when(() -> LocalServices.getService(AppStandbyInternal.class)); doReturn(mock(BatteryManagerInternal.class)) .when(() -> LocalServices.getService(BatteryManagerInternal.class)); @@ -239,8 +240,6 @@ public class QuotaControllerTest { // Initialize real objects. // Capture the listeners. - ArgumentCaptor<AppStandbyInternal.AppIdleStateChangeListener> aiscListenerCaptor = - ArgumentCaptor.forClass(AppStandbyInternal.AppIdleStateChangeListener.class); ArgumentCaptor<IUidObserver> uidObserverCaptor = ArgumentCaptor.forClass(IUidObserver.class); ArgumentCaptor<PowerAllowlistInternal.TempAllowlistChangeListener> taChangeCaptor = @@ -250,8 +249,6 @@ public class QuotaControllerTest { mQuotaController = new QuotaController(mJobSchedulerService, mock(BackgroundJobsController.class), mock(ConnectivityController.class)); - verify(appStandbyInternal).addListener(aiscListenerCaptor.capture()); - mAppIdleStateChangeListener = aiscListenerCaptor.getValue(); verify(mPowerAllowlistInternal) .registerTempAllowlistChangeListener(taChangeCaptor.capture()); mTempAllowlistListener = taChangeCaptor.getValue(); @@ -488,14 +485,12 @@ public class QuotaControllerTest { now - 10 * MINUTE_IN_MILLIS, 9 * MINUTE_IN_MILLIS, 3); TimingSession two = createTimingSession( now - (70 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); - QuotaBump bump1 = new QuotaBump(now - 2 * HOUR_IN_MILLIS); TimingSession thr = createTimingSession( now - (3 * HOUR_IN_MILLIS + 10 * MINUTE_IN_MILLIS), 9 * MINUTE_IN_MILLIS, 1); // Overlaps 24 hour boundary. TimingSession fou = createTimingSession( now - (24 * HOUR_IN_MILLIS + 2 * MINUTE_IN_MILLIS), 7 * MINUTE_IN_MILLIS, 1); // Way past the 24 hour boundary. - QuotaBump bump2 = new QuotaBump(now - 24 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS); TimingSession fiv = createTimingSession( now - (25 * HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 4); List<TimedEvent> expectedRegular = new ArrayList<>(); @@ -503,16 +498,13 @@ public class QuotaControllerTest { // Added in correct (chronological) order. expectedRegular.add(fou); expectedRegular.add(thr); - expectedRegular.add(bump1); expectedRegular.add(two); expectedRegular.add(one); expectedEJ.add(fou); expectedEJ.add(one); mQuotaController.saveTimingSession(0, "com.android.test", fiv, false); - mQuotaController.getTimingSessions(0, "com.android.test").add(bump2); mQuotaController.saveTimingSession(0, "com.android.test", fou, false); mQuotaController.saveTimingSession(0, "com.android.test", thr, false); - mQuotaController.getTimingSessions(0, "com.android.test").add(bump1); mQuotaController.saveTimingSession(0, "com.android.test", two, false); mQuotaController.saveTimingSession(0, "com.android.test", one, false); mQuotaController.saveTimingSession(0, "com.android.test", fiv, true); @@ -942,7 +934,8 @@ public class QuotaControllerTest { * Tests that getExecutionStatsLocked returns the correct stats. */ @Test - public void testGetExecutionStatsLocked_Values() { + @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetExecutionStatsLocked_Values_LegacyDefaultBucketWindowSizes() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - HOUR_IN_MILLIS), @@ -1027,11 +1020,112 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetExecutionStatsLocked_Values_NewDefaultBucketWindowSizes() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (23 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (7 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (2 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(now - (6 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + + ExecutionStats expectedStats = new ExecutionStats(); + + // Exempted + expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_EXEMPTED; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_EXEMPTED; + expectedStats.expirationTimeElapsed = now + 14 * MINUTE_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 3 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 5; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.sessionCountInWindow = 1; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + EXEMPTED_INDEX)); + } + + // Active + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; + // There is only one session in the past active bucket window, the empty time for this + // window is the bucket window size - duration of the session. + expectedStats.expirationTimeElapsed = now + 24 * MINUTE_IN_MILLIS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + ACTIVE_INDEX)); + } + + // Working + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_WORKING_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 13 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 10; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.sessionCountInWindow = 2; + expectedStats.inQuotaTimeElapsed = now + 2 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS + + mQcConstants.IN_QUOTA_BUFFER_MS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + WORKING_INDEX)); + } + + // Frequent + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_FREQUENT_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 23 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 15; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.sessionCountInWindow = 3; + expectedStats.inQuotaTimeElapsed = now + 10 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS + + mQcConstants.IN_QUOTA_BUFFER_MS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX)); + } + + // Rare + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_RARE_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; + expectedStats.expirationTimeElapsed = now + HOUR_IN_MILLIS; + expectedStats.executionTimeInWindowMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 20; + expectedStats.executionTimeInMaxPeriodMs = 33 * MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 20; + expectedStats.sessionCountInWindow = 4; + expectedStats.inQuotaTimeElapsed = now + 22 * HOUR_IN_MILLIS + 3 * MINUTE_IN_MILLIS + + mQcConstants.IN_QUOTA_BUFFER_MS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + RARE_INDEX)); + } + } + /** * Tests that getExecutionStatsLocked returns the correct stats soon after device startup. */ @Test - public void testGetExecutionStatsLocked_Values_BeginningOfTime() { + @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetExecutionStatsLocked_Values_BeginningOfTime_LegacyDefaultBucketWindowSizes() { // Set time to 3 minutes after boot. advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis()); advanceElapsedClock(3 * MINUTE_IN_MILLIS); @@ -1054,7 +1148,8 @@ public class QuotaControllerTest { expectedStats.sessionCountInWindow = 1; synchronized (mQuotaController.mLock) { assertEquals(expectedStats, - mQuotaController.getExecutionStatsLocked(0, "com.android.test", ACTIVE_INDEX)); + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + ACTIVE_INDEX)); } // Working @@ -1064,7 +1159,8 @@ public class QuotaControllerTest { expectedStats.expirationTimeElapsed = 2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; synchronized (mQuotaController.mLock) { assertEquals(expectedStats, - mQuotaController.getExecutionStatsLocked(0, "com.android.test", WORKING_INDEX)); + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + WORKING_INDEX)); } // Frequent @@ -1085,7 +1181,83 @@ public class QuotaControllerTest { expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; synchronized (mQuotaController.mLock) { assertEquals(expectedStats, - mQuotaController.getExecutionStatsLocked(0, "com.android.test", RARE_INDEX)); + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + RARE_INDEX)); + } + } + + @Test + @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetExecutionStatsLocked_Values_BeginningOfTime_NewDefaultBucketWindowSizes() { + // Set time to 3 minutes after boot. + advanceElapsedClock(-JobSchedulerService.sElapsedRealtimeClock.millis()); + advanceElapsedClock(3 * MINUTE_IN_MILLIS); + + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession(MINUTE_IN_MILLIS, MINUTE_IN_MILLIS, 2), false); + + ExecutionStats expectedStats = new ExecutionStats(); + + // Exempted + expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_EXEMPTED_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; + expectedStats.expirationTimeElapsed = 10 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS; + expectedStats.executionTimeInWindowMs = MINUTE_IN_MILLIS; + expectedStats.bgJobCountInWindow = 2; + expectedStats.executionTimeInMaxPeriodMs = MINUTE_IN_MILLIS; + expectedStats.bgJobCountInMaxPeriod = 2; + expectedStats.sessionCountInWindow = 1; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + EXEMPTED_INDEX)); + } + + // Active + expectedStats.allowedTimePerPeriodMs = mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS; + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_ACTIVE_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_ACTIVE; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_ACTIVE; + expectedStats.expirationTimeElapsed = 20 * MINUTE_IN_MILLIS + 11 * MINUTE_IN_MILLIS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + ACTIVE_INDEX)); + } + + // Working + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_WORKING_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_WORKING; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_WORKING; + expectedStats.expirationTimeElapsed = 4 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + WORKING_INDEX)); + } + + // Frequent + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_FREQUENT_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_FREQUENT; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_FREQUENT; + expectedStats.expirationTimeElapsed = 12 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX)); + } + + // Rare + expectedStats.windowSizeMs = mQcConstants.WINDOW_SIZE_RARE_MS; + expectedStats.jobCountLimit = mQcConstants.MAX_JOB_COUNT_RARE; + expectedStats.sessionCountLimit = mQcConstants.MAX_SESSION_COUNT_RARE; + expectedStats.expirationTimeElapsed = 24 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS; + synchronized (mQuotaController.mLock) { + assertEquals(expectedStats, + mQuotaController.getExecutionStatsLocked(0, "com.android.test", + RARE_INDEX)); } } @@ -1093,7 +1265,8 @@ public class QuotaControllerTest { * Tests that getExecutionStatsLocked returns the correct timing session stats when coalescing. */ @Test - public void testGetExecutionStatsLocked_CoalescingSessions() { + @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetExecutionStatsLocked_CoalescingSessions_LegacyDefaultBucketWindowSizes() { for (int i = 0; i < 10; ++i) { mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession( @@ -1110,12 +1283,14 @@ public class QuotaControllerTest { advanceElapsedClock(54 * SECOND_IN_MILLIS); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession( - JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false); + JobSchedulerService.sElapsedRealtimeClock.millis(), + 500, 1), false); advanceElapsedClock(500); advanceElapsedClock(400); mQuotaController.saveTimingSession(0, "com.android.test", createTimingSession( - JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false); + JobSchedulerService.sElapsedRealtimeClock.millis(), + 100, 1), false); advanceElapsedClock(100); advanceElapsedClock(5 * SECOND_IN_MILLIS); } @@ -1241,6 +1416,164 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetExecutionStatsLocked_CoalescingSessions_NewDefaultBucketWindowSizes() { + for (int i = 0; i < 20; ++i) { + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis(), + 5 * MINUTE_IN_MILLIS, 5), false); + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + advanceElapsedClock(5 * MINUTE_IN_MILLIS); + for (int j = 0; j < 5; ++j) { + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis(), + MINUTE_IN_MILLIS, 2), false); + advanceElapsedClock(MINUTE_IN_MILLIS); + advanceElapsedClock(54 * SECOND_IN_MILLIS); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis(), 500, 1), false); + advanceElapsedClock(500); + advanceElapsedClock(400); + mQuotaController.saveTimingSession(0, "com.android.test", + createTimingSession( + JobSchedulerService.sElapsedRealtimeClock.millis(), 100, 1), false); + advanceElapsedClock(100); + advanceElapsedClock(5 * SECOND_IN_MILLIS); + } + advanceElapsedClock(40 * MINUTE_IN_MILLIS); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 0); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(0, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(64, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(192, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(320, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 500); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(0, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + // WINDOW_SIZE_WORKING_MS * 5 TimingSessions are coalesced + assertEquals(44, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + // WINDOW_SIZE_FREQUENT_MS * 5 TimingSessions are coalesced + assertEquals(132, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + // WINDOW_SIZE_RARE_MS * 5 TimingSessions are coalesced + assertEquals(220, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, 1000); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(0, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(44, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(132, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(220, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, + 5 * SECOND_IN_MILLIS); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(0, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + // WINDOW_SIZE_WORKING_MS * 9 TimingSessions are coalesced + assertEquals(28, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + // WINDOW_SIZE_FREQUENT_MS * 9 TimingSessions are coalesced + assertEquals(84, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + // WINDOW_SIZE_RARE_MS * 9 TimingSessions are coalesced + assertEquals(140, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, + MINUTE_IN_MILLIS); + + // Only two TimingSessions there for every hour. + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(0, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(8, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(24, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(40, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, + 5 * MINUTE_IN_MILLIS); + + // Only one TimingSessions there for every hour + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(0, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(4, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(12, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(20, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, + 15 * MINUTE_IN_MILLIS); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(0, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(4, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(12, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(20, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + + // QuotaController caps the duration at 15 minutes, so there shouldn't be any difference + // between an hour and 15 minutes. + setDeviceConfigLong(QcConstants.KEY_TIMING_SESSION_COALESCING_DURATION_MS, HOUR_IN_MILLIS); + + synchronized (mQuotaController.mLock) { + mQuotaController.invalidateAllExecutionStatsLocked(); + assertEquals(0, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", ACTIVE_INDEX).sessionCountInWindow); + assertEquals(4, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", WORKING_INDEX).sessionCountInWindow); + assertEquals(12, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", FREQUENT_INDEX).sessionCountInWindow); + assertEquals(20, mQuotaController.getExecutionStatsLocked( + 0, "com.android.test", RARE_INDEX).sessionCountInWindow); + } + } + /** * Tests that getExecutionStatsLocked properly caches the stats and returns the cached object. */ @@ -1466,7 +1799,8 @@ public class QuotaControllerTest { } @Test - public void testGetMaxJobExecutionTimeLocked_EJ() { + @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetMaxJobExecutionTimeLocked_EJ_LegacyDefaultEJLimits() { final long timeUsedMs = 3 * MINUTE_IN_MILLIS; mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS), @@ -1542,11 +1876,91 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetMaxJobExecutionTimeLocked_EJ_NewDefaultEJLimits() { + final long timeUsedMs = 3 * MINUTE_IN_MILLIS; + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(sElapsedRealtimeClock.millis() - (6 * MINUTE_IN_MILLIS), + timeUsedMs, 5), true); + JobStatus job = createExpeditedJobStatus("testGetMaxJobExecutionTimeLocked_EJ", 0); + setStandbyBucket(RARE_INDEX, job); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(job, null); + } + + setCharging(); + synchronized (mQuotaController.mLock) { + assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + } + + setDischarging(); + setProcessState(getProcessStateQuotaFreeThreshold()); + synchronized (mQuotaController.mLock) { + assertEquals(mQcConstants.EJ_LIMIT_WORKING_MS / 2, + mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + } + + // Top-started job + setProcessState(ActivityManager.PROCESS_STATE_TOP); + synchronized (mQuotaController.mLock) { + mQuotaController.prepareForExecutionLocked(job); + } + setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); + synchronized (mQuotaController.mLock) { + assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2, + mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + mQuotaController.maybeStopTrackingJobLocked(job, null); + } + + setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); + synchronized (mQuotaController.mLock) { + assertEquals(mQcConstants.EJ_LIMIT_RARE_MS - timeUsedMs, + mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + } + + // Test used quota rolling out of window. + synchronized (mQuotaController.mLock) { + mQuotaController.clearAppStatsLocked(SOURCE_USER_ID, SOURCE_PACKAGE); + } + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(sElapsedRealtimeClock.millis() - mQcConstants.EJ_WINDOW_SIZE_MS, + timeUsedMs, 5), true); + + setProcessState(getProcessStateQuotaFreeThreshold()); + synchronized (mQuotaController.mLock) { + // max of 50% WORKING limit and remaining quota + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + } + + // Top-started job + setProcessState(ActivityManager.PROCESS_STATE_TOP); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStartTrackingJobLocked(job, null); + mQuotaController.prepareForExecutionLocked(job); + } + setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND); + synchronized (mQuotaController.mLock) { + assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2, + mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + mQuotaController.maybeStopTrackingJobLocked(job, null); + } + + setProcessState(ActivityManager.PROCESS_STATE_RECEIVER); + synchronized (mQuotaController.mLock) { + assertEquals(mQcConstants.EJ_LIMIT_RARE_MS, + mQuotaController.getMaxJobExecutionTimeMsLocked(job)); + } + } + /** * Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size. */ @Test - public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow() { + @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_LegacyDefaultBucketWindowSizes() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false); @@ -1570,27 +1984,56 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetTimeUntilQuotaConsumedLocked_AllowedEqualsWindow_NewDefaultBucketWindowSizes() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (8 * HOUR_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), + false); + + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 20 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS); + // window size = allowed time, so jobs can essentially run non-stop until they reach the + // max execution time. + setStandbyBucket(EXEMPTED_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + } + /** * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket * window. */ @Test - public void testGetTimeUntilQuotaConsumedLocked_BucketWindow() { + @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_LegacyDefaultBucketWindowSizes() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); // Close to RARE boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), + createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - 30 * SECOND_IN_MILLIS), 30 * SECOND_IN_MILLIS, 5), false); // Far away from FREQUENT boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + createTimingSession(now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS), + 3 * MINUTE_IN_MILLIS, 5), false); // Overlap WORKING_SET boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), + createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS + MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); // Close to ACTIVE boundary. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS), + 3 * MINUTE_IN_MILLIS, 5), false); setStandbyBucket(RARE_INDEX); synchronized (mQuotaController.mLock) { @@ -1635,6 +2078,69 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetTimeUntilQuotaConsumedLocked_BucketWindow_NewDefaultBucketWindowSizes() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + // Close to RARE boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (mQcConstants.WINDOW_SIZE_RARE_MS - 30 * SECOND_IN_MILLIS), + 30 * SECOND_IN_MILLIS, 5), false); + // Far away from FREQUENT boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (mQcConstants.WINDOW_SIZE_FREQUENT_MS - HOUR_IN_MILLIS), + 3 * MINUTE_IN_MILLIS, 5), false); + // Overlap WORKING_SET boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS + MINUTE_IN_MILLIS), + 3 * MINUTE_IN_MILLIS, 5), false); + // Close to ACTIVE boundary. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (mQcConstants.WINDOW_SIZE_ACTIVE_MS - MINUTE_IN_MILLIS), + 3 * MINUTE_IN_MILLIS, 5), false); + + setStandbyBucket(RARE_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(30 * SECOND_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + setStandbyBucket(FREQUENT_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + setStandbyBucket(WORKING_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(5 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + + // ACTIVE window != allowed time. + setStandbyBucket(ACTIVE_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(7 * MINUTE_IN_MILLIS, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(10 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + } + /** * Test getTimeUntilQuotaConsumedLocked when the app is close to the max execution limit. */ @@ -1759,7 +2265,8 @@ public class QuotaControllerTest { * Test getTimeUntilQuotaConsumedLocked when allowed time equals the bucket window size. */ @Test - public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow() { + @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_LegacyDefaultBucketWindowSizes() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, createTimingSession(now - (24 * HOUR_IN_MILLIS), @@ -1785,55 +2292,84 @@ public class QuotaControllerTest { } } + @Test + @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_AllowedEqualsWindow_NewDefaultBucketWindowSizes() { + final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (24 * HOUR_IN_MILLIS), + mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS, 5), + false); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (20 * MINUTE_IN_MILLIS), 20 * MINUTE_IN_MILLIS, 5), + false); + + setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, + 20 * MINUTE_IN_MILLIS); + setDeviceConfigLong(QcConstants.KEY_WINDOW_SIZE_EXEMPTED_MS, 20 * MINUTE_IN_MILLIS); + // window size != allowed time. + setStandbyBucket(EXEMPTED_INDEX); + synchronized (mQuotaController.mLock) { + assertEquals(0, + mQuotaController.getRemainingExecutionTimeLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 20 * MINUTE_IN_MILLIS, + mQuotaController.getTimeUntilQuotaConsumedLocked( + SOURCE_USER_ID, SOURCE_PACKAGE)); + } + } + /** * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket * window and the session is rolling out of the window. */ @Test - public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow() { + @DisableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow_LegacyDefaultBucketWindowSizes() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (24 * HOUR_IN_MILLIS), - 10 * MINUTE_IN_MILLIS, 5), false); + createTimingSession(now - mQcConstants.WINDOW_SIZE_RARE_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS, 5), false); setStandbyBucket(RARE_INDEX); synchronized (mQuotaController.mLock) { assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(10 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS, mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (8 * HOUR_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), false); + createTimingSession(now - mQcConstants.WINDOW_SIZE_FREQUENT_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 5), false); setStandbyBucket(FREQUENT_INDEX); synchronized (mQuotaController.mLock) { assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(10 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (2 * HOUR_IN_MILLIS), - 10 * MINUTE_IN_MILLIS, 5), false); + createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS, 5), false); setStandbyBucket(WORKING_INDEX); synchronized (mQuotaController.mLock) { assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(10 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS, mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (10 * MINUTE_IN_MILLIS), 10 * MINUTE_IN_MILLIS, 5), - false); + createTimingSession(now - mQcConstants.WINDOW_SIZE_ACTIVE_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 5), false); // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the // max execution time. setStandbyBucket(ACTIVE_INDEX); @@ -1841,130 +2377,85 @@ public class QuotaControllerTest { assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 30 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS + - (mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS + + mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS + + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS), mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } } - /** - * Test getTimeUntilQuotaConsumedLocked when the determination is based within the bucket - * window and there are valid QuotaBumps in the history. - */ @Test - public void testGetTimeUntilQuotaConsumedLocked_QuotaBump() { - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - + @EnableFlags(Flags.FLAG_ADJUST_QUOTA_DEFAULT_CONSTANTS) + public void testGetTimeUntilQuotaConsumedLocked_EdgeOfWindow_BucketWindow_NewDefaultBucketWindowSizes() { final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - // Close to RARE boundary. - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (24 * HOUR_IN_MILLIS - 30 * SECOND_IN_MILLIS), - 30 * SECOND_IN_MILLIS, 5), false); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS)); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS)); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS)); - // Far away from FREQUENT boundary. - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (7 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); - // Overlap WORKING_SET boundary. - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 2 * HOUR_IN_MILLIS)); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), - 3 * MINUTE_IN_MILLIS, 5), false); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS)); - // Close to ACTIVE boundary. - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (9 * MINUTE_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 5), false); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - mQcConstants.WINDOW_SIZE_RARE_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS, 5), false); setStandbyBucket(RARE_INDEX); synchronized (mQuotaController.mLock) { - assertEquals(3 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS, + assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(4 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_RARE_MS, mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - mQcConstants.WINDOW_SIZE_FREQUENT_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, 5), false); setStandbyBucket(FREQUENT_INDEX); synchronized (mQuotaController.mLock) { - assertEquals(4 * MINUTE_IN_MILLIS, + assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(4 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_FREQUENT_MS, mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - mQcConstants.WINDOW_SIZE_WORKING_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS, 5), false); setStandbyBucket(WORKING_INDEX); synchronized (mQuotaController.mLock) { - assertEquals(8 * MINUTE_IN_MILLIS, + assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(10 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_WORKING_MS, mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } - // ACTIVE window = allowed time, so jobs can essentially run non-stop until they reach the - // max execution time. + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - mQcConstants.WINDOW_SIZE_ACTIVE_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, 5), + false); + // ACTIVE window != allowed time. setStandbyBucket(ACTIVE_INDEX); synchronized (mQuotaController.mLock) { - assertEquals(10 * MINUTE_IN_MILLIS, + assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(mQcConstants.MAX_EXECUTION_TIME_MS - 9 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_ACTIVE_MS, mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } - } - /** - * Test getTimeUntilQuotaConsumedLocked when there are valid QuotaBumps in recent history that - * provide enough additional quota to bridge gaps between sessions. - */ - @Test - public void testGetTimeUntilQuotaConsumedLocked_QuotaBump_CrucialBumps() { - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - - final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (25 * HOUR_IN_MILLIS), - 30 * MINUTE_IN_MILLIS, 25), false); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 16 * HOUR_IN_MILLIS)); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 12 * HOUR_IN_MILLIS)); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 8 * HOUR_IN_MILLIS)); - // Without the valid quota bumps, the app would only 3 minutes until the quota was consumed. - // The quota bumps provide enough quota to bridge the gap between the two earliest sessions. mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (8 * HOUR_IN_MILLIS), 3 * MINUTE_IN_MILLIS, 1), false); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (8 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS), - 2 * MINUTE_IN_MILLIS, 5), false); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (2 * HOUR_IN_MILLIS + MINUTE_IN_MILLIS), - 3 * MINUTE_IN_MILLIS, 1), false); - mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE) - .add(new QuotaBump(now - 15 * MINUTE_IN_MILLIS)); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (9 * MINUTE_IN_MILLIS), 2 * MINUTE_IN_MILLIS, 1), false); - - setStandbyBucket(FREQUENT_INDEX); + createTimingSession(now - mQcConstants.WINDOW_SIZE_EXEMPTED_MS, + mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, 5), + false); + // EXEMPTED window != allowed time + setStandbyBucket(EXEMPTED_INDEX); synchronized (mQuotaController.mLock) { - assertEquals(2 * MINUTE_IN_MILLIS, + assertEquals(0, mQuotaController.getRemainingExecutionTimeLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); - assertEquals(7 * MINUTE_IN_MILLIS, + assertEquals(mQcConstants.ALLOWED_TIME_PER_PERIOD_EXEMPTED_MS, mQuotaController.getTimeUntilQuotaConsumedLocked( SOURCE_USER_ID, SOURCE_PACKAGE)); } @@ -2316,272 +2807,6 @@ public class QuotaControllerTest { } } - @Test - public void testIsWithinQuotaLocked_WithQuotaBump_Duration() { - setDischarging(); - int standbyBucket = WORKING_INDEX; - setStandbyBucket(standbyBucket); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, - 5 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); - - long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession( - now - (HOUR_IN_MILLIS - 2 * MINUTE_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 1), - false); - final ExecutionStats stats; - synchronized (mQuotaController.mLock) { - stats = mQuotaController.getExecutionStatsLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); - mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 1); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(5 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - - advanceElapsedClock(HOUR_IN_MILLIS); - - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - - // Emulate a quota bump while some jobs are executing - JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 1); - JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_Duration", 2); - - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job1, null); - mQuotaController.prepareForExecutionLocked(job1); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - mQuotaController.maybeStartTrackingJobLocked(job2, null); - mQuotaController.prepareForExecutionLocked(job2); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null); - mQuotaController.maybeStopTrackingJobLocked(job2, null); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - - // Phase out the first session - advanceElapsedClock(5 * MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - - // Phase out the first quota bump - advanceElapsedClock(7 * HOUR_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(6 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs); - } - } - - @Test - public void testIsWithinQuotaLocked_WithQuotaBump_JobCount() { - setDischarging(); - int standbyBucket = WORKING_INDEX; - setStandbyBucket(standbyBucket); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, - 20 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 1); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 0); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); - - long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS - HOUR_IN_MILLIS), - 5 * MINUTE_IN_MILLIS, 10), false); - final ExecutionStats stats; - synchronized (mQuotaController.mLock) { - stats = mQuotaController.getExecutionStatsLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); - mQuotaController.incrementJobCountLocked(SOURCE_USER_ID, SOURCE_PACKAGE, 10); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(10, stats.jobCountLimit); - } - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(11, stats.jobCountLimit); - } - - advanceElapsedClock(HOUR_IN_MILLIS); - - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(11, stats.jobCountLimit); - } - - // Emulate a quota bump while some jobs are executing - JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1); - JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2); - - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job1, null); - mQuotaController.prepareForExecutionLocked(job1); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(12, stats.jobCountLimit); - mQuotaController.maybeStartTrackingJobLocked(job2, null); - mQuotaController.prepareForExecutionLocked(job2); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null); - mQuotaController.maybeStopTrackingJobLocked(job2, null); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(12, stats.jobCountLimit); - } - - // Phase out the first session - advanceElapsedClock(3 * MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(12, stats.jobCountLimit); - } - - // Phase out the first quota bump - advanceElapsedClock(7 * HOUR_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(11, stats.jobCountLimit); - } - } - - @Test - public void testIsWithinQuotaLocked_WithQuotaBump_SessionCount() { - setDischarging(); - int standbyBucket = WORKING_INDEX; - setStandbyBucket(standbyBucket); - setDeviceConfigLong(QcConstants.KEY_ALLOWED_TIME_PER_PERIOD_WORKING_MS, - 20 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_MAX_SESSION_COUNT_WORKING, 2); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, 0); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 0); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 1); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 8 * HOUR_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 5); - - long now = JobSchedulerService.sElapsedRealtimeClock.millis(); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (mQcConstants.WINDOW_SIZE_WORKING_MS - HOUR_IN_MILLIS), - 5 * MINUTE_IN_MILLIS, 1), false); - mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, - createTimingSession(now - (30 * MINUTE_IN_MILLIS), MINUTE_IN_MILLIS, 1), false); - final ExecutionStats stats; - synchronized (mQuotaController.mLock) { - stats = mQuotaController.getExecutionStatsLocked( - SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(2, stats.sessionCountLimit); - } - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(3, stats.sessionCountLimit); - } - - advanceElapsedClock(HOUR_IN_MILLIS); - - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(3, stats.sessionCountLimit); - } - - // Emulate a quota bump while some jobs are executing - JobStatus job1 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 1); - JobStatus job2 = createJobStatus("testIsWithinQuotaLocked_WithQuotaBump_JobCount", 2); - - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job1, null); - mQuotaController.prepareForExecutionLocked(job1); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job1, null); - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(4, stats.sessionCountLimit); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStartTrackingJobLocked(job2, null); - mQuotaController.prepareForExecutionLocked(job2); - } - - advanceElapsedClock(MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - mQuotaController.maybeStopTrackingJobLocked(job2, null); - assertFalse(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(4, stats.sessionCountLimit); - } - - // Phase out the first session - advanceElapsedClock(2 * MINUTE_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(4, stats.sessionCountLimit); - } - - // Phase out the first quota bump - advanceElapsedClock(7 * HOUR_IN_MILLIS); - synchronized (mQuotaController.mLock) { - assertTrue(mQuotaController - .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); - assertEquals(3, stats.sessionCountLimit); - } - } @Test public void testIsWithinEJQuotaLocked_NeverApp() { @@ -3590,12 +3815,6 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 84 * SECOND_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 83 * SECOND_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, - 93 * SECOND_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, 92); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, 91); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 90 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, 89); assertEquals(8 * MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); @@ -3653,11 +3872,6 @@ public class QuotaControllerTest { assertEquals(85 * SECOND_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(84 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(83 * SECOND_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs()); - assertEquals(93 * SECOND_IN_MILLIS, mQuotaController.getQuotaBumpAdditionDurationMs()); - assertEquals(92, mQuotaController.getQuotaBumpAdditionJobCount()); - assertEquals(91, mQuotaController.getQuotaBumpAdditionSessionCount()); - assertEquals(90 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); - assertEquals(89, mQuotaController.getQuotaBumpLimit()); } @Test @@ -3710,11 +3924,6 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, -1); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, -1); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_DURATION_MS, -1); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_JOB_COUNT, -1); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_ADDITIONAL_SESSION_COUNT, -1); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 59 * MINUTE_IN_MILLIS); - setDeviceConfigInt(QcConstants.KEY_QUOTA_BUMP_LIMIT, -1); assertEquals(MINUTE_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); @@ -3765,11 +3974,6 @@ public class QuotaControllerTest { assertEquals(0, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(0, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(0, mQuotaController.getEJGracePeriodTopAppMs()); - assertEquals(0, mQuotaController.getQuotaBumpAdditionDurationMs()); - assertEquals(0, mQuotaController.getQuotaBumpAdditionJobCount()); - assertEquals(0, mQuotaController.getQuotaBumpAdditionSessionCount()); - assertEquals(60 * MINUTE_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); - assertEquals(0, mQuotaController.getQuotaBumpLimit()); // Invalid configurations. // In_QUOTA_BUFFER should never be greater than ALLOWED_TIME_PER_PERIOD @@ -3827,7 +4031,6 @@ public class QuotaControllerTest { setDeviceConfigLong(QcConstants.KEY_EJ_REWARD_NOTIFICATION_SEEN_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TEMP_ALLOWLIST_MS, 25 * HOUR_IN_MILLIS); setDeviceConfigLong(QcConstants.KEY_EJ_GRACE_PERIOD_TOP_APP_MS, 25 * HOUR_IN_MILLIS); - setDeviceConfigLong(QcConstants.KEY_QUOTA_BUMP_WINDOW_SIZE_MS, 25 * HOUR_IN_MILLIS); assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getAllowedTimePerPeriodMs()[EXEMPTED_INDEX]); @@ -3868,7 +4071,6 @@ public class QuotaControllerTest { assertEquals(5 * MINUTE_IN_MILLIS, mQuotaController.getEJRewardNotificationSeenMs()); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTempAllowlistMs()); assertEquals(HOUR_IN_MILLIS, mQuotaController.getEJGracePeriodTopAppMs()); - assertEquals(24 * HOUR_IN_MILLIS, mQuotaController.getQuotaBumpWindowSizeMs()); } /** Tests that TimingSessions aren't saved when the device is charging. */ @@ -4157,7 +4359,7 @@ public class QuotaControllerTest { /** Tests that Timers count FOREGROUND_SERVICE jobs. */ @Test - @RequiresFlagsEnabled(FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS) + @EnableFlags(Flags.FLAG_ENFORCE_QUOTA_POLICY_TO_FGS_JOBS) public void testTimerTracking_Fgs() { setDischarging(); diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java index 9ba272446689..625dbe6e16f9 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundUserSoundNotifierTest.java @@ -18,6 +18,7 @@ package com.android.server.pm; import static android.media.AudioAttributes.USAGE_ALARM; +import static org.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.clearInvocations; @@ -91,6 +92,7 @@ public class BackgroundUserSoundNotifierTest { } @Test public void testAlarmOnBackgroundUser_foregroundUserNotified() throws RemoteException { + assumeTrue(UserManager.supportsMultipleUsers()); AudioAttributes aa = new AudioAttributes.Builder().setUsage(USAGE_ALARM).build(); UserInfo user = createUser("User", UserManager.USER_TYPE_FULL_SECONDARY, 0); final int fgUserId = mSpiedContext.getUserId(); @@ -100,7 +102,7 @@ public class BackgroundUserSoundNotifierTest { /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT); clearInvocations(mNotificationManager); - mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext); + mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi); verify(mNotificationManager) .notifyAsUser(eq(BackgroundUserSoundNotifier.class.getSimpleName()), eq(afi.getClientUid()), any(Notification.class), @@ -116,7 +118,7 @@ public class BackgroundUserSoundNotifierTest { /* packageName= */ "com.android.car.audio", AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT); clearInvocations(mNotificationManager); - mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext); + mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi); verifyZeroInteractions(mNotificationManager); } @@ -131,7 +133,7 @@ public class BackgroundUserSoundNotifierTest { AudioManager.AUDIOFOCUS_GAIN, AudioManager.AUDIOFOCUS_NONE, /* flags= */ 0, Build.VERSION.SDK_INT); clearInvocations(mNotificationManager); - mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi, mSpiedContext); + mBackgroundUserSoundNotifier.notifyForegroundUserAboutSoundIfNecessary(afi); verifyZeroInteractions(mNotificationManager); } @@ -169,7 +171,7 @@ public class BackgroundUserSoundNotifierTest { doReturn(focusStack).when(mockAudioPolicy).getFocusStack(); mBackgroundUserSoundNotifier.mFocusControlAudioPolicy = mockAudioPolicy; - mBackgroundUserSoundNotifier.muteAlarmSounds(mSpiedContext); + mBackgroundUserSoundNotifier.muteAlarmSounds(bgUserUid); verify(apc1.getPlayerProxy()).stop(); verify(mockAudioPolicy).sendFocusLossAndUpdate(afi); @@ -178,6 +180,7 @@ public class BackgroundUserSoundNotifierTest { @Test public void testOnAudioFocusGrant_alarmOnBackgroundUser_notifiesForegroundUser() { + assumeTrue(UserManager.supportsMultipleUsers()); final int fgUserId = mSpiedContext.getUserId(); UserInfo bgUser = createUser("Background User", UserManager.USER_TYPE_FULL_SECONDARY, 0); int bgUserUid = bgUser.id * 100000; @@ -205,7 +208,7 @@ public class BackgroundUserSoundNotifierTest { .when(mUserManager).getUserSwitchability(any()); Notification notification = mBackgroundUserSoundNotifier.createNotification(userName, - mSpiedContext); + mSpiedContext, 101000); assertEquals("Alarm for BgUser", notification.extras.getString( Notification.EXTRA_TITLE)); @@ -232,7 +235,7 @@ public class BackgroundUserSoundNotifierTest { .when(mUserManager).getUserSwitchability(any()); Notification notification = mBackgroundUserSoundNotifier.createNotification(userName, - mSpiedContext); + mSpiedContext, 101000); assertEquals(1, notification.actions.length); assertEquals(mSpiedContext.getString( 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 e131a98b52d0..de029e0d770f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt +++ b/services/tests/mockingservicestests/src/com/android/server/pm/DistractingPackageHelperTest.kt @@ -185,7 +185,8 @@ class DistractingPackageHelperTest : PackageHelperTestBase() { verify(pms, never()).scheduleWritePackageRestrictions(eq(TEST_USER_ID)) verify(broadcastHelper, never()).sendPackageBroadcast(eq( Intent.ACTION_DISTRACTING_PACKAGES_CHANGED), nullable(), nullable(), anyInt(), - nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable()) + nullable(), nullable(), any(), nullable(), nullable(), nullable(), nullable(), + nullable()) distractingPackageHelper.removeDistractingPackageRestrictions(pms.snapshotComputer(), arrayOfNulls(0), TEST_USER_ID) diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java index 43a8aa957fa5..124c41ed449f 100644 --- a/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/pm/StagingManagerTest.java @@ -741,7 +741,8 @@ public class StagingManagerTest { /* stagedSessionErrorCode */ PackageManager.INSTALL_UNKNOWN, /* stagedSessionErrorMessage */ "no error", /* preVerifiedDomains */ null, - /* verifierController */ null); + /* verifierController */ null, + /* verificationPolicy */ PackageInstaller.VERIFICATION_POLICY_BLOCK_FAIL_CLOSED); StagingManager.StagedSession stagedSession = spy(session.mStagedSession); doReturn(packageName).when(stagedSession).getPackageName(); diff --git a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java index 639ae30c00b9..58489f398775 100644 --- a/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java +++ b/services/tests/performancehinttests/src/com/android/server/power/hint/HintManagerServiceTest.java @@ -81,7 +81,6 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; @@ -581,8 +580,6 @@ public class HintManagerServiceTest { HintManagerService service = createService(); IBinder token = new Binder(); int threadCount = 2; - - // session 1 has 2 non-isolated tids long sessionPtr1 = 111; CountDownLatch stopLatch1 = new CountDownLatch(1); int[] tids1 = createThreads(threadCount, stopLatch1); @@ -618,7 +615,6 @@ public class HintManagerServiceTest { IBinder token = new Binder(); int threadCount = 2; - // session 1 has 2 non-isolated tids long sessionPtr1 = 111; CountDownLatch stopLatch1 = new CountDownLatch(1); int[] tids1 = createThreads(threadCount, stopLatch1); @@ -630,67 +626,23 @@ public class HintManagerServiceTest { SessionTag.OTHER, new SessionConfig()); assertNotNull(session1); - // session 2 has 2 non-isolated tids and 2 isolated tids - long sessionPtr2 = 222; - CountDownLatch stopLatch2 = new CountDownLatch(1); - // negative value used for test only to avoid conflicting with any real thread that exists - int isoProc1 = -100; - int isoProc2 = 99999999; - when(mAmInternalMock.getIsolatedProcesses(eq(UID))).thenReturn(List.of(0)); - int[] tids2 = createThreads(threadCount, stopLatch2); - int[] tids2WithIsolated = Arrays.copyOf(tids2, tids2.length + 2); - tids2WithIsolated[threadCount] = isoProc1; - tids2WithIsolated[threadCount + 1] = isoProc2; - int[] expectedTids2 = Arrays.copyOf(tids2, tids2.length + 1); - expectedTids2[tids2.length] = isoProc1; - when(mNativeWrapperMock.halCreateHintSessionWithConfig(eq(TGID), eq(UID), - eq(tids2WithIsolated), eq(DEFAULT_TARGET_DURATION), anyInt(), - any(SessionConfig.class))).thenReturn(sessionPtr2); - AppHintSession session2 = (AppHintSession) service.getBinderServiceInstance() - .createHintSessionWithConfig(token, tids2WithIsolated, - DEFAULT_TARGET_DURATION, SessionTag.OTHER, new SessionConfig()); - assertNotNull(session2); - - // trigger clean up through UID state change by making the process foreground->background - // this will remove the one unexpected isolated tid from session 2 - service.mUidObserver.onUidStateChanged(UID, - ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); - service.mUidObserver.onUidStateChanged(UID, - ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos( - CLEAN_UP_UID_DELAY_MILLIS)); - verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); - verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any()); - // the new TIDs pending list should be updated - assertArrayEquals(expectedTids2, session2.getTidsInternal()); - reset(mNativeWrapperMock); - - // this should resume and update the threads so those never-existed invalid isolated - // processes should be cleaned up - service.mUidObserver.onUidStateChanged(UID, - ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); - // wait for the async uid state change to trigger resume and setThreads - LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000)); - verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr2), eq(expectedTids2)); - reset(mNativeWrapperMock); - // let all session 1 threads to exit and the cleanup should force pause the session 1 stopLatch1.countDown(); LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); service.mUidObserver.onUidStateChanged(UID, + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); + service.mUidObserver.onUidStateChanged(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000) + TimeUnit.MILLISECONDS.toNanos( CLEAN_UP_UID_DELAY_MILLIS)); verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1)); verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); - verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any()); verifyAllHintsEnabled(session1, false); - verifyAllHintsEnabled(session2, false); service.mUidObserver.onUidStateChanged(UID, ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND, 0, 0); LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000)); + assertArrayEquals(tids1, session1.getTidsInternal()); verifyAllHintsEnabled(session1, false); - verifyAllHintsEnabled(session2, true); reset(mNativeWrapperMock); // in foreground, set new tids for session 1 then it should be resumed and all hints allowed @@ -704,8 +656,6 @@ public class HintManagerServiceTest { // let all session 1 and 2 non isolated threads to exit stopLatch1.countDown(); - stopLatch2.countDown(); - expectedTids2 = new int[]{isoProc1}; LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100)); service.mUidObserver.onUidStateChanged(UID, ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND, 0, 0); @@ -713,14 +663,11 @@ public class HintManagerServiceTest { CLEAN_UP_UID_DELAY_MILLIS)); verify(mNativeWrapperMock, times(1)).halPauseHintSession(eq(sessionPtr1)); verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr1), any()); - verify(mNativeWrapperMock, never()).halSetThreads(eq(sessionPtr2), any()); // in background, set threads for session 1 then it should not be force paused next time session1.setThreads(SESSION_TIDS_A); // the new TIDs pending list should be updated assertArrayEquals(SESSION_TIDS_A, session1.getTidsInternal()); - assertArrayEquals(expectedTids2, session2.getTidsInternal()); verifyAllHintsEnabled(session1, false); - verifyAllHintsEnabled(session2, false); reset(mNativeWrapperMock); service.mUidObserver.onUidStateChanged(UID, @@ -729,10 +676,7 @@ public class HintManagerServiceTest { CLEAN_UP_UID_DELAY_MILLIS)); verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr1), eq(SESSION_TIDS_A)); - verify(mNativeWrapperMock, times(1)).halSetThreads(eq(sessionPtr2), - eq(expectedTids2)); verifyAllHintsEnabled(session1, true); - verifyAllHintsEnabled(session2, true); } private void verifyAllHintsEnabled(AppHintSession session, boolean verifyEnabled) { diff --git a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java index a1db18232c09..1c7fc63efd41 100644 --- a/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java +++ b/services/tests/powerservicetests/src/com/android/server/power/NotifierTest.java @@ -54,6 +54,7 @@ import android.os.test.TestLooper; import android.provider.Settings; import android.testing.TestableContext; import android.util.IntArray; +import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.Display; import android.view.DisplayAddress; @@ -384,6 +385,32 @@ public class NotifierTest { } @Test + public void testOnGroupRemoved_perDisplayWakeByTouchEnabled() { + createNotifier(); + // GIVEN per-display wake by touch is enabled and one display group has been defined + when(mPowerManagerFlags.isPerDisplayWakeByTouchEnabled()).thenReturn(true); + final int groupId = 313; + final int displayId1 = 3113; + final int displayId2 = 4114; + final int[] displays = new int[]{displayId1, displayId2}; + when(mDisplayManagerInternal.getDisplayIds()).thenReturn(IntArray.wrap(displays)); + when(mDisplayManagerInternal.getDisplayIdsForGroup(groupId)).thenReturn(displays); + mNotifier.onGroupWakefulnessChangeStarted( + groupId, WAKEFULNESS_AWAKE, PowerManager.WAKE_REASON_TAP, /* eventTime= */ 1000); + final SparseBooleanArray expectedDisplayInteractivities = new SparseBooleanArray(); + expectedDisplayInteractivities.put(displayId1, true); + expectedDisplayInteractivities.put(displayId2, true); + verify(mInputManagerInternal).setDisplayInteractivities(expectedDisplayInteractivities); + + // WHEN display group is removed + when(mDisplayManagerInternal.getDisplayIdsByGroupsIds()).thenReturn(new SparseArray<>()); + mNotifier.onGroupRemoved(groupId); + + // THEN native input manager is informed that displays in that group no longer exist + verify(mInputManagerInternal).setDisplayInteractivities(new SparseBooleanArray()); + } + + @Test public void testOnWakeLockListener_RemoteException_NoRethrow() throws RemoteException { when(mPowerManagerFlags.improveWakelockLatency()).thenReturn(true); createNotifier(); diff --git a/services/tests/security/forensic/Android.bp b/services/tests/security/forensic/Android.bp new file mode 100644 index 000000000000..adc49040a004 --- /dev/null +++ b/services/tests/security/forensic/Android.bp @@ -0,0 +1,41 @@ +package { + default_team: "trendy_team_platform_security", + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "frameworks_base_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["frameworks_base_license"], +} + +android_test { + name: "ForensicServiceTests", + srcs: [ + "src/**/*.java", + ], + + static_libs: [ + "androidx.test.core", + "androidx.test.rules", + "androidx.test.runner", + "compatibility-device-util-axt", + "frameworks-base-testutils", + "junit", + "platform-test-annotations", + "services.core", + "truth", + ], + + platform_apis: true, + + test_suites: [ + "device-tests", + "automotive-tests", + ], + + certificate: "platform", + dxflags: ["--multi-dex"], + optimize: { + enabled: false, + }, +} diff --git a/services/tests/security/forensic/AndroidManifest.xml b/services/tests/security/forensic/AndroidManifest.xml new file mode 100644 index 000000000000..40feb19aecba --- /dev/null +++ b/services/tests/security/forensic/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.server.security.forensic.tests"> + + <application android:testOnly="true"> + <uses-library android:name="android.test.runner"/> + </application> + + <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner" + android:targetPackage="com.android.server.security.forensic.tests" + android:label="Frameworks Forensic Services Tests"/> +</manifest> diff --git a/services/tests/security/forensic/AndroidTest.xml b/services/tests/security/forensic/AndroidTest.xml new file mode 100644 index 000000000000..bbe2e9c303ce --- /dev/null +++ b/services/tests/security/forensic/AndroidTest.xml @@ -0,0 +1,32 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2024 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. +--> +<configuration description="Runs Frameworks Forensic Service tests."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-instrumentation" /> + + <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller"> + <option name="cleanup-apks" value="true"/> + <option name="test-file-name" value="ForensicServiceTests.apk"/> + <option name="install-arg" value="-t" /> + </target_preparer> + + <option name="test-tag" value="ForensicServiceTests" /> + <test class="com.android.tradefed.testtype.InstrumentationTest" > + <option name="package" value="com.android.server.security.forensic.tests" /> + <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" /> + <option name="hidden-api-checks" value="false"/> + </test> +</configuration> diff --git a/services/tests/security/forensic/TEST_MAPPING b/services/tests/security/forensic/TEST_MAPPING new file mode 100644 index 000000000000..bd8b2ab7c41f --- /dev/null +++ b/services/tests/security/forensic/TEST_MAPPING @@ -0,0 +1,7 @@ +{ + "postsubmit": [ + { + "name": "ForensicServiceTests" + } + ] +} diff --git a/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java new file mode 100644 index 000000000000..7aa2e0f609a7 --- /dev/null +++ b/services/tests/security/forensic/src/com/android/server/security/forensic/ForensicServiceTest.java @@ -0,0 +1,387 @@ +/* + * Copyright (C) 2024 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.security.forensic; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Looper; +import android.os.RemoteException; +import android.os.test.TestLooper; +import android.security.forensic.IForensicServiceCommandCallback; +import android.security.forensic.IForensicServiceStateCallback; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ForensicServiceTest { + private static final int STATE_UNKNOWN = IForensicServiceStateCallback.State.UNKNOWN; + private static final int STATE_INVISIBLE = IForensicServiceStateCallback.State.INVISIBLE; + private static final int STATE_VISIBLE = IForensicServiceStateCallback.State.VISIBLE; + private static final int STATE_ENABLED = IForensicServiceStateCallback.State.ENABLED; + + private static final int ERROR_UNKNOWN = IForensicServiceCommandCallback.ErrorCode.UNKNOWN; + private static final int ERROR_PERMISSION_DENIED = + IForensicServiceCommandCallback.ErrorCode.PERMISSION_DENIED; + private static final int ERROR_INVALID_STATE_TRANSITION = + IForensicServiceCommandCallback.ErrorCode.INVALID_STATE_TRANSITION; + private static final int ERROR_BACKUP_TRANSPORT_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.BACKUP_TRANSPORT_UNAVAILABLE; + private static final int ERROR_DATA_SOURCE_UNAVAILABLE = + IForensicServiceCommandCallback.ErrorCode.DATA_SOURCE_UNAVAILABLE; + + @Mock + private Context mContextSpy; + + private ForensicService mForensicService; + private TestLooper mTestLooper; + private Looper mLooper; + + @SuppressLint("VisibleForTests") + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mTestLooper = new TestLooper(); + mLooper = mTestLooper.getLooper(); + mForensicService = new ForensicService(new MockInjector(mContextSpy)); + mForensicService.onStart(); + } + + @Test + public void testMonitorState_Invisible() throws RemoteException { + StateCallback scb = new StateCallback(); + assertEquals(STATE_UNKNOWN, scb.mState); + mForensicService.getBinderService().monitorState(scb); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb.mState); + } + + @Test + public void testMonitorState_Invisible_TwoMonitors() throws RemoteException { + StateCallback scb1 = new StateCallback(); + assertEquals(STATE_UNKNOWN, scb1.mState); + mForensicService.getBinderService().monitorState(scb1); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + + StateCallback scb2 = new StateCallback(); + assertEquals(STATE_UNKNOWN, scb2.mState); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb2.mState); + } + + @Test + public void testMakeVisible_FromInvisible() throws RemoteException { + StateCallback scb = new StateCallback(); + assertEquals(STATE_UNKNOWN, scb.mState); + mForensicService.getBinderService().monitorState(scb); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().makeVisible(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb.mState); + assertNull(ccb.mErrorCode); + } + + @Test + public void testMakeVisible_FromInvisible_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_INVISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().makeVisible(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + assertNull(ccb.mErrorCode); + } + + @Test + public void testMakeVisible_FromVisible_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_VISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().makeVisible(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + assertNull(ccb.mErrorCode); + } + + @Test + public void testMakeVisible_FromEnabled_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_ENABLED); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_ENABLED, scb1.mState); + assertEquals(STATE_ENABLED, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().makeVisible(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_ENABLED, scb1.mState); + assertEquals(STATE_ENABLED, scb2.mState); + assertNotNull(ccb.mErrorCode); + assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue()); + } + + @Test + public void testMakeInvisible_FromInvisible_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_INVISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().makeInvisible(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + assertNull(ccb.mErrorCode); + } + + @Test + public void testMakeInvisible_FromVisible_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_VISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().makeInvisible(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + assertNull(ccb.mErrorCode); + } + + @Test + public void testMakeInvisible_FromEnabled_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_ENABLED); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_ENABLED, scb1.mState); + assertEquals(STATE_ENABLED, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().makeInvisible(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + assertNull(ccb.mErrorCode); + } + + + @Test + public void testEnable_FromInvisible_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_INVISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().enable(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + assertNotNull(ccb.mErrorCode); + assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue()); + } + + @Test + public void testEnable_FromVisible_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_VISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().enable(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_ENABLED, scb1.mState); + assertEquals(STATE_ENABLED, scb2.mState); + assertNull(ccb.mErrorCode); + } + + @Test + public void testEnable_FromEnabled_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_ENABLED); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_ENABLED, scb1.mState); + assertEquals(STATE_ENABLED, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().enable(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_ENABLED, scb1.mState); + assertEquals(STATE_ENABLED, scb2.mState); + assertNull(ccb.mErrorCode); + } + + @Test + public void testDisable_FromInvisible_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_INVISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().disable(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_INVISIBLE, scb1.mState); + assertEquals(STATE_INVISIBLE, scb2.mState); + assertNotNull(ccb.mErrorCode); + assertEquals(ERROR_INVALID_STATE_TRANSITION, ccb.mErrorCode.intValue()); + } + + @Test + public void testDisable_FromVisible_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_VISIBLE); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().disable(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + assertNull(ccb.mErrorCode); + } + + @Test + public void testDisable_FromEnabled_TwoMonitors() throws RemoteException { + mForensicService.setState(STATE_ENABLED); + StateCallback scb1 = new StateCallback(); + StateCallback scb2 = new StateCallback(); + mForensicService.getBinderService().monitorState(scb1); + mForensicService.getBinderService().monitorState(scb2); + mTestLooper.dispatchAll(); + assertEquals(STATE_ENABLED, scb1.mState); + assertEquals(STATE_ENABLED, scb2.mState); + + CommandCallback ccb = new CommandCallback(); + mForensicService.getBinderService().disable(ccb); + mTestLooper.dispatchAll(); + assertEquals(STATE_VISIBLE, scb1.mState); + assertEquals(STATE_VISIBLE, scb2.mState); + assertNull(ccb.mErrorCode); + } + + private class MockInjector implements ForensicService.Injector { + private final Context mContext; + + MockInjector(Context context) { + mContext = context; + } + + @Override + public Context getContext() { + return mContext; + } + + + @Override + public Looper getLooper() { + return mLooper; + } + + } + + private static class StateCallback extends IForensicServiceStateCallback.Stub { + int mState = STATE_UNKNOWN; + + @Override + public void onStateChange(int state) throws RemoteException { + mState = state; + } + } + + private static class CommandCallback extends IForensicServiceCommandCallback.Stub { + Integer mErrorCode = null; + + public void reset() { + mErrorCode = null; + } + + @Override + public void onSuccess() throws RemoteException { + + } + + @Override + public void onFailure(int errorCode) throws RemoteException { + mErrorCode = errorCode; + } + } +} 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 b745e6a7d4a5..e5831b326de5 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 @@ -1417,7 +1417,7 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseMoveEventsDoNotMoveMagnifierViewport() { runMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE); } @@ -1471,55 +1471,55 @@ public class FullScreenMagnificationGestureHandlerTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseHoverMoveEventsDoNotMoveMagnifierViewport() { runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_MOUSE); } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testStylusHoverMoveEventsDoNotMoveMagnifierViewport() { runHoverMoveEventsDoNotMoveMagnifierViewport(InputDevice.SOURCE_STYLUS); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseHoverMoveEventsMoveMagnifierViewport() { runHoverMovesViewportTest(InputDevice.SOURCE_MOUSE); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testStylusHoverMoveEventsMoveMagnifierViewport() { runHoverMovesViewportTest(InputDevice.SOURCE_STYLUS); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseDownEventsDoNotMoveMagnifierViewport() { runDownDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testStylusDownEventsDoNotMoveMagnifierViewport() { runDownDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseUpEventsDoNotMoveMagnifierViewport() { runUpDoesNotMoveViewportTest(InputDevice.SOURCE_MOUSE); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testStylusUpEventsDoNotMoveMagnifierViewport() { runUpDoesNotMoveViewportTest(InputDevice.SOURCE_STYLUS); } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void testMouseMoveEventsMoveMagnifierViewport() { final EventCaptor eventCaptor = new EventCaptor(); mMgh.setNext(eventCaptor); diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java index d80a1f056e94..45c157d1c1a0 100644 --- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java +++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java @@ -93,7 +93,7 @@ public class MagnificationGestureHandlerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() { final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); mouseEvent.setSource(InputDevice.SOURCE_MOUSE); @@ -108,7 +108,7 @@ public class MagnificationGestureHandlerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() { final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); stylusEvent.setSource(InputDevice.SOURCE_STYLUS); @@ -123,7 +123,7 @@ public class MagnificationGestureHandlerTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() { final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); mouseEvent.setSource(InputDevice.SOURCE_MOUSE); @@ -138,7 +138,7 @@ public class MagnificationGestureHandlerTest { } @Test - @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE) + @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE_BUGFIX) public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() { final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0); stylusEvent.setSource(InputDevice.SOURCE_STYLUS); diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index d1f6c2f9f1f0..9c6412b81b34 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -67,10 +67,8 @@ import android.provider.Settings; import androidx.test.InstrumentationRegistry; import com.android.internal.R; -import com.android.internal.pm.parsing.PackageParser2; import com.android.server.compat.PlatformCompat; import com.android.server.integrity.model.IntegrityCheckResult; -import com.android.server.pm.parsing.TestPackageParser2; import com.android.server.testutils.TestUtils; import org.junit.After; @@ -140,8 +138,6 @@ public class AppIntegrityManagerServiceImplTest { @Mock IntegrityFileManager mIntegrityFileManager; @Mock Handler mHandler; - private Supplier<PackageParser2> mParserSupplier = TestPackageParser2::new; - private final Context mRealContext = InstrumentationRegistry.getTargetContext(); private PackageManager mSpyPackageManager; @@ -173,7 +169,6 @@ public class AppIntegrityManagerServiceImplTest { new AppIntegrityManagerServiceImpl( mMockContext, mPackageManagerInternal, - mParserSupplier, mIntegrityFileManager, mHandler); diff --git a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java index a54501029712..f45eddcf4480 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/CallLogQueryHelperTest.java @@ -24,6 +24,7 @@ import static org.mockito.Mockito.when; import android.database.Cursor; import android.database.MatrixCursor; +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.provider.CallLog.Calls; import android.test.mock.MockContentProvider; @@ -58,6 +59,7 @@ public final class CallLogQueryHelperTest { private MatrixCursor mCursor; private EventConsumer mEventConsumer; private CallLogQueryHelper mHelper; + private CallLogContentProvider mCallLogContentProvider; @Before public void setUp() { @@ -66,7 +68,8 @@ public final class CallLogQueryHelperTest { mCursor = new MatrixCursor(CALL_LOG_COLUMNS); MockContentResolver contentResolver = new MockContentResolver(); - contentResolver.addProvider(CALL_LOG_AUTHORITY, new CallLogContentProvider()); + mCallLogContentProvider = new CallLogContentProvider(); + contentResolver.addProvider(CALL_LOG_AUTHORITY, mCallLogContentProvider); when(mContext.getContentResolver()).thenReturn(contentResolver); mEventConsumer = new EventConsumer(); @@ -80,6 +83,12 @@ public final class CallLogQueryHelperTest { } @Test + public void testQueryWithSQLiteException() { + mCallLogContentProvider.setThrowSQLiteException(true); + assertFalse(mHelper.querySince(50L)); + } + + @Test public void testQueryIncomingCall() { mCursor.addRow(new Object[] { NORMALIZED_PHONE_NUMBER, /* date= */ 100L, /* duration= */ 30L, @@ -159,11 +168,20 @@ public final class CallLogQueryHelperTest { } private class CallLogContentProvider extends MockContentProvider { + private boolean mThrowSQLiteException = false; @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + if (mThrowSQLiteException) { + throw new SQLiteException(); + } + return mCursor; } + + public void setThrowSQLiteException(boolean throwException) { + this.mThrowSQLiteException = throwException; + } } } diff --git a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java index 16a02b678511..1daee39ce9de 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/ContactsQueryHelperTest.java @@ -99,6 +99,14 @@ public final class ContactsQueryHelperTest { } @Test + public void testQueryOtherException_returnsFalse() { + contentProvider.setThrowOtherException(true); + + Uri contactUri = Uri.withAppendedPath(Contacts.CONTENT_LOOKUP_URI, CONTACT_LOOKUP_KEY); + assertFalse(mHelper.query(contactUri.toString())); + } + + @Test public void testQueryIllegalArgumentException_returnsFalse() { contentProvider.setThrowIllegalArgumentException(true); @@ -152,6 +160,13 @@ public final class ContactsQueryHelperTest { } @Test + public void testQueryWithPhoneNumber_otherExceptionReturnsFalse() { + contentProvider.setThrowOtherException(true); + String contactUri = "tel:" + PHONE_NUMBER; + assertFalse(mHelper.query(contactUri)); + } + + @Test public void testQueryWithEmail() { mContactsLookupCursor.addRow(new Object[] { /* id= */ 11, CONTACT_LOOKUP_KEY, /* starred= */ 1, /* hasPhoneNumber= */ 0 }); @@ -188,6 +203,7 @@ public final class ContactsQueryHelperTest { private Map<Uri, Cursor> mUriPrefixToCursorMap = new ArrayMap<>(); private boolean mThrowSQLiteException = false; private boolean mThrowIllegalArgumentException = false; + private boolean mThrowOtherException = false; @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, @@ -198,6 +214,9 @@ public final class ContactsQueryHelperTest { if (mThrowIllegalArgumentException) { throw new IllegalArgumentException(); } + if (mThrowOtherException) { + throw new ArrayIndexOutOfBoundsException(); + } for (Uri prefixUri : mUriPrefixToCursorMap.keySet()) { if (uri.isPathPrefixMatch(prefixUri)) { @@ -215,6 +234,10 @@ public final class ContactsQueryHelperTest { this.mThrowIllegalArgumentException = throwException; } + public void setThrowOtherException(boolean throwException) { + this.mThrowOtherException = throwException; + } + private void registerCursor(Uri uriPrefix, Cursor cursor) { mUriPrefixToCursorMap.put(uriPrefix, cursor); } diff --git a/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java index 7730890e1486..9f4a43df6de8 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/MmsQueryHelperTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.database.Cursor; import android.database.MatrixCursor; +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.provider.Telephony.BaseMmsColumns; import android.provider.Telephony.Mms; @@ -63,6 +64,7 @@ public final class MmsQueryHelperTest { private final List<MatrixCursor> mAddrCursors = new ArrayList<>(); private EventConsumer mEventConsumer; private MmsQueryHelper mHelper; + private MmsContentProvider mMmsContentProvider; @Before public void setUp() { @@ -73,7 +75,8 @@ public final class MmsQueryHelperTest { mAddrCursors.add(new MatrixCursor(ADDR_COLUMNS)); MockContentResolver contentResolver = new MockContentResolver(); - contentResolver.addProvider(MMS_AUTHORITY, new MmsContentProvider()); + mMmsContentProvider = new MmsContentProvider(); + contentResolver.addProvider(MMS_AUTHORITY, mMmsContentProvider); when(mContext.getContentResolver()).thenReturn(contentResolver); mEventConsumer = new EventConsumer(); @@ -87,6 +90,12 @@ public final class MmsQueryHelperTest { } @Test + public void testQueryWithSQLiteException() { + mMmsContentProvider.setThrowSQLiteException(true); + assertFalse(mHelper.querySince(50_000L)); + } + + @Test public void testQueryIncomingMessage() { mMmsCursor.addRow(new Object[] { /* id= */ 0, /* date= */ 100L, /* msgBox= */ BaseMmsColumns.MESSAGE_BOX_INBOX }); @@ -159,10 +168,15 @@ public final class MmsQueryHelperTest { } private class MmsContentProvider extends MockContentProvider { + private boolean mThrowSQLiteException = false; @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + if (mThrowSQLiteException) { + throw new SQLiteException(); + } + List<String> segments = uri.getPathSegments(); if (segments.size() == 2 && "addr".equals(segments.get(1))) { int messageId = Integer.valueOf(segments.get(0)); @@ -170,5 +184,9 @@ public final class MmsQueryHelperTest { } return mMmsCursor; } + + public void setThrowSQLiteException(boolean throwException) { + this.mThrowSQLiteException = throwException; + } } } diff --git a/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java b/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java index 5cb8cb4fe9f1..09a0dff77eb0 100644 --- a/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java +++ b/services/tests/servicestests/src/com/android/server/people/data/SmsQueryHelperTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.when; import android.database.Cursor; import android.database.MatrixCursor; +import android.database.sqlite.SQLiteException; import android.net.Uri; import android.provider.Telephony.Sms; import android.provider.Telephony.TextBasedSmsColumns; @@ -59,6 +60,7 @@ public final class SmsQueryHelperTest { private MatrixCursor mSmsCursor; private EventConsumer mEventConsumer; private SmsQueryHelper mHelper; + private SmsContentProvider mSmsContentProvider; @Before public void setUp() { @@ -67,7 +69,8 @@ public final class SmsQueryHelperTest { mSmsCursor = new MatrixCursor(SMS_COLUMNS); MockContentResolver contentResolver = new MockContentResolver(); - contentResolver.addProvider(SMS_AUTHORITY, new SmsContentProvider()); + mSmsContentProvider = new SmsContentProvider(); + contentResolver.addProvider(SMS_AUTHORITY, mSmsContentProvider); when(mContext.getContentResolver()).thenReturn(contentResolver); mEventConsumer = new EventConsumer(); @@ -130,6 +133,12 @@ public final class SmsQueryHelperTest { assertEquals(110L, events.get(1).getTimestamp()); } + @Test + public void testQueryWithSQLiteException() { + mSmsContentProvider.setThrowSQLiteException(true); + assertFalse(mHelper.querySince(50L)); + } + private class EventConsumer implements BiConsumer<String, Event> { private final Map<String, List<Event>> mEventMap = new ArrayMap<>(); @@ -141,11 +150,19 @@ public final class SmsQueryHelperTest { } private class SmsContentProvider extends MockContentProvider { + private boolean mThrowSQLiteException = false; @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { + if (mThrowSQLiteException) { + throw new SQLiteException(); + } return mSmsCursor; } + + public void setThrowSQLiteException(boolean throwException) { + this.mThrowSQLiteException = throwException; + } } } diff --git a/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigReadOnlyFeaturesTest.kt b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigReadOnlyFeaturesTest.kt new file mode 100644 index 000000000000..22d894a5a739 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/systemconfig/SystemConfigReadOnlyFeaturesTest.kt @@ -0,0 +1,167 @@ +/* + * Copyright (C) 2024 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.systemconfig + +import android.content.Context +import android.content.pm.FeatureInfo +import android.util.ArrayMap +import android.util.Xml + +import androidx.test.filters.SmallTest +import androidx.test.platform.app.InstrumentationRegistry +import com.android.server.SystemConfig +import com.google.common.truth.Truth.assertThat +import org.junit.runner.RunWith +import org.junit.Rule + +import org.junit.Test +import org.junit.rules.TemporaryFolder +import org.junit.runners.JUnit4 + +@SmallTest +@RunWith(JUnit4::class) +class SystemConfigReadOnlyFeaturesTest { + + companion object { + private const val FEATURE_ONE = "feature.test.1" + private const val FEATURE_TWO = "feature.test.2" + private const val FEATURE_RUNTIME_AVAILABLE_TEMPLATE = + """ + <permissions> + <feature name="%s" /> + </permissions> + """ + private const val FEATURE_RUNTIME_DISABLED_TEMPLATE = + """ + <permissions> + <Disabled-feature name="%s" /> + </permissions> + """ + + fun featureInfo(featureName: String) = FeatureInfo().apply { name = featureName } + } + + private val context: Context = InstrumentationRegistry.getInstrumentation().context + + @get:Rule + val tempFolder = TemporaryFolder(context.filesDir) + + private val injector = TestInjector() + + private var uniqueCounter = 0 + + @Test + fun empty() { + assertFeatures().isEmpty() + } + + @Test + fun readOnlyEnabled() { + addReadOnlyEnabledFeature(FEATURE_ONE) + addReadOnlyEnabledFeature(FEATURE_TWO) + + assertFeatures().containsAtLeast(FEATURE_ONE, FEATURE_TWO) + } + + @Test + fun readOnlyAndRuntimeEnabled() { + addReadOnlyEnabledFeature(FEATURE_ONE) + addRuntimeEnabledFeature(FEATURE_TWO) + + // No issues with matching availability. + assertFeatures().containsAtLeast(FEATURE_ONE, FEATURE_TWO) + } + + @Test + fun readOnlyEnabledRuntimeDisabled() { + addReadOnlyEnabledFeature(FEATURE_ONE) + addRuntimeDisabledFeature(FEATURE_ONE) + + // Read-only feature availability should take precedence. + assertFeatures().contains(FEATURE_ONE) + } + + @Test + fun readOnlyDisabled() { + addReadOnlyDisabledFeature(FEATURE_ONE) + + assertFeatures().doesNotContain(FEATURE_ONE) + } + + @Test + fun readOnlyAndRuntimeDisabled() { + addReadOnlyDisabledFeature(FEATURE_ONE) + addRuntimeDisabledFeature(FEATURE_ONE) + + // No issues with matching (un)availability. + assertFeatures().doesNotContain(FEATURE_ONE) + } + + @Test + fun readOnlyDisabledRuntimeEnabled() { + addReadOnlyDisabledFeature(FEATURE_ONE) + addRuntimeEnabledFeature(FEATURE_ONE) + addRuntimeEnabledFeature(FEATURE_TWO) + + // Read-only feature (un)availability should take precedence. + assertFeatures().doesNotContain(FEATURE_ONE) + assertFeatures().contains(FEATURE_TWO) + } + + fun addReadOnlyEnabledFeature(featureName: String) { + injector.readOnlyEnabledFeatures[featureName] = featureInfo(featureName) + } + + fun addReadOnlyDisabledFeature(featureName: String) { + injector.readOnlyDisabledFeatures.add(featureName) + } + + fun addRuntimeEnabledFeature(featureName: String) { + FEATURE_RUNTIME_AVAILABLE_TEMPLATE.format(featureName).write() + } + + fun addRuntimeDisabledFeature(featureName: String) { + FEATURE_RUNTIME_DISABLED_TEMPLATE.format(featureName).write() + } + + private fun String.write() = tempFolder.root.resolve("${uniqueCounter++}.xml") + .writeText(this.trimIndent()) + + private fun assertFeatures() = assertThat(availableFeatures().keys) + + private fun availableFeatures() = SystemConfig(false, injector).apply { + val parser = Xml.newPullParser() + readPermissions(parser, tempFolder.root, /*Grant all permission flags*/ 0.inv()) + }.let { it.availableFeatures } + + internal class TestInjector() : SystemConfig.Injector() { + val readOnlyEnabledFeatures = ArrayMap<String, FeatureInfo>() + val readOnlyDisabledFeatures = mutableSetOf<String>() + + override fun isReadOnlySystemEnabledFeature(featureName: String, version: Int): Boolean { + return readOnlyEnabledFeatures.containsKey(featureName) + } + + override fun isReadOnlySystemDisabledFeature(featureName: String, version: Int): Boolean { + return readOnlyDisabledFeatures.contains(featureName) + } + + override fun getReadOnlySystemEnabledFeatures(): ArrayMap<String, FeatureInfo> { + return readOnlyEnabledFeatures + } + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java index c247c08c8010..3b0cb4ad8779 100644 --- a/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/UiModeManagerServiceTest.java @@ -68,6 +68,7 @@ import static org.testng.Assert.assertThrows; import android.Manifest; import android.app.Activity; +import android.app.ActivityManager; import android.app.AlarmManager; import android.app.Flags; import android.app.IOnProjectionStateChangedListener; @@ -247,6 +248,8 @@ public class UiModeManagerServiceTest extends UiServiceTestCase { mInjector = spy(new TestInjector()); mUiManagerService = new UiModeManagerService(mContext, /* setupWizardComplete= */ true, mTwilightManager, mInjector); + // Initialize the current user. + mUiManagerService.setCurrentUser(ActivityManager.getCurrentUser()); try { mUiManagerService.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY); } catch (SecurityException e) {/* ignore for permission denial */} 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 becb9fdabfcf..e845d80b412a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -4838,7 +4838,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { null, mPkg, Process.myUserHandle()); verify(mPreferencesHelper, times(1)).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -4856,7 +4856,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } verify(mPreferencesHelper, never()).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -4871,7 +4871,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { null, mPkg, Process.myUserHandle()); verify(mPreferencesHelper, times(1)).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -4891,7 +4891,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } verify(mPreferencesHelper, never()).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -4913,7 +4913,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } verify(mPreferencesHelper, never()).getNotificationChannels( - anyString(), anyInt(), anyBoolean()); + anyString(), anyInt(), anyBoolean(), anyBoolean()); } @Test @@ -17062,4 +17062,20 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertThat(mService.hasFlag(captor.getValue().getNotification().flags, FLAG_PROMOTED_ONGOING)).isFalse(); } + + @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testAppCannotUseReservedBundleChannels() throws Exception { + mBinderService.getBubblePreferenceForPackage(mPkg, mUid); + NotificationChannel news = mBinderService.getNotificationChannel( + mPkg, mContext.getUserId(), mPkg, NEWS_ID); + assertThat(news).isNotNull(); + + NotificationRecord nr = generateNotificationRecord(news); + mBinderService.enqueueNotificationWithTag(mPkg, mPkg, nr.getSbn().getTag(), + nr.getSbn().getId(), nr.getSbn().getNotification(), nr.getSbn().getUserId()); + waitForIdle(); + + assertThat(mService.mNotificationList).isEmpty(); + } } 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 404ede676f02..b92bdb5f3e6e 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -2547,7 +2547,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // Returns only non-deleted channels List<NotificationChannel> channels = - mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList(); + mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true).getList(); // Default channel + non-deleted channel + system defaults assertEquals(notificationClassification() ? 6 : 2, channels.size()); for (NotificationChannel nc : channels) { @@ -2557,7 +2557,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { } // Returns deleted channels too - channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList(); + channels = mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true, true).getList(); // Includes system channel(s) assertEquals(notificationClassification() ? 7 : 3, channels.size()); for (NotificationChannel nc : channels) { @@ -3199,7 +3199,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.permanentlyDeleteNotificationChannels(PKG_N_MR1, UID_N_MR1); // Only default channel remains - assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true).getList().size()); + assertEquals(1, mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, true, true) + .getList().size()); } @Test @@ -3315,12 +3316,12 @@ public class PreferencesHelperTest extends UiServiceTestCase { // user 0 records remain for (int i = 0; i < user0Uids.length; i++) { assertEquals(notificationClassification() ? 5 : 1, - mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false) + mHelper.getNotificationChannels(PKG_N_MR1, user0Uids[i], false, true) .getList().size()); } // user 1 records are gone for (int i = 0; i < user1Uids.length; i++) { - assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false) + assertEquals(0, mHelper.getNotificationChannels(PKG_N_MR1, user1Uids[i], false, true) .getList().size()); } } @@ -3337,7 +3338,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { new int[]{UID_N_MR1})); assertEquals(0, mHelper.getNotificationChannels( - PKG_N_MR1, UID_N_MR1, true).getList().size()); + PKG_N_MR1, UID_N_MR1, true, true).getList().size()); // Not deleted mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel1, true, false, @@ -3346,7 +3347,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertFalse(mHelper.onPackagesChanged(false, USER_SYSTEM, new String[]{PKG_N_MR1}, new int[]{UID_N_MR1})); assertEquals(notificationClassification() ? 6 : 2, - mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); + mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true) + .getList().size()); } @Test @@ -3405,7 +3407,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(mHelper.canShowBadge(PKG_O, UID_O)); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); assertEquals(0, mHelper.getAppLockedFields(PKG_O, UID_O)); - assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true).getList().size()); + assertEquals(0, mHelper.getNotificationChannels(PKG_O, UID_O, true, true).getList().size()); assertEquals(0, mHelper.getNotificationChannelGroups(PKG_O, UID_O).size()); NotificationChannel channel = getChannel(); @@ -3419,7 +3421,8 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testRecordDefaults() throws Exception { assertEquals(true, mHelper.canShowBadge(PKG_N_MR1, UID_N_MR1)); assertEquals(notificationClassification() ? 5 : 1, - mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false).getList().size()); + mHelper.getNotificationChannels(PKG_N_MR1, UID_N_MR1, false, true) + .getList().size()); } @Test @@ -6363,6 +6366,15 @@ public class PreferencesHelperTest extends UiServiceTestCase { @Test @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) + public void testGetNotificationChannels_omitBundleChannels() { + // do something that triggers settings creation for an app + mHelper.setShowBadge(PKG_O, UID_O, true); + + assertThat(mHelper.getNotificationChannels(PKG_O, UID_O, true, false).getList()).isEmpty(); + } + + @Test + @EnableFlags(FLAG_NOTIFICATION_CLASSIFICATION) public void testNotificationBundles() { // do something that triggers settings creation for an app mHelper.setShowBadge(PKG_O, UID_O, true); 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 b7821623855c..7f5da41bdf10 100644 --- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java +++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java @@ -16,8 +16,6 @@ package com.android.server.vibrator; -import static android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED; - import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertArrayEquals; @@ -28,6 +26,7 @@ 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.junit.Assume.assumeTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -89,17 +88,14 @@ import android.os.vibrator.PrimitiveSegment; import android.os.vibrator.StepSegment; import android.os.vibrator.VibrationConfig; import android.os.vibrator.VibrationEffectSegment; -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.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.view.HapticFeedbackConstants; import android.view.InputDevice; -import android.view.flags.Flags; import androidx.test.InstrumentationRegistry; @@ -168,9 +164,7 @@ public class VibratorManagerServiceTest { @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule(); @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - - @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); @Mock private VibratorManagerService.NativeWrapper mNativeWrapperMock; @@ -800,7 +794,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS) + @EnableFlags(android.os.vibrator.Flags.FLAG_CANCEL_BY_APPOPS) public void vibrate_thenDeniedAppOps_getsCancelled() throws Throwable { mockVibrators(1); VibratorManagerService service = createSystemReadyService(); @@ -894,7 +888,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS) + @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS) public void vibrate_thenFgUserRequestsMute_getsCancelled() throws Throwable { mockVibrators(1); VibratorManagerService service = createSystemReadyService(); @@ -1331,6 +1325,37 @@ public class VibratorManagerServiceTest { } @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_VIBRATION_PIPELINE_ENABLED) + public void vibrate_withPipelineFlagEnabledAndShortEffect_continuesOngoingEffect() + throws Exception { + assumeTrue(mVibrationConfig.getVibrationPipelineMaxDurationMs() > 0); + + mockVibrators(1); + FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); + fakeVibrator.setCapabilities(IVibrator.CAP_COMPOSE_EFFECTS); + fakeVibrator.setSupportedPrimitives( + VibrationEffect.Composition.PRIMITIVE_CLICK, + VibrationEffect.Composition.PRIMITIVE_THUD); + fakeVibrator.setPrimitiveDuration( + mVibrationConfig.getVibrationPipelineMaxDurationMs() - 1); + VibratorManagerService service = createSystemReadyService(); + + HalVibration firstVibration = vibrateWithUid(service, /* uid= */ 123, + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK) + .compose(), HAPTIC_FEEDBACK_ATTRS); + HalVibration secondVibration = vibrateWithUid(service, /* uid= */ 456, + VibrationEffect.startComposition() + .addPrimitive(VibrationEffect.Composition.PRIMITIVE_THUD) + .compose(), HAPTIC_FEEDBACK_ATTRS); + secondVibration.waitForEnd(); + + assertThat(fakeVibrator.getAllEffectSegments()).hasSize(2); + assertThat(firstVibration.getStatus()).isEqualTo(Status.FINISHED); + assertThat(secondVibration.getStatus()).isEqualTo(Status.FINISHED); + } + + @Test public void vibrate_withInputDevices_vibratesInputDevices() throws Exception { mockVibrators(1); FakeVibratorControllerProvider fakeVibrator = mVibratorProviders.get(1); @@ -1512,6 +1537,7 @@ public class VibratorManagerServiceTest { } @Test + @EnableFlags(android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API) public void performHapticFeedback_doesNotRequireVibrateOrBypassPermissions() throws Exception { // Deny permissions that would have been required for regular vibrations, and check that // the vibration proceed as expected to verify that haptic feedback does not need these @@ -1520,8 +1546,6 @@ public class VibratorManagerServiceTest { denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS); denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE); denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING); - // Flag override to enable the scroll feedack constants to bypass interruption policies. - mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API); mHapticFeedbackVibrationMap.put( HapticFeedbackConstants.SCROLL_TICK, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); @@ -1544,6 +1568,10 @@ public class VibratorManagerServiceTest { } @Test + @EnableFlags({ + android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API, + android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED, + }) public void performHapticFeedbackForInputDevice_doesNotRequireVibrateOrBypassPermissions() throws Exception { // Deny permissions that would have been required for regular vibrations, and check that @@ -1553,9 +1581,6 @@ public class VibratorManagerServiceTest { denyPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS); denyPermission(android.Manifest.permission.MODIFY_PHONE_STATE); denyPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING); - // Flag override to enable the scroll feedback constants to bypass interruption policies. - mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API); - mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); mHapticFeedbackVibrationMapSourceRotary.put( HapticFeedbackConstants.SCROLL_TICK, VibrationEffect.createPredefined(VibrationEffect.EFFECT_CLICK)); @@ -1628,12 +1653,14 @@ public class VibratorManagerServiceTest { } @Test + @EnableFlags({ + android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API, + android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED, + }) public void performHapticFeedbackForInputDevice_restrictedConstantsWithoutPermission_doesNotVibrate() throws Exception { // Deny permission to vibrate with restricted constants denyPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS); - mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API); - mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); // Public constant, no permission required mHapticFeedbackVibrationMapSourceRotary.put( HapticFeedbackConstants.CONFIRM, @@ -1697,9 +1724,9 @@ public class VibratorManagerServiceTest { } @Test + @EnableFlags(android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED) public void performHapticFeedbackForInputDevice_restrictedConstantsWithPermission_playsVibration() throws Exception { - mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); // Grant permission to vibrate with restricted constants grantPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS); // Public constant, no permission required @@ -1732,9 +1759,11 @@ public class VibratorManagerServiceTest { } @Test + @EnableFlags({ + android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API, + android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED, + }) public void performHapticFeedback_doesNotVibrateWhenVibratorInfoNotReady() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API); - mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); denyPermission(android.Manifest.permission.VIBRATE); mHapticFeedbackVibrationMap.put( HapticFeedbackConstants.KEYBOARD_TAP, @@ -1767,9 +1796,11 @@ public class VibratorManagerServiceTest { } @Test + @EnableFlags({ + android.view.flags.Flags.FLAG_SCROLL_FEEDBACK_API, + android.os.vibrator.Flags.FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED, + }) public void performHapticFeedback_doesNotVibrateForInvalidConstant() throws Exception { - mSetFlagsRule.enableFlags(Flags.FLAG_SCROLL_FEEDBACK_API); - mSetFlagsRule.enableFlags(FLAG_HAPTIC_FEEDBACK_INPUT_SOURCE_CUSTOMIZATION_ENABLED); denyPermission(android.Manifest.permission.VIBRATE); mockVibrators(1); VibratorManagerService service = createSystemReadyService(); @@ -1791,7 +1822,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrate_vendorEffectsWithoutPermission_doesNotVibrate() throws Exception { // Deny permission to vibrate with vendor effects denyPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS); @@ -1816,7 +1847,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) + @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS) public void vibrate_vendorEffectsWithPermission_successful() throws Exception { // Grant permission to vibrate with vendor effects grantPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS); @@ -1904,7 +1935,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) + @EnableFlags(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) public void vibrate_withAdaptiveHaptics_appliesCorrectAdaptiveScales() throws Exception { // Keep user settings the same as device default so only adaptive scale is applied. setUserSetting(Settings.System.ALARM_VIBRATION_INTENSITY, @@ -1947,7 +1978,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsEnabled({ + @EnableFlags({ android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED, android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS, }) @@ -2418,7 +2449,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) + @EnableFlags(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) public void onExternalVibration_withAdaptiveHaptics_returnsCorrectAdaptiveScales() { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL, @@ -2465,7 +2496,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsDisabled(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) + @DisableFlags(android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED) public void onExternalVibration_withAdaptiveHapticsFlagDisabled_alwaysReturnScaleNone() { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL, @@ -2585,7 +2616,7 @@ public class VibratorManagerServiceTest { } @Test - @RequiresFlagsEnabled(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS) + @EnableFlags(android.multiuser.Flags.FLAG_ADD_UI_FOR_SOUNDS_FROM_BACKGROUND_USERS) public void onExternalVibration_thenFgUserRequestsMute_doNotCancelVibration() throws Throwable { mockVibrators(1); mVibratorProviders.get(1).setCapabilities(IVibrator.CAP_EXTERNAL_CONTROL); @@ -3105,9 +3136,20 @@ public class VibratorManagerServiceTest { return vibrateWithDevice(service, Context.DEVICE_ID_DEFAULT, effect, attrs); } + private HalVibration vibrateWithUid(VibratorManagerService service, int uid, + VibrationEffect effect, VibrationAttributes attrs) { + return vibrateWithUidAndDevice(service, uid, Context.DEVICE_ID_DEFAULT, + CombinedVibration.createParallel(effect), attrs); + } + private HalVibration vibrateWithDevice(VibratorManagerService service, int deviceId, CombinedVibration effect, VibrationAttributes attrs) { - HalVibration vib = service.vibrateWithPermissionCheck(UID, deviceId, PACKAGE_NAME, effect, + return vibrateWithUidAndDevice(service, UID, deviceId, effect, attrs); + } + + private HalVibration vibrateWithUidAndDevice(VibratorManagerService service, int uid, + int deviceId, CombinedVibration effect, VibrationAttributes attrs) { + HalVibration vib = service.vibrateWithPermissionCheck(uid, deviceId, PACKAGE_NAME, effect, attrs, "some reason", service); if (vib != null) { mPendingVibrations.add(vib); diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java index 6dc1b10ec930..75a9cedfd8c4 100644 --- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java +++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java @@ -80,6 +80,7 @@ public final class FakeVibratorControllerProvider { private float[] mFrequenciesHz; private float[] mOutputAccelerationsGs; private long mVendorEffectDuration = EFFECT_DURATION; + private long mPrimitiveDuration = EFFECT_DURATION; void recordEffectSegment(long vibrationId, VibrationEffectSegment segment) { mEffectSegments.computeIfAbsent(vibrationId, k -> new ArrayList<>()).add(segment); @@ -171,7 +172,7 @@ public final class FakeVibratorControllerProvider { } long duration = 0; for (PrimitiveSegment primitive : primitives) { - duration += EFFECT_DURATION + primitive.getDelay(); + duration += mPrimitiveDuration + primitive.getDelay(); recordEffectSegment(vibrationId, primitive); } applyLatency(mOnLatency); @@ -381,6 +382,11 @@ public final class FakeVibratorControllerProvider { mVendorEffectDuration = durationMs; } + /** Set the duration of primitives in fake vibrator hardware. */ + public void setPrimitiveDuration(long primitiveDuration) { + mPrimitiveDuration = primitiveDuration; + } + /** * Set the maximum number of envelope effects control points supported in fake vibrator * hardware. diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp index ab00bfdf41ae..1f167761fc06 100644 --- a/services/tests/wmtests/Android.bp +++ b/services/tests/wmtests/Android.bp @@ -71,6 +71,7 @@ android_test { "CtsSurfaceValidatorLib", "service-sdksandbox.impl", "com.android.window.flags.window-aconfig-java", + "android.view.inputmethod.flags-aconfig-java", "flag-junit", ], diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java index d0080d29f82b..d5f86b6feac8 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java @@ -1261,6 +1261,33 @@ public class ActivityStarterTests extends WindowTestsBase { } @Test + public void testLaunchAdjacentDisabled() { + final ActivityStarter starter = + prepareStarter(FLAG_ACTIVITY_LAUNCH_ADJACENT, false /* mockGetRootTask */); + final ActivityRecord top = new ActivityBuilder(mAtm).setCreateTask(true).build(); + final ActivityOptions options = ActivityOptions.makeBasic(); + final ActivityRecord[] outActivity = new ActivityRecord[1]; + + // Activity must not land on split-screen task if currently not in split-screen mode. + starter.setActivityOptions(options.toBundle()) + .setReason("testLaunchAdjacentDisabled") + .setOutActivity(outActivity).execute(); + assertThat(outActivity[0].inMultiWindowMode()).isFalse(); + + // Move activity to split-screen-primary task and make sure it has the focus. + TestSplitOrganizer splitOrg = new TestSplitOrganizer(mAtm, top.getDisplayContent()); + top.getRootTask().reparent(splitOrg.mPrimary, POSITION_BOTTOM); + top.getRootTask().moveToFront("testLaunchAdjacentDisabled"); + top.getRootTask().setLaunchAdjacentDisabled(true); + + // Ensure activity does not launch into split-screen-secondary when launch adjacent is + // disabled + startActivityInner(starter, outActivity[0], top, options, null /* inTask */, + null /* taskFragment*/); + assertThat(outActivity[0].isDescendantOf(splitOrg.mSecondary)).isFalse(); + } + + @Test public void testTransientLaunchWithKeyguard() { final ActivityStarter starter = prepareStarter(0 /* flags */); final ActivityRecord target = new ActivityBuilder(mAtm).setCreateTask(true).build(); 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 d2cf03dd4b9a..ee56210e278d 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java @@ -41,11 +41,13 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.verify; import android.app.StatusBarManager; +import android.graphics.Insets; import android.graphics.Rect; import android.os.Binder; import android.platform.test.annotations.DisableFlags; import android.platform.test.annotations.EnableFlags; import android.platform.test.annotations.Presubmit; +import android.platform.test.annotations.RequiresFlagsEnabled; import android.view.InsetsFrameProvider; import android.view.InsetsSource; import android.view.InsetsSourceControl; @@ -525,6 +527,59 @@ public class InsetsPolicyTest extends WindowTestsBase { assertTrue(win1.getWindowFrames().hasInsetsChanged()); } + /** + * This test verifies that after setting {@link WindowContainer#mExcludeInsetsTypes}, the IME + * insets have a height of zero (applied in {@link InsetsPolicy#adjustVisibilityForIme}). + */ + @RequiresFlagsEnabled(android.view.inputmethod.Flags.FLAG_REFACTOR_INSETS_CONTROLLER) + @SetupWindows(addWindows = W_INPUT_METHOD) + @Test + public void testExcludeImeInsets() { + final DisplayPolicy displayPolicy = mDisplayContent.getDisplayPolicy(); + final InsetsSource imeSource = new InsetsSource(ID_IME, ime()); + imeSource.setVisible(true); + mImeWindow.mHasSurface = true; + + final WindowState win = addWindow(TYPE_APPLICATION, "win1"); + win.setRequestedVisibleTypes(0, ime()); + + win.mAboveInsetsState.addSource(imeSource); + win.mHasSurface = true; + + DisplayContentTests.performLayout(mDisplayContent); + // IME should cover half of the app's window + final var winFrame = win.getFrame(); + imeSource.setFrame(winFrame.left, winFrame.bottom / 2, winFrame.right, winFrame.bottom); + imeSource.setVisibleFrame(imeSource.getFrame()); + DisplayContentTests.performLayout(mDisplayContent); + + assertTrue(mImeWindow.isVisible()); + assertTrue(win.isVisible()); + + displayPolicy.beginPostLayoutPolicyLw(); + displayPolicy.applyPostLayoutPolicyLw(win, win.mAttrs, null, null); + displayPolicy.finishPostLayoutPolicyLw(); + + final var imeInsetsShown = win.getInsetsState().calculateInsets(win.getFrame(), ime(), + true); + assertEquals(new Rect(0, 0, 0, winFrame.bottom / 2), imeInsetsShown.toRect()); + + + // Now we're setting the excludedInsetsTypes for the IME. The IME is still showing, but + // in this case, InsetsPolicy#adjustVisibilityForIme will override and dispatch IME + // insets with zero height. + win.setExcludeInsetsTypes(ime()); + + displayPolicy.beginPostLayoutPolicyLw(); + displayPolicy.applyPostLayoutPolicyLw(win, win.mAttrs, null, null); + displayPolicy.finishPostLayoutPolicyLw(); + + final var imeInsetsHidden = win.getInsetsState().calculateInsets(win.getFrame(), ime(), + true); + assertEquals(Insets.NONE, imeInsetsHidden); + } + + 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/InsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java index 457058849fca..79967b861ea5 100644 --- a/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/InsetsSourceProviderTest.java @@ -30,6 +30,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import android.graphics.Insets; +import android.graphics.Point; import android.graphics.Rect; import android.platform.test.annotations.Presubmit; import android.view.InsetsSource; @@ -260,6 +261,27 @@ public class InsetsSourceProviderTest extends WindowTestsBase { } @Test + public void testUpdateInsetsControlPosition() { + final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); + + final WindowState ime1 = createWindow(null, TYPE_INPUT_METHOD, "ime1"); + ime1.getFrame().set(new Rect(0, 0, 0, 0)); + mImeProvider.setWindowContainer(ime1, null, null); + mImeProvider.updateControlForTarget(target, false /* force */, null /* statsToken */); + ime1.getFrame().set(new Rect(0, 400, 500, 500)); + mImeProvider.updateInsetsControlPosition(ime1); + assertEquals(new Point(0, 400), mImeProvider.getControl(target).getSurfacePosition()); + + final WindowState ime2 = createWindow(null, TYPE_INPUT_METHOD, "ime2"); + ime2.getFrame().set(new Rect(0, 0, 0, 0)); + mImeProvider.setWindowContainer(ime2, null, null); + mImeProvider.updateControlForTarget(target, false /* force */, null /* statsToken */); + ime2.getFrame().set(new Rect(0, 400, 500, 500)); + mImeProvider.updateInsetsControlPosition(ime2); + assertEquals(new Point(0, 400), mImeProvider.getControl(target).getSurfacePosition()); + } + + @Test public void testSetRequestedVisibleTypes() { final WindowState statusBar = createWindow(null, TYPE_APPLICATION, "statusBar"); final WindowState target = createWindow(null, TYPE_APPLICATION, "target"); diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java index 72f4fa9158fb..c30f70ee2903 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java @@ -183,15 +183,32 @@ public class SizeCompatTests extends WindowTestsBase { DeviceConfig.setProperties(mInitialConstrainDisplayApisFlags); } - private void setUpApp(DisplayContent display) { - mTask = new TaskBuilder(mSupervisor).setDisplay(display).setCreateActivity(true).build(); - mActivity = mTask.getTopNonFinishingActivity(); + private ActivityRecord setUpApp(DisplayContent display) { + return setUpApp(display, null /* appBuilder */); + } + + private ActivityRecord setUpApp(DisplayContent display, ActivityBuilder appBuilder) { + // Use the real package name (com.android.frameworks.wmtests) so that + // EnableCompatChanges/DisableCompatChanges can take effect. + // Otherwise the fake WindowTestsBase.DEFAULT_COMPONENT_PACKAGE_NAME will make + // PlatformCompat#isChangeEnabledByPackageName always return default value. + final ComponentName componentName = ComponentName.createRelative( + mContext, SizeCompatTests.class.getName()); + mTask = new TaskBuilder(mSupervisor).setDisplay(display).setComponent(componentName) + .build(); + final ActivityBuilder builder = appBuilder != null ? appBuilder : new ActivityBuilder(mAtm); + mActivity = builder.setTask(mTask).setComponent(componentName).build(); doReturn(false).when(mActivity).isImmersiveMode(any()); + return mActivity; + } + + private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh) { + return setUpDisplaySizeWithApp(dw, dh, null /* appBuilder */); } - private void setUpDisplaySizeWithApp(int dw, int dh) { + private ActivityRecord setUpDisplaySizeWithApp(int dw, int dh, ActivityBuilder appBuilder) { final TestDisplayContent.Builder builder = new TestDisplayContent.Builder(mAtm, dw, dh); - setUpApp(builder.build()); + return setUpApp(builder.build(), appBuilder); } @Test @@ -352,15 +369,13 @@ public class SizeCompatTests extends WindowTestsBase { .setCanRotate(false) .setNotch(cutoutHeight) .build(); - setUpApp(display); display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); mWm.mAppCompatConfiguration.setLetterboxVerticalPositionMultiplier(0.5f); mWm.mAppCompatConfiguration.setIsVerticalReachabilityEnabled(true); - final ActivityRecord activity = getActivityBuilderOnSameTask() + final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm) .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) - .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE) - .build(); + .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE)); doReturn(true).when(activity).isImmersiveMode(any()); addWindowToActivity(activity); @@ -424,17 +439,16 @@ public class SizeCompatTests extends WindowTestsBase { @Test @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testFixedAspectRatioBoundsWithDecorInSquareDisplay() { - final int notchHeight = 100; - setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800).setNotch(notchHeight).build()); - - final Rect displayBounds = mActivity.mDisplayContent.getWindowConfiguration().getBounds(); final float aspectRatio = 1.2f; - final ActivityRecord activity = getActivityBuilderOnSameTask() + final ActivityBuilder activityBuilder = new ActivityBuilder(mAtm) .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED) .setMinAspectRatio(aspectRatio) .setMaxAspectRatio(aspectRatio) - .setResizeMode(RESIZE_MODE_UNRESIZEABLE) - .build(); + .setResizeMode(RESIZE_MODE_UNRESIZEABLE); + final int notchHeight = 100; + final ActivityRecord activity = setUpApp(new TestDisplayContent.Builder(mAtm, 600, 800) + .setNotch(notchHeight).build(), activityBuilder); + final Rect displayBounds = activity.mDisplayContent.getWindowConfiguration().getBounds(); final Rect appBounds = activity.getWindowConfiguration().getAppBounds(); // The parent configuration doesn't change since the first resolved configuration, so the @@ -1327,12 +1341,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideMinAspectRatioSmall_overridden() { final int dh = 1200; final int dw = 1000; - setUpDisplaySizeWithApp(dw, dh); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(dw, dh, new ActivityBuilder(mAtm) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); final Rect bounds = activity.getBounds(); assertEquals(dh, bounds.height()); @@ -1346,13 +1356,9 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideMinAspectRatioSmall_notOverridden() { final int dh = 1200; final int dw = 1000; - setUpDisplaySizeWithApp(dw, dh); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(dw, dh, new ActivityBuilder(mAtm) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE)); // Activity's requested aspect ratio is larger than OVERRIDE_MIN_ASPECT_RATIO_SMALL, // so OVERRIDE_MIN_ASPECT_RATIO_SMALL is ignored. @@ -1366,12 +1372,8 @@ public class SizeCompatTests extends WindowTestsBase { @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioMedium() { - setUpDisplaySizeWithApp(1000, 1200); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); // The per-package override forces the activity into a 3:2 aspect ratio assertEquals(1200, activity.getBounds().height()); @@ -1388,13 +1390,8 @@ public class SizeCompatTests extends WindowTestsBase { final int notchHeight = 200; final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1400, dh) .setNotch(notchHeight).setSystemDecorations(true).build(); - mTask = new TaskBuilder(mSupervisor).setDisplay(display).build(); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setMinAspectRatio(2f) - .build(); + final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT).setMinAspectRatio(2f)); // The per-package override should have no effect, because the manifest aspect ratio is // larger (2:1) @@ -1411,13 +1408,9 @@ public class SizeCompatTests extends WindowTestsBase { @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) public void testOverrideMinAspectRatioLargerThanManifest() { - setUpDisplaySizeWithApp(1400, 1600); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .setMinAspectRatio(1.1f) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1400, 1600, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) + .setMinAspectRatio(1.1f)); // The per-package override should have no effect, because the manifest aspect ratio is // larger (2:1) @@ -1430,12 +1423,8 @@ public class SizeCompatTests extends WindowTestsBase { @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) public void testOverrideMinAspectRatioLarge() { - setUpDisplaySizeWithApp(1500, 1600); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1500, 1600, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); // The per-package override forces the activity into a 16:9 aspect ratio assertEquals(1600, activity.getBounds().height()); @@ -1449,13 +1438,8 @@ public class SizeCompatTests extends WindowTestsBase { ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) public void testOverrideMinAspectRatio_Both() { // If multiple override aspect ratios are set, we should use the largest one - - setUpDisplaySizeWithApp(1400, 1600); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1400, 1600, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); // The per-package override forces the activity into a 16:9 aspect ratio assertEquals(1600, activity.getBounds().height()); @@ -1470,11 +1454,7 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideMinAspectRatioScreenOrientationNotSetThenChangedToPortrait() { // In this test, the activity's orientation isn't fixed to portrait, therefore the override // isn't applied. - - setUpDisplaySizeWithApp(1000, 1200); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask().build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200); // The per-package override should have no effect assertEquals(1200, activity.getBounds().height()); @@ -1497,13 +1477,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideMinAspectRatioScreenOrientationLandscapeThenChangedToPortrait() { // In this test, the activity's orientation isn't fixed to portrait, therefore the override // isn't applied. - - setUpDisplaySizeWithApp(1000, 1200); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)); // The per-package override should have no effect assertEquals(1200, activity.getBounds().height()); @@ -1524,12 +1499,8 @@ public class SizeCompatTests extends WindowTestsBase { ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) public void testOverrideMinAspectRatioScreenOrientationPortraitThenChangedToUnspecified() { - setUpDisplaySizeWithApp(1000, 1200); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); // The per-package override forces the activity into a 3:2 aspect ratio assertEquals(1200, activity.getBounds().height()); @@ -1550,10 +1521,7 @@ public class SizeCompatTests extends WindowTestsBase { ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM}) @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY}) public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationNotSet() { - setUpDisplaySizeWithApp(1000, 1200); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask().build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200); // The per-package override forces the activity into a 3:2 aspect ratio assertEquals(1200, activity.getBounds().height()); @@ -1568,13 +1536,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideMinAspectRatioPortraitOnlyDisabledScreenOrientationLandscape() { // In this test, the activity's orientation isn't fixed to portrait, therefore the override // isn't applied. - - setUpDisplaySizeWithApp(1000, 1200); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)); // The per-package override forces the activity into a 3:2 aspect ratio assertEquals(1000 / ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, @@ -1588,12 +1551,8 @@ public class SizeCompatTests extends WindowTestsBase { // In this test, only OVERRIDE_MIN_ASPECT_RATIO_1_5 is set, which has no effect without // OVERRIDE_MIN_ASPECT_RATIO being also set. - setUpDisplaySizeWithApp(1000, 1200); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(1000, 1200, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE)); // The per-package override should have no effect assertEquals(1200, activity.getBounds().height()); @@ -1604,12 +1563,8 @@ public class SizeCompatTests extends WindowTestsBase { @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE}) public void testOverrideMinAspectRatioLargeForResizableAppInSplitScreen() { - setUpDisplaySizeWithApp(/* dw= */ 1000, /* dh= */ 2800); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(/* dw= */ 1000, /* dh= */ 2800, + new ActivityBuilder(mAtm).setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, activity.getDisplayContent()); @@ -2462,10 +2417,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideSplitScreenAspectRatioForUnresizablePortraitApps() { final int displayWidth = 1400; final int displayHeight = 1600; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setMinAspectRatio(1.1f) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight, + new ActivityBuilder(mAtm).setMinAspectRatio(1.1f)); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); @@ -2483,10 +2436,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideSplitScreenAspectRatioForUnresizablePortraitAppsFromLandscape() { final int displayWidth = 1600; final int displayHeight = 1400; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setMinAspectRatio(1.1f) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight, + new ActivityBuilder(mAtm).setMinAspectRatio(1.1f)); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); @@ -2505,10 +2456,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeApps() { final int displayWidth = 1400; final int displayHeight = 1600; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setMinAspectRatio(1.1f) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight, + new ActivityBuilder(mAtm).setMinAspectRatio(1.1f)); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); @@ -2527,10 +2476,8 @@ public class SizeCompatTests extends WindowTestsBase { public void testOverrideSplitScreenAspectRatioForUnresizableLandscapeAppsFromLandscape() { final int displayWidth = 1600; final int displayHeight = 1400; - setUpDisplaySizeWithApp(displayWidth, displayHeight); - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setMinAspectRatio(1.1f) - .build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(displayWidth, displayHeight, + new ActivityBuilder(mAtm).setMinAspectRatio(1.1f)); // Setup Letterbox Configuration activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); activity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.5f); @@ -2549,8 +2496,7 @@ public class SizeCompatTests extends WindowTestsBase { mAtm.mDevEnableNonResizableMultiWindow = true; final int screenWidth = 1800; final int screenHeight = 1000; - setUpDisplaySizeWithApp(screenWidth, screenHeight); - final ActivityRecord activity = getActivityBuilderOnSameTask().build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(screenWidth, screenHeight); activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Simulate real display with top insets. @@ -2585,8 +2531,7 @@ public class SizeCompatTests extends WindowTestsBase { mAtm.mDevEnableNonResizableMultiWindow = true; final int screenWidth = 1000; final int screenHeight = 1800; - setUpDisplaySizeWithApp(screenWidth, screenHeight); - final ActivityRecord activity = getActivityBuilderOnSameTask().build(); + final ActivityRecord activity = setUpDisplaySizeWithApp(screenWidth, screenHeight); activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Simulate real display with top insets. @@ -2616,18 +2561,18 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN}) public void testOverrideMinAspectRatioExcludePortraitFullscreen() { - setUpDisplaySizeWithApp(2600, 1600); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); - - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask().build(); + resizeDisplay(mDisplayContent, 2600, 1600); + mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); // Non-resizable portrait activity - prepareUnresizable(activity, 0f, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + final ActivityRecord activity = setUpApp(mDisplayContent, new ActivityBuilder(mAtm) + .setResizeMode(RESIZE_MODE_UNRESIZEABLE) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); // At first, OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_FULLSCREEN does not apply, because the // display is in landscape @@ -2645,16 +2590,16 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO, + ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE, ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN}) public void testOverrideMinAspectRatioExcludePortraitFullscreenNotApplied() { // In this test, the activity is not in fullscreen, so the override is not applied - setUpDisplaySizeWithApp(2600, 1600); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - mActivity.mWmService.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); + resizeDisplay(mDisplayContent, 2600, 1600); + mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); + mWm.mAppCompatConfiguration.setFixedOrientationLetterboxAspectRatio(1.33f); - // Create a size compat activity on the same task. - final ActivityRecord activity = getActivityBuilderOnSameTask().build(); + final ActivityRecord activity = setUpApp(mDisplayContent); final TestSplitOrganizer organizer = new TestSplitOrganizer(mAtm, activity.getDisplayContent()); @@ -2983,6 +2928,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION}) public void testDisplayIgnoreOrientationRequest_orientationChangedToUnspecified() { // Set up a display in landscape and ignoring orientation request. setUpDisplaySizeWithApp(2800, 1400); @@ -3437,6 +3383,7 @@ public class SizeCompatTests extends WindowTestsBase { } @Test + @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION}) public void testSupportsNonResizableInSplitScreen_letterboxForAspectRatioRestriction() { // Support non resizable in multi window mAtm.mDevEnableNonResizableMultiWindow = true; @@ -4050,9 +3997,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test @EnableCompatChanges({ActivityInfo.OVERRIDE_ENABLE_INSETS_DECOUPLED_CONFIGURATION}) public void testPortraitCloseToSquareDisplayWithTaskbar_insetsOverridden_notLetterboxed() { + final DisplayContent display = mDisplayContent; // Set up portrait close to square display. - setUpDisplaySizeWithApp(2200, 2280); - final DisplayContent display = mActivity.mDisplayContent; + resizeDisplay(display, 2200, 2280); display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Simulate insets, final app bounds are (0, 0, 2200, 2130) - landscape. final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, @@ -4067,9 +4014,8 @@ public class SizeCompatTests extends WindowTestsBase { display.sendNewConfiguration(); } - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); // Activity should not be letterboxed and should have portrait app bounds even though // orientation is not respected with insets as insets have been decoupled. @@ -4085,9 +4031,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testPortraitCloseToSquareDisplayWithTaskbar_letterboxed() { + final DisplayContent display = mDisplayContent; // Set up portrait close to square display - setUpDisplaySizeWithApp(2200, 2280); - final DisplayContent display = mActivity.mDisplayContent; + resizeDisplay(display, 2200, 2280); display.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, @@ -4102,9 +4048,8 @@ public class SizeCompatTests extends WindowTestsBase { display.sendNewConfiguration(); } - final ActivityRecord activity = getActivityBuilderOnSameTask() - .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT) - .build(); + final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm) + .setScreenOrientation(SCREEN_ORIENTATION_PORTRAIT)); final Rect bounds = activity.getBounds(); // Activity should be letterboxed and should have portrait app bounds @@ -4116,9 +4061,9 @@ public class SizeCompatTests extends WindowTestsBase { @Test @DisableCompatChanges({ActivityInfo.INSETS_DECOUPLED_CONFIGURATION_ENFORCED}) public void testFixedAspectRatioAppInPortraitCloseToSquareDisplay_notInSizeCompat() { - setUpDisplaySizeWithApp(2200, 2280); - mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); - final DisplayContent dc = mActivity.mDisplayContent; + final DisplayContent dc = mDisplayContent; + resizeDisplay(dc, 2200, 2280); + dc.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */); // Simulate taskbar, final app bounds are (0, 0, 2200, 2130) - landscape final WindowState navbar = createWindow(null, TYPE_NAVIGATION_BAR, mDisplayContent, "navbar"); @@ -4132,11 +4077,10 @@ public class SizeCompatTests extends WindowTestsBase { dc.sendNewConfiguration(); } - final ActivityRecord activity = getActivityBuilderOnSameTask() + final ActivityRecord activity = setUpApp(dc, new ActivityBuilder(mAtm) .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .setScreenOrientation(SCREEN_ORIENTATION_LANDSCAPE) - .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE) - .build(); + .setMinAspectRatio(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE)); // To force config to update again but with the same landscape orientation. activity.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE); @@ -4174,13 +4118,10 @@ public class SizeCompatTests extends WindowTestsBase { .setCutout(0, 100, 0, 150) .build(); - setUpApp(display); - - final ActivityRecord activity = getActivityBuilderOnSameTask() + final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm) .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .setMaxAspectRatio(2.1f) - .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED) - .build(); + .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); // The activity height is 2100 and the display's app bounds height is 2050, so the activity // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display @@ -4196,13 +4137,10 @@ public class SizeCompatTests extends WindowTestsBase { .setCutout(100, 0, 150, 0) .build(); - setUpApp(display); - - final ActivityRecord activity = getActivityBuilderOnSameTask() + final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm) .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .setMaxAspectRatio(2.1f) - .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED) - .build(); + .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); // The activity width is 2100 and the display's app bounds width is 2250, so the activity // can be aligned inside parentAppBounds assertEquals(activity.getBounds(), new Rect(175, 0, 2275, 1000)); @@ -4216,12 +4154,10 @@ public class SizeCompatTests extends WindowTestsBase { .setCutout(100, 0, 150, 0) .build(); - setUpApp(display); - final ActivityRecord activity = getActivityBuilderOnSameTask() + final ActivityRecord activity = setUpApp(display, new ActivityBuilder(mAtm) .setResizeMode(RESIZE_MODE_UNRESIZEABLE) .setMaxAspectRatio(2.1f) - .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED) - .build(); + .setScreenOrientation(SCREEN_ORIENTATION_UNSPECIFIED)); // The activity width is 2100 and the display's app bounds width is 2050, so the activity // cannot be aligned inside parentAppBounds and it will fill the parentBounds of the display assertEquals(activity.getBounds(), display.getBounds()); @@ -4883,7 +4819,7 @@ public class SizeCompatTests extends WindowTestsBase { @Test - @EnableCompatChanges({ActivityRecord.UNIVERSAL_RESIZABLE_BY_DEFAULT}) + @EnableCompatChanges({ActivityInfo.UNIVERSAL_RESIZABLE_BY_DEFAULT}) public void testUniversalResizeableByDefault() { mSetFlagsRule.enableFlags(Flags.FLAG_UNIVERSAL_RESIZABLE_BY_DEFAULT); mDisplayContent.setIgnoreOrientationRequest(false); @@ -5032,17 +4968,13 @@ public class SizeCompatTests extends WindowTestsBase { */ private ActivityRecord buildActivityRecord(boolean supportsSizeChanges, int resizeMode, @ScreenOrientation int screenOrientation) { - return getActivityBuilderOnSameTask() + return getActivityBuilderWithoutTask().setTask(mTask) .setResizeMode(resizeMode) .setSupportsSizeChanges(supportsSizeChanges) .setScreenOrientation(screenOrientation) .build(); } - private ActivityBuilder getActivityBuilderOnSameTask() { - return getActivityBuilderWithoutTask().setTask(mTask); - } - private ActivityBuilder getActivityBuilderWithoutTask() { return new ActivityBuilder(mAtm) .setComponent(ComponentName.createRelative(mContext, diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java index c8aa61c0316d..a7fe0cb0940c 100644 --- a/telephony/java/android/telephony/TelephonyManager.java +++ b/telephony/java/android/telephony/TelephonyManager.java @@ -2127,7 +2127,6 @@ public class TelephonyManager { * <p>On some devices, this settings activity may not exist. Callers should ensure that this * case is appropriately handled. */ - @FlaggedApi(Flags.FLAG_RESET_MOBILE_NETWORK_SETTINGS) @SdkConstant(SdkConstant.SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String ACTION_RESET_MOBILE_NETWORK_SETTINGS = "android.telephony.action.RESET_MOBILE_NETWORK_SETTINGS"; @@ -8779,13 +8778,14 @@ public class TelephonyManager { * Authentication error, no memory space available in EFMUK * * @throws UnsupportedOperationException If the device does not have - * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}. + * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} or doesn't support given + * authType. */ // TODO(b/73660190): This should probably require MODIFY_PHONE_STATE, not // READ_PRIVILEGED_PHONE_STATE. It certainly shouldn't reference the permission in Javadoc since // it's not public API. @RequiresFeature(PackageManager.FEATURE_TELEPHONY_SUBSCRIPTION) - public String getIccAuthentication(int appType,@AuthType int authType, String data) { + public String getIccAuthentication(int appType, @AuthType int authType, String data) { return getIccAuthentication(getSubId(), appType, authType, data); } @@ -8810,10 +8810,14 @@ public class TelephonyManager { * Key freshness failure * Authentication error, no memory space available * Authentication error, no memory space available in EFMUK + * @throws UnsupportedOperationException If the device does not have + * {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION} or doesn't support given + * authType. * @hide */ @UnsupportedAppUsage - public String getIccAuthentication(int subId, int appType,@AuthType int authType, String data) { + public String getIccAuthentication(int subId, int appType, @AuthType int authType, + String data) { try { IPhoneSubInfo info = getSubscriberInfoService(); if (info == null) diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml index f2e34257ef01..9ce8e807f612 100644 --- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml +++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml @@ -352,7 +352,8 @@ android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenActivity" android:theme="@style/CutoutShortEdges" android:label="SplitScreenPrimaryActivity" - android:exported="true"> + android:exported="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> @@ -363,7 +364,8 @@ android:taskAffinity="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" android:theme="@style/CutoutShortEdges" android:label="SplitScreenSecondaryActivity" - android:exported="true"> + android:exported="true" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> diff --git a/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt index 90dff47ab706..a1e165551b5b 100644 --- a/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt +++ b/tests/Input/src/android/hardware/input/InputDeviceBatteryListenerTest.kt @@ -24,17 +24,16 @@ import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any +import com.android.test.input.MockInputManagerRule import java.util.concurrent.Executor import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertTrue import kotlin.test.fail -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.Mockito import org.mockito.Mockito.anyInt import org.mockito.Mockito.doAnswer @@ -61,9 +60,8 @@ class InputDeviceBatteryListenerTest { private lateinit var context: Context private lateinit var inputManager: InputManager - @Mock - private lateinit var iInputManagerMock: IInputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession + @get:Rule + val inputManagerRule = MockInputManagerRule() @Before fun setUp() { @@ -72,7 +70,6 @@ class InputDeviceBatteryListenerTest { executor = HandlerExecutor(Handler(testLooper.looper)) registeredListener = null monitoredDevices.clear() - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -92,7 +89,7 @@ class InputDeviceBatteryListenerTest { monitoredDevices.add(deviceId) registeredListener = listener null - }.`when`(iInputManagerMock).registerBatteryListener(anyInt(), any()) + }.`when`(inputManagerRule.mock).registerBatteryListener(anyInt(), any()) // Handle battery listener being unregistered. doAnswer { @@ -108,14 +105,7 @@ class InputDeviceBatteryListenerTest { if (monitoredDevices.isEmpty()) { registeredListener = null } - }.`when`(iInputManagerMock).unregisterBatteryListener(anyInt(), any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterBatteryListener(anyInt(), any()) } private fun notifyBatteryStateChanged( diff --git a/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java index 080186e4a2c1..3fc9ce12e718 100644 --- a/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java +++ b/tests/Input/src/android/hardware/input/InputDeviceLightsManagerTest.java @@ -45,15 +45,14 @@ import android.view.InputDevice; import androidx.test.platform.app.InstrumentationRegistry; +import com.android.test.input.MockInputManagerRule; + 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.MockitoJUnitRunner; -import org.mockito.junit.MockitoRule; import java.util.ArrayList; import java.util.Arrays; @@ -73,23 +72,22 @@ public class InputDeviceLightsManagerTest { private static final int DEVICE_ID = 1000; private static final int PLAYER_ID = 3; - @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final MockInputManagerRule mInputManagerRule = new MockInputManagerRule(); private InputManager mInputManager; - @Mock private IInputManager mIInputManagerMock; private InputManagerGlobal.TestSession mInputManagerGlobalSession; @Before public void setUp() throws Exception { final Context context = spy( new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())); - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); + when(mInputManagerRule.getMock().getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); - when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn( + when(mInputManagerRule.getMock().getInputDevice(eq(DEVICE_ID))).thenReturn( createInputDevice(DEVICE_ID)); - mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock); mInputManager = new InputManager(context); when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager); @@ -102,7 +100,7 @@ public class InputDeviceLightsManagerTest { lightStatesById.put(lightIds[i], lightStates[i]); } return null; - }).when(mIInputManagerMock).setLightStates(eq(DEVICE_ID), + }).when(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID), any(int[].class), any(LightState[].class), any(IBinder.class)); doAnswer(invocation -> { @@ -111,7 +109,7 @@ public class InputDeviceLightsManagerTest { return lightStatesById.get(lightId); } return new LightState(0); - }).when(mIInputManagerMock).getLightState(eq(DEVICE_ID), anyInt()); + }).when(mInputManagerRule.getMock()).getLightState(eq(DEVICE_ID), anyInt()); } @After @@ -130,7 +128,7 @@ public class InputDeviceLightsManagerTest { private void mockLights(Light[] lights) throws Exception { // Mock the Lights returned form InputManagerService - when(mIInputManagerMock.getLights(eq(DEVICE_ID))).thenReturn( + when(mInputManagerRule.getMock().getLights(eq(DEVICE_ID))).thenReturn( new ArrayList(Arrays.asList(lights))); } @@ -151,7 +149,7 @@ public class InputDeviceLightsManagerTest { LightsManager lightsManager = device.getLightsManager(); List<Light> lights = lightsManager.getLights(); - verify(mIInputManagerMock).getLights(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getLights(eq(DEVICE_ID)); assertEquals(lights, Arrays.asList(mockedLights)); } @@ -185,9 +183,9 @@ public class InputDeviceLightsManagerTest { .build()); IBinder token = session.getToken(); - verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID), + verify(mInputManagerRule.getMock()).openLightSession(eq(DEVICE_ID), any(String.class), eq(token)); - verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}), + verify(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID), eq(new int[]{1, 2, 3}), eq(states), eq(token)); // Then all 3 should turn on. @@ -204,7 +202,7 @@ public class InputDeviceLightsManagerTest { // close session session.close(); - verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token)); + verify(mInputManagerRule.getMock()).closeLightSession(eq(DEVICE_ID), eq(token)); } @Test @@ -232,9 +230,9 @@ public class InputDeviceLightsManagerTest { .build()); IBinder token = session.getToken(); - verify(mIInputManagerMock).openLightSession(eq(DEVICE_ID), + verify(mInputManagerRule.getMock()).openLightSession(eq(DEVICE_ID), any(String.class), eq(token)); - verify(mIInputManagerMock).setLightStates(eq(DEVICE_ID), eq(new int[]{1}), + verify(mInputManagerRule.getMock()).setLightStates(eq(DEVICE_ID), eq(new int[]{1}), eq(states), eq(token)); // Verify the light state @@ -245,7 +243,7 @@ public class InputDeviceLightsManagerTest { // close session session.close(); - verify(mIInputManagerMock).closeLightSession(eq(DEVICE_ID), eq(token)); + verify(mInputManagerRule.getMock()).closeLightSession(eq(DEVICE_ID), eq(token)); } @Test diff --git a/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java index 0e3c200699d2..3057f5ddb540 100644 --- a/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java +++ b/tests/Input/src/android/hardware/input/InputDeviceSensorManagerTest.java @@ -41,16 +41,13 @@ import android.view.InputDevice; import androidx.test.platform.app.InstrumentationRegistry; import com.android.internal.annotations.GuardedBy; +import com.android.test.input.MockInputManagerRule; -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.MockitoJUnitRunner; -import org.mockito.junit.MockitoRule; import java.util.List; import java.util.concurrent.BlockingQueue; @@ -70,43 +67,34 @@ public class InputDeviceSensorManagerTest { private static final int DEVICE_ID = 1000; - @Rule public final MockitoRule mockito = MockitoJUnit.rule(); + @Rule + public final MockInputManagerRule mInputManagerRule = new MockInputManagerRule(); private InputManager mInputManager; private IInputSensorEventListener mIInputSensorEventListener; private final Object mLock = new Object(); - @Mock private IInputManager mIInputManagerMock; - private InputManagerGlobal.TestSession mInputManagerGlobalSession; - @Before public void setUp() throws Exception { final Context context = spy( new ContextWrapper(InstrumentationRegistry.getInstrumentation().getContext())); - mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock); mInputManager = new InputManager(context); when(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(mInputManager); - when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); + when(mInputManagerRule.getMock().getInputDeviceIds()).thenReturn(new int[]{DEVICE_ID}); - when(mIInputManagerMock.getInputDevice(eq(DEVICE_ID))).thenReturn( + when(mInputManagerRule.getMock().getInputDevice(eq(DEVICE_ID))).thenReturn( createInputDeviceWithSensor(DEVICE_ID)); - when(mIInputManagerMock.getSensorList(eq(DEVICE_ID))).thenReturn(new InputSensorInfo[] { - createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER), - createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)}); + when(mInputManagerRule.getMock().getSensorList(eq(DEVICE_ID))).thenReturn( + new InputSensorInfo[]{ + createInputSensorInfo(DEVICE_ID, Sensor.TYPE_ACCELEROMETER), + createInputSensorInfo(DEVICE_ID, Sensor.TYPE_GYROSCOPE)}); - when(mIInputManagerMock.enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt())) + when(mInputManagerRule.getMock().enableSensor(eq(DEVICE_ID), anyInt(), anyInt(), anyInt())) .thenReturn(true); - when(mIInputManagerMock.registerSensorListener(any())).thenReturn(true); - } - - @After - public void tearDown() { - if (mInputManagerGlobalSession != null) { - mInputManagerGlobalSession.close(); - } + when(mInputManagerRule.getMock().registerSensorListener(any())).thenReturn(true); } private class InputTestSensorEventListener implements SensorEventListener { @@ -175,13 +163,13 @@ public class InputDeviceSensorManagerTest { SensorManager sensorManager = device.getSensorManager(); List<Sensor> accelList = sensorManager.getSensorList(Sensor.TYPE_ACCELEROMETER); - verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID)); assertEquals(1, accelList.size()); assertEquals(DEVICE_ID, accelList.get(0).getId()); assertEquals(Sensor.TYPE_ACCELEROMETER, accelList.get(0).getType()); List<Sensor> gyroList = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE); - verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID)); assertEquals(1, gyroList.size()); assertEquals(DEVICE_ID, gyroList.get(0).getId()); assertEquals(Sensor.TYPE_GYROSCOPE, gyroList.get(0).getType()); @@ -197,11 +185,11 @@ public class InputDeviceSensorManagerTest { List<Sensor> gameRotationList = sensorManager.getSensorList( Sensor.TYPE_GAME_ROTATION_VECTOR); - verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID)); assertEquals(0, gameRotationList.size()); List<Sensor> gravityList = sensorManager.getSensorList(Sensor.TYPE_GRAVITY); - verify(mIInputManagerMock).getSensorList(eq(DEVICE_ID)); + verify(mInputManagerRule.getMock()).getSensorList(eq(DEVICE_ID)); assertEquals(0, gravityList.size()); } @@ -218,13 +206,13 @@ public class InputDeviceSensorManagerTest { mIInputSensorEventListener = invocation.getArgument(0); assertNotNull(mIInputSensorEventListener); return true; - }).when(mIInputManagerMock).registerSensorListener(any()); + }).when(mInputManagerRule.getMock()).registerSensorListener(any()); InputTestSensorEventListener listener = new InputTestSensorEventListener(); assertTrue(sensorManager.registerListener(listener, sensor, SensorManager.SENSOR_DELAY_NORMAL)); - verify(mIInputManagerMock).registerSensorListener(any()); - verify(mIInputManagerMock).enableSensor(eq(DEVICE_ID), eq(sensor.getType()), + verify(mInputManagerRule.getMock()).registerSensorListener(any()); + verify(mInputManagerRule.getMock()).enableSensor(eq(DEVICE_ID), eq(sensor.getType()), anyInt(), anyInt()); float[] values = new float[] {0.12f, 9.8f, 0.2f}; @@ -240,7 +228,7 @@ public class InputDeviceSensorManagerTest { } sensorManager.unregisterListener(listener); - verify(mIInputManagerMock).disableSensor(eq(DEVICE_ID), eq(sensor.getType())); + verify(mInputManagerRule.getMock()).disableSensor(eq(DEVICE_ID), eq(sensor.getType())); } } diff --git a/tests/Input/src/android/hardware/input/InputManagerTest.kt b/tests/Input/src/android/hardware/input/InputManagerTest.kt index 152dde94f006..4c6bb849155c 100644 --- a/tests/Input/src/android/hardware/input/InputManagerTest.kt +++ b/tests/Input/src/android/hardware/input/InputManagerTest.kt @@ -23,18 +23,16 @@ import android.view.Display import android.view.DisplayInfo import android.view.InputDevice import androidx.test.core.app.ApplicationProvider -import org.junit.After -import org.junit.Assert.assertNotNull +import com.android.test.input.MockInputManagerRule import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.eq import org.mockito.Mockito.`when` -import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnitRunner /** @@ -54,35 +52,23 @@ class InputManagerTest { } @get:Rule - val rule = MockitoJUnit.rule()!! + val inputManagerRule = MockInputManagerRule() private lateinit var devicesChangedListener: IInputDevicesChangedListener private val deviceGenerationMap = mutableMapOf<Int /*deviceId*/, Int /*generation*/>() private lateinit var context: Context private lateinit var inputManager: InputManager - @Mock - private lateinit var iInputManager: IInputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) inputManager = InputManager(context) `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager) - `when`(iInputManager.inputDeviceIds).then { + `when`(inputManagerRule.mock.inputDeviceIds).then { deviceGenerationMap.keys.toIntArray() } } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - private fun notifyDeviceChanged( deviceId: Int, associatedDisplayId: Int, @@ -92,7 +78,7 @@ class InputManagerTest { ?: throw IllegalArgumentException("Device $deviceId was never added!") deviceGenerationMap[deviceId] = generation - `when`(iInputManager.getInputDevice(deviceId)) + `when`(inputManagerRule.mock.getInputDevice(deviceId)) .thenReturn(createInputDevice(deviceId, associatedDisplayId, usiVersion, generation)) val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) } if (::devicesChangedListener.isInitialized) { @@ -125,7 +111,7 @@ class InputManagerTest { fun testUsiVersionFallBackToDisplayConfig() { addInputDevice(DEVICE_ID, Display.DEFAULT_DISPLAY, null) - `when`(iInputManager.getHostUsiVersionFromDisplayConfig(eq(42))) + `when`(inputManagerRule.mock.getHostUsiVersionFromDisplayConfig(eq(42))) .thenReturn(HostUsiVersion(9, 8)) val usiVersion = inputManager.getHostUsiVersion(createDisplay(42)) assertEquals(HostUsiVersion(9, 8), usiVersion) diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt index 072341dcefae..e99c81493394 100644 --- a/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyGestureEventHandlerTest.kt @@ -18,20 +18,17 @@ package android.hardware.input import android.content.Context import android.content.ContextWrapper -import android.os.Handler import android.os.IBinder -import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any -import org.junit.After +import com.android.test.input.MockInputManagerRule import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` @@ -69,20 +66,16 @@ class KeyGestureEventHandlerTest { @get:Rule val rule = SetFlagsRule() + @get:Rule + val inputManagerRule = MockInputManagerRule() - private val testLooper = TestLooper() private var registeredListener: IKeyGestureHandler? = null private lateinit var context: Context private lateinit var inputManager: InputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - - @Mock - private lateinit var iInputManagerMock: IInputManager @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -97,7 +90,7 @@ class KeyGestureEventHandlerTest { } registeredListener = listener null - }.`when`(iInputManagerMock).registerKeyGestureHandler(any()) + }.`when`(inputManagerRule.mock).registerKeyGestureHandler(any()) // Handle key gesture handler being unregistered. doAnswer { @@ -108,14 +101,7 @@ class KeyGestureEventHandlerTest { } registeredListener = null null - }.`when`(iInputManagerMock).unregisterKeyGestureHandler(any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterKeyGestureHandler(any()) } private fun handleKeyGestureEvent(event: KeyGestureEvent) { diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt index ca9de6000a5a..cf0bfcc4f6df 100644 --- a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt @@ -26,20 +26,19 @@ import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any -import org.junit.After +import com.android.test.input.MockInputManagerRule +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.fail import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.fail /** * Tests for [InputManager.KeyGestureEventListener]. @@ -63,21 +62,18 @@ class KeyGestureEventListenerTest { @get:Rule val rule = SetFlagsRule() + @get:Rule + val inputManagerRule = MockInputManagerRule() private val testLooper = TestLooper() private val executor = HandlerExecutor(Handler(testLooper.looper)) private var registeredListener: IKeyGestureEventListener? = null private lateinit var context: Context private lateinit var inputManager: InputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - - @Mock - private lateinit var iInputManagerMock: IInputManager @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -92,7 +88,7 @@ class KeyGestureEventListenerTest { } registeredListener = listener null - }.`when`(iInputManagerMock).registerKeyGestureEventListener(any()) + }.`when`(inputManagerRule.mock).registerKeyGestureEventListener(any()) // Handle key gesture event listener being unregistered. doAnswer { @@ -103,14 +99,7 @@ class KeyGestureEventListenerTest { } registeredListener = null null - }.`when`(iInputManagerMock).unregisterKeyGestureEventListener(any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterKeyGestureEventListener(any()) } private fun notifyKeyGestureEvent(event: KeyGestureEvent) { diff --git a/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt index 23135b2550b0..d25dee1d402c 100644 --- a/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt +++ b/tests/Input/src/android/hardware/input/KeyboardBacklightListenerTest.kt @@ -24,22 +24,20 @@ import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any -import org.junit.After +import com.android.test.input.MockInputManagerRule +import java.util.concurrent.Executor +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.fail import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` -import org.mockito.junit.MockitoJUnit import org.mockito.junit.MockitoJUnitRunner -import java.util.concurrent.Executor -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.fail /** * Tests for [InputManager.KeyboardBacklightListener]. @@ -50,23 +48,19 @@ import kotlin.test.fail @Presubmit @RunWith(MockitoJUnitRunner::class) class KeyboardBacklightListenerTest { + @get:Rule - val rule = MockitoJUnit.rule()!! + val inputManagerRule = MockInputManagerRule() private lateinit var testLooper: TestLooper private var registeredListener: IKeyboardBacklightListener? = null private lateinit var executor: Executor private lateinit var context: Context private lateinit var inputManager: InputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - - @Mock - private lateinit var iInputManagerMock: IInputManager @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) testLooper = TestLooper() executor = HandlerExecutor(Handler(testLooper.looper)) registeredListener = null @@ -84,7 +78,7 @@ class KeyboardBacklightListenerTest { } registeredListener = listener null - }.`when`(iInputManagerMock).registerKeyboardBacklightListener(any()) + }.`when`(inputManagerRule.mock).registerKeyboardBacklightListener(any()) // Handle keyboard backlight listener being unregistered. doAnswer { @@ -95,14 +89,7 @@ class KeyboardBacklightListenerTest { } registeredListener = null null - }.`when`(iInputManagerMock).unregisterKeyboardBacklightListener(any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterKeyboardBacklightListener(any()) } private fun notifyKeyboardBacklightChanged( diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt index bcd56ad0c669..1c2a0538e552 100644 --- a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt +++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt @@ -27,21 +27,20 @@ import android.platform.test.flag.junit.SetFlagsRule import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.android.server.testutils.any -import org.junit.After +import com.android.test.input.MockInputManagerRule +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith -import org.mockito.Mock import org.mockito.Mockito import org.mockito.Mockito.doAnswer import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnitRunner -import kotlin.test.assertEquals -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.test.fail /** * Tests for [InputManager.StickyModifierStateListener]. @@ -59,21 +58,18 @@ class StickyModifierStateListenerTest { @get:Rule val rule = SetFlagsRule() + @get:Rule + val inputManagerRule = MockInputManagerRule() private val testLooper = TestLooper() private val executor = HandlerExecutor(Handler(testLooper.looper)) private var registeredListener: IStickyModifierStateListener? = null private lateinit var context: Context private lateinit var inputManager: InputManager - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession - - @Mock - private lateinit var iInputManagerMock: IInputManager @Before fun setUp() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock) inputManager = InputManager(context) `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) @@ -88,7 +84,7 @@ class StickyModifierStateListenerTest { } registeredListener = listener null - }.`when`(iInputManagerMock).registerStickyModifierStateListener(any()) + }.`when`(inputManagerRule.mock).registerStickyModifierStateListener(any()) // Handle sticky modifier state listener being unregistered. doAnswer { @@ -99,14 +95,7 @@ class StickyModifierStateListenerTest { } registeredListener = null null - }.`when`(iInputManagerMock).unregisterStickyModifierStateListener(any()) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + }.`when`(inputManagerRule.mock).unregisterStickyModifierStateListener(any()) } private fun notifyStickyModifierStateChanged(modifierState: Int, lockedModifierState: Int) { diff --git a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt index f2724e605553..044f11d6904c 100644 --- a/tests/Input/src/com/android/server/input/BatteryControllerTests.kt +++ b/tests/Input/src/com/android/server/input/BatteryControllerTests.kt @@ -27,7 +27,6 @@ import android.hardware.input.HostUsiVersion import android.hardware.input.IInputDeviceBatteryListener import android.hardware.input.IInputDeviceBatteryState import android.hardware.input.IInputDevicesChangedListener -import android.hardware.input.IInputManager import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.os.Binder @@ -42,13 +41,13 @@ import com.android.server.input.BatteryController.BluetoothBatteryManager.Blueto import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS import com.android.server.input.BatteryController.UEventBatteryListener import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS +import com.android.test.input.MockInputManagerRule import org.hamcrest.Description import org.hamcrest.Matcher import org.hamcrest.MatcherAssert.assertThat import org.hamcrest.Matchers import org.hamcrest.TypeSafeMatcher import org.hamcrest.core.IsEqual.equalTo -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue @@ -184,12 +183,12 @@ class BatteryControllerTests { @get:Rule val rule = MockitoJUnit.rule()!! + @get:Rule + val inputManagerRule = MockInputManagerRule() @Mock private lateinit var native: NativeInputManagerService @Mock - private lateinit var iInputManager: IInputManager - @Mock private lateinit var uEventManager: UEventManager @Mock private lateinit var bluetoothBatteryManager: BluetoothBatteryManager @@ -205,10 +204,9 @@ class BatteryControllerTests { fun setup() { context = TestableContext(ApplicationProvider.getApplicationContext()) testLooper = TestLooper() - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) val inputManager = InputManager(context) context.addMockSystemService(InputManager::class.java, inputManager) - `when`(iInputManager.inputDeviceIds).then { + `when`(inputManagerRule.mock.inputDeviceIds).then { deviceGenerationMap.keys.toIntArray() } addInputDevice(DEVICE_ID) @@ -218,18 +216,11 @@ class BatteryControllerTests { bluetoothBatteryManager) batteryController.systemRunning() val listenerCaptor = ArgumentCaptor.forClass(IInputDevicesChangedListener::class.java) - verify(iInputManager).registerInputDevicesChangedListener(listenerCaptor.capture()) + verify(inputManagerRule.mock).registerInputDevicesChangedListener(listenerCaptor.capture()) devicesChangedListener = listenerCaptor.value testLooper.dispatchAll() } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - private fun notifyDeviceChanged( deviceId: Int, hasBattery: Boolean = true, @@ -239,7 +230,7 @@ class BatteryControllerTests { ?: throw IllegalArgumentException("Device $deviceId was never added!") deviceGenerationMap[deviceId] = generation - `when`(iInputManager.getInputDevice(deviceId)) + `when`(inputManagerRule.mock.getInputDevice(deviceId)) .thenReturn(createInputDevice(deviceId, hasBattery, supportsUsi, generation)) val list = deviceGenerationMap.flatMap { listOf(it.key, it.value) } if (::devicesChangedListener.isInitialized) { @@ -657,9 +648,9 @@ class BatteryControllerTests { @Test fun testRegisterBluetoothListenerForMonitoredBluetoothDevices() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") - `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) .thenReturn("11:22:33:44:55:66") addInputDevice(BT_DEVICE_ID) testLooper.dispatchNext() @@ -686,7 +677,7 @@ class BatteryControllerTests { batteryController.unregisterBatteryListener(BT_DEVICE_ID, listener, PID) verify(bluetoothBatteryManager, never()).removeBatteryListener(any()) - `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) .thenReturn(null) notifyDeviceChanged(SECOND_BT_DEVICE_ID) testLooper.dispatchNext() @@ -695,7 +686,7 @@ class BatteryControllerTests { @Test fun testNotifiesBluetoothBatteryChanges() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) addInputDevice(BT_DEVICE_ID) @@ -716,7 +707,7 @@ class BatteryControllerTests { @Test fun testBluetoothBatteryIsPrioritized() { `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device") - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98) @@ -745,7 +736,7 @@ class BatteryControllerTests { @Test fun testFallBackToNativeBatteryStateWhenBluetoothStateInvalid() { `when`(native.getBatteryDevicePath(BT_DEVICE_ID)).thenReturn("/sys/dev/bt_device") - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) `when`(native.getBatteryCapacity(BT_DEVICE_ID)).thenReturn(98) @@ -776,9 +767,9 @@ class BatteryControllerTests { @Test fun testRegisterBluetoothMetadataListenerForMonitoredBluetoothDevices() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") - `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) .thenReturn("11:22:33:44:55:66") addInputDevice(BT_DEVICE_ID) testLooper.dispatchNext() @@ -811,7 +802,7 @@ class BatteryControllerTests { verify(bluetoothBatteryManager) .removeMetadataListener("AA:BB:CC:DD:EE:FF", metadataListener1.value) - `when`(iInputManager.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(SECOND_BT_DEVICE_ID)) .thenReturn(null) notifyDeviceChanged(SECOND_BT_DEVICE_ID) testLooper.dispatchNext() @@ -821,7 +812,7 @@ class BatteryControllerTests { @Test fun testNotifiesBluetoothMetadataBatteryChanges() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF", BluetoothDevice.METADATA_MAIN_BATTERY)) @@ -861,7 +852,7 @@ class BatteryControllerTests { @Test fun testBluetoothMetadataBatteryIsPrioritized() { - `when`(iInputManager.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) + `when`(inputManagerRule.mock.getInputDeviceBluetoothAddress(BT_DEVICE_ID)) .thenReturn("AA:BB:CC:DD:EE:FF") `when`(bluetoothBatteryManager.getBatteryLevel(eq("AA:BB:CC:DD:EE:FF"))).thenReturn(21) `when`(bluetoothBatteryManager.getMetadata("AA:BB:CC:DD:EE:FF", diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt index 351ec4635977..927958eb62cc 100644 --- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt +++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt @@ -93,14 +93,12 @@ class InputManagerServiceTests { ) } - @JvmField - @Rule + @get:Rule val extendedMockitoRule = ExtendedMockitoRule.Builder(this).mockStatic(LocalServices::class.java) .mockStatic(PermissionChecker::class.java).build()!! - @JvmField - @Rule + @get:Rule val setFlagsRule = SetFlagsRule() @get:Rule diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt index 4ae06a4f9812..7526737f60bf 100644 --- a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt @@ -46,6 +46,7 @@ import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule import junitparams.JUnitParamsRunner import junitparams.Parameters +import org.junit.After import org.junit.Assert.assertArrayEquals import org.junit.Assert.assertEquals import org.junit.Assert.assertTrue @@ -128,6 +129,13 @@ class KeyGestureControllerTests { currentPid = Process.myPid() } + @After + fun teardown() { + if (this::inputManagerGlobalSession.isInitialized) { + inputManagerGlobalSession.close() + } + } + private fun setupBehaviors() { Mockito.`when`( resources.getBoolean( diff --git a/tests/Input/src/com/android/server/input/KeyRemapperTests.kt b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt index f74fd723d540..4f4c97bef4c0 100644 --- a/tests/Input/src/com/android/server/input/KeyRemapperTests.kt +++ b/tests/Input/src/com/android/server/input/KeyRemapperTests.kt @@ -18,16 +18,18 @@ package com.android.server.input import android.content.Context import android.content.ContextWrapper -import android.hardware.input.IInputManager import android.hardware.input.InputManager -import android.hardware.input.InputManagerGlobal import android.os.test.TestLooper import android.platform.test.annotations.Presubmit import android.provider.Settings import android.view.InputDevice import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider -import org.junit.After +import com.android.test.input.MockInputManagerRule +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule @@ -35,10 +37,6 @@ import org.junit.Test import org.mockito.Mock import org.mockito.Mockito import org.mockito.junit.MockitoJUnit -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream private fun createKeyboard(deviceId: Int): InputDevice = InputDevice.Builder() @@ -73,15 +71,15 @@ class KeyRemapperTests { @get:Rule val rule = MockitoJUnit.rule()!! - @Mock - private lateinit var iInputManager: IInputManager + @get:Rule + val inputManagerRule = MockInputManagerRule() + @Mock private lateinit var native: NativeInputManagerService private lateinit var mKeyRemapper: KeyRemapper private lateinit var context: Context private lateinit var dataStore: PersistentDataStore private lateinit var testLooper: TestLooper - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession @Before fun setup() { @@ -104,25 +102,17 @@ class KeyRemapperTests { dataStore, testLooper.looper ) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) val inputManager = InputManager(context) Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) - Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) - } - - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } + Mockito.`when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) } @Test fun testKeyRemapping_whenRemappingEnabled() { ModifierRemappingFlag(true).use { val keyboard = createKeyboard(DEVICE_ID) - Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard) + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboard) for (i in REMAPPABLE_KEYS.indices) { val fromKeyCode = REMAPPABLE_KEYS[i] @@ -160,7 +150,7 @@ class KeyRemapperTests { fun testKeyRemapping_whenRemappingDisabled() { ModifierRemappingFlag(false).use { val keyboard = createKeyboard(DEVICE_ID) - Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboard) + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboard) mKeyRemapper.remapKey(REMAPPABLE_KEYS[0], REMAPPABLE_KEYS[1]) testLooper.dispatchAll() diff --git a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt index 59aa96c46336..58fb4e1ed103 100644 --- a/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardBacklightControllerTests.kt @@ -20,11 +20,9 @@ import android.animation.ValueAnimator import android.content.Context import android.content.ContextWrapper import android.graphics.Color -import android.hardware.input.IInputManager import android.hardware.input.IKeyboardBacklightListener import android.hardware.input.IKeyboardBacklightState import android.hardware.input.InputManager -import android.hardware.input.InputManagerGlobal import android.hardware.lights.Light import android.os.UEventObserver import android.os.test.TestLooper @@ -35,7 +33,11 @@ import androidx.test.core.app.ApplicationProvider import com.android.server.input.KeyboardBacklightController.DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL import com.android.server.input.KeyboardBacklightController.MAX_BRIGHTNESS_CHANGE_STEPS import com.android.server.input.KeyboardBacklightController.USER_INACTIVITY_THRESHOLD_MILLIS -import org.junit.After +import com.android.test.input.MockInputManagerRule +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertNotEquals @@ -52,10 +54,6 @@ import org.mockito.Mockito.eq import org.mockito.Mockito.spy import org.mockito.Mockito.`when` import org.mockito.junit.MockitoJUnit -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream private fun createKeyboard(deviceId: Int): InputDevice = InputDevice.Builder() @@ -100,10 +98,10 @@ class KeyboardBacklightControllerTests { @get:Rule val rule = MockitoJUnit.rule()!! + @get:Rule + val inputManagerRule = MockInputManagerRule() @Mock - private lateinit var iInputManager: IInputManager - @Mock private lateinit var native: NativeInputManagerService @Mock private lateinit var uEventManager: UEventManager @@ -111,7 +109,6 @@ class KeyboardBacklightControllerTests { private lateinit var context: Context private lateinit var dataStore: PersistentDataStore private lateinit var testLooper: TestLooper - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession private var lightColorMap: HashMap<Int, Int> = HashMap() private var lastBacklightState: KeyboardBacklightState? = null private var sysfsNodeChanges = 0 @@ -134,10 +131,9 @@ class KeyboardBacklightControllerTests { testLooper = TestLooper() keyboardBacklightController = KeyboardBacklightController(context, native, dataStore, testLooper.looper, FakeAnimatorFactory(), uEventManager) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) val inputManager = InputManager(context) `when`(context.getSystemService(eq(Context.INPUT_SERVICE))).thenReturn(inputManager) - `when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) + `when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) `when`(native.setLightColor(anyInt(), anyInt(), anyInt())).then { val args = it.arguments lightColorMap.put(args[1] as Int, args[2] as Int) @@ -152,13 +148,6 @@ class KeyboardBacklightControllerTests { } } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - @Test fun testKeyboardBacklightIncrementDecrement() { KeyboardBacklightFlags( @@ -168,8 +157,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, @@ -186,8 +176,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithoutBacklight = createKeyboard(DEVICE_ID) val keyboardInputLight = createLight(LIGHT_ID, Light.LIGHT_TYPE_INPUT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithoutBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithoutBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardInputLight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) incrementKeyboardBacklight(DEVICE_ID) @@ -205,8 +196,9 @@ class KeyboardBacklightControllerTests { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) val keyboardInputLight = createLight(SECOND_LIGHT_ID, Light.LIGHT_TYPE_INPUT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn( + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn( listOf( keyboardBacklight, keyboardInputLight @@ -230,8 +222,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) for (level in 1 until DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size) { dataStore.setKeyboardBacklightBrightness( @@ -263,7 +256,8 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) dataStore.setKeyboardBacklightBrightness( keyboardWithBacklight.descriptor, LIGHT_ID, @@ -278,7 +272,7 @@ class KeyboardBacklightControllerTests { lightColorMap.isEmpty() ) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceChanged(DEVICE_ID) keyboardBacklightController.notifyUserActivity() testLooper.dispatchNext() @@ -300,8 +294,9 @@ class KeyboardBacklightControllerTests { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) val maxLevel = DEFAULT_BRIGHTNESS_VALUE_FOR_LEVEL.size - 1 - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) // Register backlight listener @@ -352,8 +347,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) dataStore.setKeyboardBacklightBrightness( keyboardWithBacklight.descriptor, LIGHT_ID, @@ -388,8 +384,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) dataStore.setKeyboardBacklightBrightness( keyboardWithBacklight.descriptor, LIGHT_ID, @@ -482,8 +479,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) incrementKeyboardBacklight(DEVICE_ID) @@ -511,8 +509,9 @@ class KeyboardBacklightControllerTests { val suggestedLevels = intArrayOf(0, 22, 63, 135, 196, 255) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, suggestedLevels) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, @@ -531,8 +530,9 @@ class KeyboardBacklightControllerTests { val suggestedLevels = IntArray(MAX_BRIGHTNESS_CHANGE_STEPS + 1) { 10 * (it + 1) } val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, suggestedLevels) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) assertIncrementDecrementForLevels(keyboardWithBacklight, keyboardBacklight, @@ -551,8 +551,9 @@ class KeyboardBacklightControllerTests { val suggestedLevels = intArrayOf(22, 63, 135, 196) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, suggestedLevels) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) // Framework will add the lowest and maximum levels if not provided via config @@ -572,8 +573,10 @@ class KeyboardBacklightControllerTests { val suggestedLevels = intArrayOf(22, 63, 135, 400, 196, 1000) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT, suggestedLevels) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)) + .thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) // Framework will drop out of bound levels in the config @@ -591,8 +594,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) dataStore.setKeyboardBacklightBrightness( keyboardWithBacklight.descriptor, @@ -619,8 +623,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) incrementKeyboardBacklight(DEVICE_ID) @@ -642,8 +647,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) sendAmbientBacklightValue(1) assertEquals( @@ -671,8 +677,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) sendAmbientBacklightValue(254) assertEquals( @@ -701,8 +708,9 @@ class KeyboardBacklightControllerTests { ).use { val keyboardWithBacklight = createKeyboard(DEVICE_ID) val keyboardBacklight = createLight(LIGHT_ID, Light.LIGHT_TYPE_KEYBOARD_BACKLIGHT) - `when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardWithBacklight) - `when`(iInputManager.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) + `when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)) + .thenReturn(keyboardWithBacklight) + `when`(inputManagerRule.mock.getLights(DEVICE_ID)).thenReturn(listOf(keyboardBacklight)) keyboardBacklightController.onInputDeviceAdded(DEVICE_ID) incrementKeyboardBacklight(DEVICE_ID) assertEquals( diff --git a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt index c073c7aae678..ff8a9ba94353 100644 --- a/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardGlyphManagerTests.kt @@ -23,9 +23,7 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.content.pm.ServiceInfo -import android.hardware.input.IInputManager import android.hardware.input.InputManager -import android.hardware.input.InputManagerGlobal import android.hardware.input.KeyGlyphMap.KeyCombination import android.os.Bundle import android.os.test.TestLooper @@ -36,8 +34,8 @@ import android.view.InputDevice import android.view.KeyEvent import androidx.test.core.app.ApplicationProvider import com.android.hardware.input.Flags +import com.android.test.input.MockInputManagerRule import com.android.test.input.R -import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -65,30 +63,24 @@ class KeyboardGlyphManagerTests { const val RECEIVER_NAME = "DummyReceiver" } - @JvmField - @Rule(order = 0) + @get:Rule val setFlagsRule = SetFlagsRule() - - @JvmField - @Rule(order = 1) + @get:Rule val mockitoRule = MockitoJUnit.rule()!! + @get:Rule + val inputManagerRule = MockInputManagerRule() @Mock private lateinit var packageManager: PackageManager - @Mock - private lateinit var iInputManager: IInputManager - private lateinit var keyboardGlyphManager: KeyboardGlyphManager private lateinit var context: Context private lateinit var testLooper: TestLooper - private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession private lateinit var keyboardDevice: InputDevice @Before fun setup() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) testLooper = TestLooper() keyboardGlyphManager = KeyboardGlyphManager(context, testLooper.looper) @@ -98,21 +90,14 @@ class KeyboardGlyphManagerTests { testLooper.dispatchAll() } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - private fun setupInputDevices() { val inputManager = InputManager(context) Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) .thenReturn(inputManager) keyboardDevice = createKeyboard(DEVICE_ID, VENDOR_ID, PRODUCT_ID, 0, "", "") - Mockito.`when`(iInputManager.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) - Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) + Mockito.`when`(inputManagerRule.mock.inputDeviceIds).thenReturn(intArrayOf(DEVICE_ID)) + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) } private fun setupBroadcastReceiver() { diff --git a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt index 301c0e6a159f..d6654cceb458 100644 --- a/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt +++ b/tests/Input/src/com/android/server/input/KeyboardLayoutManagerTests.kt @@ -24,11 +24,10 @@ import android.content.pm.ApplicationInfo import android.content.pm.PackageManager import android.content.pm.ResolveInfo import android.content.pm.ServiceInfo -import android.hardware.input.KeyboardLayoutSelectionResult -import android.hardware.input.IInputManager import android.hardware.input.InputManager import android.hardware.input.InputManagerGlobal import android.hardware.input.KeyboardLayout +import android.hardware.input.KeyboardLayoutSelectionResult import android.icu.util.ULocale import android.os.Bundle import android.os.test.TestLooper @@ -42,8 +41,12 @@ import com.android.dx.mockito.inline.extended.ExtendedMockito import com.android.internal.os.KeyboardConfiguredProto import com.android.internal.util.FrameworkStatsLog import com.android.modules.utils.testing.ExtendedMockitoRule +import com.android.test.input.MockInputManagerRule import com.android.test.input.R -import org.junit.After +import java.io.FileNotFoundException +import java.io.FileOutputStream +import java.io.IOException +import java.io.InputStream import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Assert.assertTrue @@ -53,10 +56,6 @@ import org.junit.Test import org.mockito.ArgumentMatchers import org.mockito.Mock import org.mockito.Mockito -import java.io.FileNotFoundException -import java.io.FileOutputStream -import java.io.IOException -import java.io.InputStream fun createKeyboard( deviceId: Int, @@ -120,8 +119,8 @@ class KeyboardLayoutManagerTests { val extendedMockitoRule = ExtendedMockitoRule.Builder(this) .mockStatic(FrameworkStatsLog::class.java).build()!! - @Mock - private lateinit var iInputManager: IInputManager + @get:Rule + val inputManagerRule = MockInputManagerRule() @Mock private lateinit var native: NativeInputManagerService @@ -148,7 +147,6 @@ class KeyboardLayoutManagerTests { @Before fun setup() { context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext())) - inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManager) dataStore = PersistentDataStore(object : PersistentDataStore.Injector() { override fun openRead(): InputStream? { throw FileNotFoundException() @@ -171,13 +169,6 @@ class KeyboardLayoutManagerTests { setupIme() } - @After - fun tearDown() { - if (this::inputManagerGlobalSession.isInitialized) { - inputManagerGlobalSession.close() - } - } - private fun setupInputDevices() { val inputManager = InputManager(context) Mockito.`when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE))) @@ -191,19 +182,19 @@ class KeyboardLayoutManagerTests { DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "dvorak") englishQwertyKeyboardDevice = createKeyboard(ENGLISH_QWERTY_DEVICE_ID, DEFAULT_VENDOR_ID, DEFAULT_PRODUCT_ID, DEFAULT_DEVICE_BUS, "en", "qwerty") - Mockito.`when`(iInputManager.inputDeviceIds) + Mockito.`when`(inputManagerRule.mock.inputDeviceIds) .thenReturn(intArrayOf( DEVICE_ID, VENDOR_SPECIFIC_DEVICE_ID, ENGLISH_DVORAK_DEVICE_ID, ENGLISH_QWERTY_DEVICE_ID )) - Mockito.`when`(iInputManager.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) - Mockito.`when`(iInputManager.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID)) + Mockito.`when`(inputManagerRule.mock.getInputDevice(DEVICE_ID)).thenReturn(keyboardDevice) + Mockito.`when`(inputManagerRule.mock.getInputDevice(VENDOR_SPECIFIC_DEVICE_ID)) .thenReturn(vendorSpecificKeyboardDevice) - Mockito.`when`(iInputManager.getInputDevice(ENGLISH_DVORAK_DEVICE_ID)) + Mockito.`when`(inputManagerRule.mock.getInputDevice(ENGLISH_DVORAK_DEVICE_ID)) .thenReturn(englishDvorakKeyboardDevice) - Mockito.`when`(iInputManager.getInputDevice(ENGLISH_QWERTY_DEVICE_ID)) + Mockito.`when`(inputManagerRule.mock.getInputDevice(ENGLISH_QWERTY_DEVICE_ID)) .thenReturn(englishQwertyKeyboardDevice) } diff --git a/tests/Input/src/com/android/test/input/MockInputManagerRule.kt b/tests/Input/src/com/android/test/input/MockInputManagerRule.kt new file mode 100644 index 000000000000..cef985600c40 --- /dev/null +++ b/tests/Input/src/com/android/test/input/MockInputManagerRule.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2024 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.test.input + +import android.hardware.input.IInputManager +import android.hardware.input.InputManagerGlobal +import org.junit.rules.ExternalResource +import org.mockito.Mockito + +/** + * A test rule that temporarily replaces the [IInputManager] connection to the server with a mock + * to be used for testing. + */ +class MockInputManagerRule : ExternalResource() { + + private lateinit var session: InputManagerGlobal.TestSession + + val mock: IInputManager = Mockito.mock(IInputManager::class.java) + + override fun before() { + session = InputManagerGlobal.createTestSession(mock) + } + + override fun after() { + if (this::session.isInitialized) { + session.close() + } + } +} diff --git a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt index 9f4df90422eb..c61a25021949 100644 --- a/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt +++ b/tests/Input/src/com/android/test/input/UinputRecordingIntegrationTests.kt @@ -39,7 +39,6 @@ import com.android.cts.input.inputeventmatchers.withSource import junit.framework.Assert.fail import org.hamcrest.Matchers.allOf import org.junit.Before -import org.junit.Ignore import org.junit.Rule import org.junit.Test import org.junit.rules.TestName @@ -108,7 +107,6 @@ class UinputRecordingIntegrationTests { parser = InputJsonParser(instrumentation.context) } - @Ignore("b/366602644") @Test fun testEvemuRecording() { VirtualDisplayActivityScenario.AutoClose<CaptureEventActivity>( diff --git a/tests/testables/src/android/testing/TestWithLooperRule.java b/tests/testables/src/android/testing/TestWithLooperRule.java index 10df17f991d3..6a8e142e2314 100644 --- a/tests/testables/src/android/testing/TestWithLooperRule.java +++ b/tests/testables/src/android/testing/TestWithLooperRule.java @@ -100,6 +100,9 @@ public class TestWithLooperRule implements MethodRule { case "ExpectException": next = this.getNextStatement(next, "next"); break; + case "UiThreadStatement": + next = this.getNextStatement(next, "base"); + break; default: throw new Exception( String.format("Unexpected Statement received: [%s]", diff --git a/tools/aapt2/SdkConstants.cpp b/tools/aapt2/SdkConstants.cpp index 37b1687fd3f1..5983cf165839 100644 --- a/tools/aapt2/SdkConstants.cpp +++ b/tools/aapt2/SdkConstants.cpp @@ -28,8 +28,19 @@ using namespace std::literals; namespace aapt { static constexpr ApiVersion sDevelopmentSdkLevel = 10000; + +// clang-format off static constexpr StringPiece sDevelopmentSdkCodeNames[] = { - "Q"sv, "R"sv, "S"sv, "Sv2"sv, "Tiramisu"sv, "UpsideDownCake"sv, "VanillaIceCream"sv}; + "Q"sv, + "R"sv, + "S"sv, + "Sv2"sv, + "Tiramisu"sv, + "UpsideDownCake"sv, + "VanillaIceCream"sv, + "Baklava"sv, +}; +// clang-format on static constexpr auto sPrivacySandboxSuffix = "PrivacySandbox"sv; diff --git a/tools/hoststubgen/.gitignore b/tools/hoststubgen/.gitignore deleted file mode 100644 index 6453bdef8cee..000000000000 --- a/tools/hoststubgen/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -out/ -*-out/ -*.log diff --git a/tools/hoststubgen/OWNERS b/tools/hoststubgen/OWNERS deleted file mode 100644 index 3d8888d83cf4..000000000000 --- a/tools/hoststubgen/OWNERS +++ /dev/null @@ -1 +0,0 @@ -file:platform/frameworks/base:/ravenwood/OWNERS diff --git a/tools/hoststubgen/TEST_MAPPING b/tools/hoststubgen/TEST_MAPPING deleted file mode 100644 index 856e6eefba15..000000000000 --- a/tools/hoststubgen/TEST_MAPPING +++ /dev/null @@ -1,7 +0,0 @@ -{ - "imports": [ - { - "path": "frameworks/base/ravenwood" - } - ] -} diff --git a/tools/hoststubgen/hoststubgen/.gitignore b/tools/hoststubgen/hoststubgen/.gitignore deleted file mode 100644 index 0f384074ed7f..000000000000 --- a/tools/hoststubgen/hoststubgen/.gitignore +++ /dev/null @@ -1 +0,0 @@ -framework-all-stub-out
\ No newline at end of file diff --git a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt index cba521e639cb..196b5e7c02ab 100644 --- a/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt +++ b/tools/systemfeatures/src/com/android/systemfeatures/SystemFeaturesGenerator.kt @@ -22,8 +22,6 @@ import com.squareup.javapoet.JavaFile import com.squareup.javapoet.MethodSpec import com.squareup.javapoet.ParameterizedTypeName import com.squareup.javapoet.TypeSpec -import java.util.HashMap -import java.util.Map import javax.lang.model.element.Modifier /* @@ -52,7 +50,7 @@ import javax.lang.model.element.Modifier * public static boolean hasFeatureAutomotive(Context context); * public static boolean hasFeatureLeanback(Context context); * public static Boolean maybeHasFeature(String feature, int version); - * public static ArrayMap<String, FeatureInfo> getCompileTimeAvailableFeatures(); + * public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures(); * } * </pre> */ @@ -63,6 +61,7 @@ object SystemFeaturesGenerator { private val PACKAGEMANAGER_CLASS = ClassName.get("android.content.pm", "PackageManager") private val CONTEXT_CLASS = ClassName.get("android.content", "Context") private val FEATUREINFO_CLASS = ClassName.get("android.content.pm", "FeatureInfo") + private val ARRAYMAP_CLASS = ClassName.get("android.util", "ArrayMap") private val ASSUME_TRUE_CLASS = ClassName.get("com.android.aconfig.annotations", "AssumeTrueForR8") private val ASSUME_FALSE_CLASS = @@ -291,19 +290,19 @@ object SystemFeaturesGenerator { features: Collection<FeatureInfo>, ) { val methodBuilder = - MethodSpec.methodBuilder("getCompileTimeAvailableFeatures") + MethodSpec.methodBuilder("getReadOnlySystemEnabledFeatures") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .addAnnotation(ClassName.get("android.annotation", "NonNull")) .addJavadoc("Gets features marked as available at compile-time, keyed by name." + "\n\n@hide") .returns(ParameterizedTypeName.get( - ClassName.get(Map::class.java), + ARRAYMAP_CLASS, ClassName.get(String::class.java), FEATUREINFO_CLASS)) val availableFeatures = features.filter { it.readonly && it.version != null } - methodBuilder.addStatement("Map<String, FeatureInfo> features = new \$T<>(\$L)", - HashMap::class.java, availableFeatures.size) + methodBuilder.addStatement("\$T<String, FeatureInfo> features = new \$T<>(\$L)", + ARRAYMAP_CLASS, ARRAYMAP_CLASS, availableFeatures.size) if (!availableFeatures.isEmpty()) { methodBuilder.addStatement("FeatureInfo fi = new FeatureInfo()") } diff --git a/tools/systemfeatures/tests/golden/RoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoFeatures.java.gen index edbfc4237547..ee97b26159de 100644 --- a/tools/systemfeatures/tests/golden/RoFeatures.java.gen +++ b/tools/systemfeatures/tests/golden/RoFeatures.java.gen @@ -13,10 +13,9 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; +import android.util.ArrayMap; import com.android.aconfig.annotations.AssumeFalseForR8; import com.android.aconfig.annotations.AssumeTrueForR8; -import java.util.HashMap; -import java.util.Map; /** * @hide @@ -94,8 +93,8 @@ public final class RoFeatures { * @hide */ @NonNull - public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() { - Map<String, FeatureInfo> features = new HashMap<>(2); + public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() { + ArrayMap<String, FeatureInfo> features = new ArrayMap<>(2); FeatureInfo fi = new FeatureInfo(); fi.name = PackageManager.FEATURE_WATCH; fi.version = 1; diff --git a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen index bf7a00679fa6..40c7db7ff1df 100644 --- a/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen +++ b/tools/systemfeatures/tests/golden/RoNoFeatures.java.gen @@ -9,8 +9,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; -import java.util.HashMap; -import java.util.Map; +import android.util.ArrayMap; /** * @hide @@ -43,8 +42,8 @@ public final class RoNoFeatures { * @hide */ @NonNull - public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() { - Map<String, FeatureInfo> features = new HashMap<>(0); + public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() { + ArrayMap<String, FeatureInfo> features = new ArrayMap<>(0); return features; } } diff --git a/tools/systemfeatures/tests/golden/RwFeatures.java.gen b/tools/systemfeatures/tests/golden/RwFeatures.java.gen index b20b228f9814..7bf89614b92d 100644 --- a/tools/systemfeatures/tests/golden/RwFeatures.java.gen +++ b/tools/systemfeatures/tests/golden/RwFeatures.java.gen @@ -12,8 +12,7 @@ import android.annotation.Nullable; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; -import java.util.HashMap; -import java.util.Map; +import android.util.ArrayMap; /** * @hide @@ -73,8 +72,8 @@ public final class RwFeatures { * @hide */ @NonNull - public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() { - Map<String, FeatureInfo> features = new HashMap<>(0); + public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() { + ArrayMap<String, FeatureInfo> features = new ArrayMap<>(0); return features; } } diff --git a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen index d91f5b62d8d4..eb7ec63f1d7d 100644 --- a/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen +++ b/tools/systemfeatures/tests/golden/RwNoFeatures.java.gen @@ -7,8 +7,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.content.Context; import android.content.pm.FeatureInfo; -import java.util.HashMap; -import java.util.Map; +import android.util.ArrayMap; /** * @hide @@ -32,8 +31,8 @@ public final class RwNoFeatures { * @hide */ @NonNull - public static Map<String, FeatureInfo> getCompileTimeAvailableFeatures() { - Map<String, FeatureInfo> features = new HashMap<>(0); + public static ArrayMap<String, FeatureInfo> getReadOnlySystemEnabledFeatures() { + ArrayMap<String, FeatureInfo> features = new ArrayMap<>(0); return features; } } diff --git a/tools/systemfeatures/tests/src/ArrayMap.java b/tools/systemfeatures/tests/src/ArrayMap.java new file mode 100644 index 000000000000..a5ed9b088896 --- /dev/null +++ b/tools/systemfeatures/tests/src/ArrayMap.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2024 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.util; + +import java.util.HashMap; + +/** Stub for testing. */ +public final class ArrayMap<K, V> extends HashMap<K, V> { + public ArrayMap(int capacity) { + super(capacity); + } +} diff --git a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java index 39f8fc44fe23..ed3f5c94ba79 100644 --- a/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java +++ b/tools/systemfeatures/tests/src/SystemFeaturesGeneratorTest.java @@ -60,7 +60,7 @@ public class SystemFeaturesGeneratorTest { assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); assertThat(RwNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); assertThat(RwNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); - assertThat(RwNoFeatures.getCompileTimeAvailableFeatures()).isEmpty(); + assertThat(RwNoFeatures.getReadOnlySystemEnabledFeatures()).isEmpty(); } @Test @@ -72,7 +72,7 @@ public class SystemFeaturesGeneratorTest { assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); assertThat(RoNoFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); assertThat(RoNoFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); - assertThat(RoNoFeatures.getCompileTimeAvailableFeatures()).isEmpty(); + assertThat(RoNoFeatures.getReadOnlySystemEnabledFeatures()).isEmpty(); // Also ensure we fall back to the PackageManager for feature APIs without an accompanying // versioned feature definition. @@ -106,7 +106,7 @@ public class SystemFeaturesGeneratorTest { assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_VULKAN, 0)).isNull(); assertThat(RwFeatures.maybeHasFeature(PackageManager.FEATURE_AUTO, 0)).isNull(); assertThat(RwFeatures.maybeHasFeature("com.arbitrary.feature", 0)).isNull(); - assertThat(RwFeatures.getCompileTimeAvailableFeatures()).isEmpty(); + assertThat(RwFeatures.getReadOnlySystemEnabledFeatures()).isEmpty(); } @Test @@ -163,7 +163,7 @@ public class SystemFeaturesGeneratorTest { assertThat(RoFeatures.maybeHasFeature("com.arbitrary.feature", 100)).isNull(); assertThat(RoFeatures.maybeHasFeature("", 0)).isNull(); - Map<String, FeatureInfo> compiledFeatures = RoFeatures.getCompileTimeAvailableFeatures(); + Map<String, FeatureInfo> compiledFeatures = RoFeatures.getReadOnlySystemEnabledFeatures(); assertThat(compiledFeatures.keySet()) .containsExactly(PackageManager.FEATURE_WATCH, PackageManager.FEATURE_WIFI); assertThat(compiledFeatures.get(PackageManager.FEATURE_WATCH).version).isEqualTo(1); |