diff options
297 files changed, 7341 insertions, 2973 deletions
diff --git a/apex/jobscheduler/service/aconfig/Android.bp b/apex/jobscheduler/service/aconfig/Android.bp index 4db39dc1976b..859c67ad8910 100644 --- a/apex/jobscheduler/service/aconfig/Android.bp +++ b/apex/jobscheduler/service/aconfig/Android.bp @@ -2,6 +2,7 @@ aconfig_declarations { name: "service-deviceidle.flags-aconfig", package: "com.android.server.deviceidle", + container: "system", srcs: [ "device_idle.aconfig", ], @@ -17,6 +18,7 @@ java_aconfig_library { aconfig_declarations { name: "service-job.flags-aconfig", package: "com.android.server.job", + container: "system", srcs: [ "job.aconfig", ], @@ -32,6 +34,7 @@ java_aconfig_library { aconfig_declarations { name: "alarm_flags", package: "com.android.server.alarm", + container: "system", srcs: ["alarm.aconfig"], } diff --git a/apex/jobscheduler/service/aconfig/alarm.aconfig b/apex/jobscheduler/service/aconfig/alarm.aconfig index bb0f3cbd5257..d3068d7d37e8 100644 --- a/apex/jobscheduler/service/aconfig/alarm.aconfig +++ b/apex/jobscheduler/service/aconfig/alarm.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.alarm" +container: "system" flag { name: "use_frozen_state_to_drop_listener_alarms" diff --git a/apex/jobscheduler/service/aconfig/device_idle.aconfig b/apex/jobscheduler/service/aconfig/device_idle.aconfig index e4cb5ad81ba0..e8c99b12828f 100644 --- a/apex/jobscheduler/service/aconfig/device_idle.aconfig +++ b/apex/jobscheduler/service/aconfig/device_idle.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.deviceidle" +container: "system" flag { name: "disable_wakelocks_in_light_idle" diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig index 5e6d3775f6a2..75e2efd2ec99 100644 --- a/apex/jobscheduler/service/aconfig/job.aconfig +++ b/apex/jobscheduler/service/aconfig/job.aconfig @@ -1,4 +1,5 @@ package: "com.android.server.job" +container: "system" flag { name: "batch_active_bucket_jobs" diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java index 012ede274bc1..096238aeda7c 100644 --- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java +++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java @@ -1737,6 +1737,17 @@ class JobConcurrencyManager { continue; } + if (!nextPending.isReady()) { + // This could happen when the job count reached its quota, the constrains + // for the job has been updated but hasn't been removed from the pending + // queue yet. + if (DEBUG) { + Slog.w(TAG, "Pending+not ready job: " + nextPending); + } + pendingJobQueue.remove(nextPending); + continue; + } + if (DEBUG && isSimilarJobRunningLocked(nextPending)) { Slog.w(TAG, "Already running similar job to: " + nextPending); } 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 3c9648b20003..cfbfa5dce399 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 @@ -512,7 +512,7 @@ public final class QuotaController extends StateController { /** An app has reached its quota. The message should contain a {@link UserPackage} object. */ @VisibleForTesting - static final int MSG_REACHED_QUOTA = 0; + static final int MSG_REACHED_TIME_QUOTA = 0; /** Drop any old timing sessions. */ private static final int MSG_CLEAN_UP_SESSIONS = 1; /** Check if a package is now within its quota. */ @@ -524,7 +524,7 @@ public final class QuotaController extends StateController { * object. */ @VisibleForTesting - static final int MSG_REACHED_EJ_QUOTA = 4; + static final int MSG_REACHED_EJ_TIME_QUOTA = 4; /** * Process a new {@link UsageEvents.Event}. The event will be the message's object and the * userId will the first arg. @@ -533,6 +533,11 @@ public final class QuotaController extends StateController { /** A UID's free quota grace period has ended. */ @VisibleForTesting static final int MSG_END_GRACE_PERIOD = 6; + /** + * An app has reached its job count quota. The message should contain a {@link UserPackage} + * object. + */ + static final int MSG_REACHED_COUNT_QUOTA = 7; public QuotaController(@NonNull JobSchedulerService service, @NonNull BackgroundJobsController backgroundJobsController, @@ -874,17 +879,37 @@ public final class QuotaController extends StateController { } @VisibleForTesting + @GuardedBy("mLock") boolean isWithinQuotaLocked(@NonNull final JobStatus jobStatus) { final int standbyBucket = jobStatus.getEffectiveStandbyBucket(); // A job is within quota if one of the following is true: // 1. it was started while the app was in the TOP state // 2. the app is currently in the foreground // 3. the app overall is within its quota - return jobStatus.shouldTreatAsUserInitiatedJob() + if (jobStatus.shouldTreatAsUserInitiatedJob() || isTopStartedJobLocked(jobStatus) - || isUidInForeground(jobStatus.getSourceUid()) - || isWithinQuotaLocked( - jobStatus.getSourceUserId(), jobStatus.getSourcePackageName(), standbyBucket); + || isUidInForeground(jobStatus.getSourceUid())) { + return true; + } + + if (standbyBucket == NEVER_INDEX) return false; + + if (isQuotaFreeLocked(standbyBucket)) return true; + + final ExecutionStats stats = getExecutionStatsLocked(jobStatus.getSourceUserId(), + jobStatus.getSourcePackageName(), standbyBucket); + if (!(getRemainingExecutionTimeLocked(stats) > 0)) { + // Out of execution time quota. + return false; + } + + if (mService.isCurrentlyRunningLocked(jobStatus)) { + // if job is running, considered as in quota so it can keep running. + return true; + } + + // Check if the app is within job count quota. + return isUnderJobCountQuotaLocked(stats) && isUnderSessionCountQuotaLocked(stats); } @GuardedBy("mLock") @@ -909,12 +934,11 @@ public final class QuotaController extends StateController { ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); // TODO: use a higher minimum remaining time for jobs with MINIMUM priority return getRemainingExecutionTimeLocked(stats) > 0 - && isUnderJobCountQuotaLocked(stats, standbyBucket) - && isUnderSessionCountQuotaLocked(stats, standbyBucket); + && isUnderJobCountQuotaLocked(stats) + && isUnderSessionCountQuotaLocked(stats); } - private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats, - final int standbyBucket) { + private boolean isUnderJobCountQuotaLocked(@NonNull ExecutionStats stats) { final long now = sElapsedRealtimeClock.millis(); final boolean isUnderAllowedTimeQuota = (stats.jobRateLimitExpirationTimeElapsed <= now @@ -923,8 +947,7 @@ public final class QuotaController extends StateController { && stats.bgJobCountInWindow < stats.jobCountLimit; } - private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats, - final int standbyBucket) { + private boolean isUnderSessionCountQuotaLocked(@NonNull ExecutionStats stats) { final long now = sElapsedRealtimeClock.millis(); final boolean isUnderAllowedTimeQuota = (stats.sessionRateLimitExpirationTimeElapsed <= now || stats.sessionCountInRateLimitingWindow < mMaxSessionCountPerRateLimitingWindow); @@ -1449,6 +1472,7 @@ public final class QuotaController extends StateController { stats.jobCountInRateLimitingWindow = 0; } stats.jobCountInRateLimitingWindow += count; + stats.bgJobCountInWindow += count; } } @@ -1683,10 +1707,11 @@ public final class QuotaController extends StateController { changedJobs.add(js); } } else if (realStandbyBucket != EXEMPTED_INDEX && realStandbyBucket != ACTIVE_INDEX - && realStandbyBucket == js.getEffectiveStandbyBucket()) { + && realStandbyBucket == js.getEffectiveStandbyBucket() + && !mService.isCurrentlyRunningLocked(js)) { // An app in the ACTIVE bucket may be out of quota while the job could be in quota // for some reason. Therefore, avoid setting the real value here and check each job - // individually. + // individually. Running job need to determine its own quota status as well. if (setConstraintSatisfied(js, nowElapsed, realInQuota, isWithinEJQuota)) { changedJobs.add(js); } @@ -1805,9 +1830,8 @@ public final class QuotaController extends StateController { } ExecutionStats stats = getExecutionStatsLocked(userId, packageName, standbyBucket); - final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats, standbyBucket); - final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats, - standbyBucket); + final boolean isUnderJobCountQuota = isUnderJobCountQuotaLocked(stats); + final boolean isUnderTimingSessionCountQuota = isUnderSessionCountQuotaLocked(stats); final long remainingEJQuota = getRemainingEJExecutionTimeLocked(userId, packageName); final boolean inRegularQuota = @@ -2126,6 +2150,11 @@ public final class QuotaController extends StateController { mBgJobCount++; if (mRegularJobTimer) { incrementJobCountLocked(mPkg.userId, mPkg.packageName, 1); + final ExecutionStats stats = getExecutionStatsLocked(mPkg.userId, + mPkg.packageName, jobStatus.getEffectiveStandbyBucket(), false); + if (stats.bgJobCountInWindow >= stats.jobCountLimit) { + mHandler.obtainMessage(MSG_REACHED_COUNT_QUOTA, mPkg).sendToTarget(); + } } if (mRunningBgJobs.size() == 1) { // Started tracking the first job. @@ -2257,7 +2286,6 @@ public final class QuotaController extends StateController { // repeatedly plugged in and unplugged, or an app changes foreground state // very frequently, the job count for a package may be artificially high. mBgJobCount = mRunningBgJobs.size(); - if (mRegularJobTimer) { incrementJobCountLocked(mPkg.userId, mPkg.packageName, mBgJobCount); // Starting the timer means that all cached execution stats are now @@ -2284,7 +2312,8 @@ public final class QuotaController extends StateController { return; } Message msg = mHandler.obtainMessage( - mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg); + mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, + mPkg); final long timeRemainingMs = mRegularJobTimer ? getTimeUntilQuotaConsumedLocked(mPkg.userId, mPkg.packageName) : getTimeUntilEJQuotaConsumedLocked(mPkg.userId, mPkg.packageName); @@ -2301,7 +2330,7 @@ public final class QuotaController extends StateController { private void cancelCutoff() { mHandler.removeMessages( - mRegularJobTimer ? MSG_REACHED_QUOTA : MSG_REACHED_EJ_QUOTA, mPkg); + mRegularJobTimer ? MSG_REACHED_TIME_QUOTA : MSG_REACHED_EJ_TIME_QUOTA, mPkg); } public void dump(IndentingPrintWriter pw, Predicate<JobStatus> predicate) { @@ -2557,7 +2586,7 @@ public final class QuotaController extends StateController { break; default: if (DEBUG) { - Slog.d(TAG, "Dropping event " + event.getEventType()); + Slog.d(TAG, "Dropping usage event " + event.getEventType()); } break; } @@ -2666,7 +2695,7 @@ public final class QuotaController extends StateController { public void handleMessage(Message msg) { synchronized (mLock) { switch (msg.what) { - case MSG_REACHED_QUOTA: { + case MSG_REACHED_TIME_QUOTA: { UserPackage pkg = (UserPackage) msg.obj; if (DEBUG) { Slog.d(TAG, "Checking if " + pkg + " has reached its quota."); @@ -2685,7 +2714,7 @@ public final class QuotaController extends StateController { // This could potentially happen if an old session phases out while a // job is currently running. // Reschedule message - Message rescheduleMsg = obtainMessage(MSG_REACHED_QUOTA, pkg); + Message rescheduleMsg = obtainMessage(MSG_REACHED_TIME_QUOTA, pkg); timeRemainingMs = getTimeUntilQuotaConsumedLocked(pkg.userId, pkg.packageName); if (DEBUG) { @@ -2695,7 +2724,7 @@ public final class QuotaController extends StateController { } break; } - case MSG_REACHED_EJ_QUOTA: { + case MSG_REACHED_EJ_TIME_QUOTA: { UserPackage pkg = (UserPackage) msg.obj; if (DEBUG) { Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota."); @@ -2713,7 +2742,7 @@ public final class QuotaController extends StateController { // This could potentially happen if an old session phases out while a // job is currently running. // Reschedule message - Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_QUOTA, pkg); + Message rescheduleMsg = obtainMessage(MSG_REACHED_EJ_TIME_QUOTA, pkg); timeRemainingMs = getTimeUntilEJQuotaConsumedLocked( pkg.userId, pkg.packageName); if (DEBUG) { @@ -2723,6 +2752,18 @@ public final class QuotaController extends StateController { } break; } + case MSG_REACHED_COUNT_QUOTA: { + UserPackage pkg = (UserPackage) msg.obj; + if (DEBUG) { + Slog.d(TAG, pkg + " has reached its count quota."); + } + + mStateChangedListener.onControllerStateChanged( + maybeUpdateConstraintForPkgLocked( + sElapsedRealtimeClock.millis(), + pkg.userId, pkg.packageName)); + break; + } case MSG_CLEAN_UP_SESSIONS: if (DEBUG) { Slog.d(TAG, "Cleaning up timing sessions."); diff --git a/api/Android.bp b/api/Android.bp index 010a2a587057..3fa9c600741e 100644 --- a/api/Android.bp +++ b/api/Android.bp @@ -382,6 +382,18 @@ stubs_defaults { ], } +soong_config_module_type { + name: "non_updatable_exportable_droidstubs", + module_type: "droidstubs", + config_namespace: "ANDROID", + bool_variables: [ + "release_hidden_api_exportable_stubs", + ], + properties: [ + "dists", + ], +} + // We resolve dependencies on APIs in modules by depending on a prebuilt of the whole // platform (sdk_system_current_android). That prebuilt does not include module-lib APIs, // so use the prebuilt module-lib stubs for modules that export module-lib stubs that the diff --git a/api/StubLibraries.bp b/api/StubLibraries.bp index c1add03fa31a..1b1bc6b9afdb 100644 --- a/api/StubLibraries.bp +++ b/api/StubLibraries.bp @@ -27,7 +27,12 @@ // These modules provide source files for the stub libraries ///////////////////////////////////////////////////////////////////// -droidstubs { +soong_config_module_type_import { + from: "frameworks/base/api/Android.bp", + module_types: ["non_updatable_exportable_droidstubs"], +} + +non_updatable_exportable_droidstubs { name: "api-stubs-docs-non-updatable", defaults: [ "android-non-updatable-stubs-defaults", @@ -54,15 +59,35 @@ droidstubs { targets: ["sdk"], dir: "apistubs/android/public/api", dest: "android-non-updatable.txt", - tag: ".api.txt", }, { targets: ["sdk"], dir: "apistubs/android/public/api", dest: "android-non-updatable-removed.txt", - tag: ".removed-api.txt", }, ], + soong_config_variables: { + release_hidden_api_exportable_stubs: { + dists: [ + { + tag: ".exportable.api.txt", + }, + { + tag: ".exportable.removed-api.txt", + }, + ], + conditions_default: { + dists: [ + { + tag: ".api.txt", + }, + { + tag: ".removed-api.txt", + }, + ], + }, + }, + }, api_surface: "public", } @@ -86,7 +111,7 @@ module_libs = [ "\\)", ] -droidstubs { +non_updatable_exportable_droidstubs { name: "system-api-stubs-docs-non-updatable", defaults: [ "android-non-updatable-stubs-defaults", @@ -114,19 +139,39 @@ droidstubs { targets: ["sdk"], dir: "apistubs/android/system/api", dest: "android-non-updatable.txt", - tag: ".api.txt", }, { targets: ["sdk"], dir: "apistubs/android/system/api", dest: "android-non-updatable-removed.txt", - tag: ".removed-api.txt", }, ], + soong_config_variables: { + release_hidden_api_exportable_stubs: { + dists: [ + { + tag: ".exportable.api.txt", + }, + { + tag: ".exportable.removed-api.txt", + }, + ], + conditions_default: { + dists: [ + { + tag: ".api.txt", + }, + { + tag: ".removed-api.txt", + }, + ], + }, + }, + }, api_surface: "system", } -droidstubs { +non_updatable_exportable_droidstubs { name: "test-api-stubs-docs-non-updatable", defaults: [ "android-non-updatable-stubs-defaults", @@ -149,31 +194,61 @@ droidstubs { targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android.txt", - tag: ".api.txt", }, { targets: ["sdk"], dir: "apistubs/android/test/api", dest: "removed.txt", - tag: ".removed-api.txt", }, { targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android-non-updatable.txt", - tag: ".api.txt", }, { targets: ["sdk"], dir: "apistubs/android/test/api", dest: "android-non-updatable-removed.txt", - tag: ".removed-api.txt", }, ], + soong_config_variables: { + release_hidden_api_exportable_stubs: { + dists: [ + { + tag: ".exportable.api.txt", + }, + { + tag: ".exportable.removed-api.txt", + }, + { + tag: ".exportable.api.txt", + }, + { + tag: ".exportable.removed-api.txt", + }, + ], + conditions_default: { + dists: [ + { + tag: ".api.txt", + }, + { + tag: ".removed-api.txt", + }, + { + tag: ".api.txt", + }, + { + tag: ".removed-api.txt", + }, + ], + }, + }, + }, api_surface: "test", } -droidstubs { +non_updatable_exportable_droidstubs { name: "module-lib-api-stubs-docs-non-updatable", defaults: [ "android-non-updatable-stubs-defaults", @@ -201,15 +276,35 @@ droidstubs { targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "android-non-updatable.txt", - tag: ".api.txt", }, { targets: ["sdk"], dir: "apistubs/android/module-lib/api", dest: "android-non-updatable-removed.txt", - tag: ".removed-api.txt", }, ], + soong_config_variables: { + release_hidden_api_exportable_stubs: { + dists: [ + { + tag: ".exportable.api.txt", + }, + { + tag: ".exportable.removed-api.txt", + }, + ], + conditions_default: { + dists: [ + { + tag: ".api.txt", + }, + { + tag: ".removed-api.txt", + }, + ], + }, + }, + }, api_surface: "module-lib", } diff --git a/core/api/system-current.txt b/core/api/system-current.txt index b7c2ee920c79..b767c52ea9ba 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -319,7 +319,6 @@ package android { field public static final String RECEIVE_DATA_ACTIVITY_CHANGE = "android.permission.RECEIVE_DATA_ACTIVITY_CHANGE"; field public static final String RECEIVE_DEVICE_CUSTOMIZATION_READY = "android.permission.RECEIVE_DEVICE_CUSTOMIZATION_READY"; field public static final String RECEIVE_EMERGENCY_BROADCAST = "android.permission.RECEIVE_EMERGENCY_BROADCAST"; - field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = "android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"; field @FlaggedApi("android.permission.flags.voice_activation_permission_apis") public static final String RECEIVE_SANDBOX_TRIGGER_AUDIO = "android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO"; field @FlaggedApi("android.view.flags.sensitive_content_app_protection_api") public static final String RECEIVE_SENSITIVE_NOTIFICATIONS = "android.permission.RECEIVE_SENSITIVE_NOTIFICATIONS"; field public static final String RECEIVE_WIFI_CREDENTIAL_CHANGE = "android.permission.RECEIVE_WIFI_CREDENTIAL_CHANGE"; diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 8913d6d15dc0..e53bd395f7fe 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -6982,7 +6982,7 @@ public final class ActivityThread extends ClientTransactionHandler } } else { // No package, perhaps it was removed? - Slog.e(TAG, "Package [" + packages[i] + "] reported as REPLACED," + Slog.d(TAG, "Package [" + packages[i] + "] reported as REPLACED," + " but missing application info. Assuming REMOVED."); mPackages.remove(packages[i]); mResourcePackages.remove(packages[i]); diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 7ed10e784b1c..7ae514ac2491 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1502,12 +1502,10 @@ public class AppOpsManager { AppProtoEnums.APP_OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; /** - * Allows the privileged assistant app to receive the training data from the sandboxed hotword - * detection service. + * This op has been deprecated. * - * @hide */ - public static final int OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = + private static final int OP_DEPRECATED_3 = AppProtoEnums.APP_OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA; /** @@ -1735,7 +1733,6 @@ public class AppOpsManager { OPSTR_CAMERA_SANDBOXED, OPSTR_RECORD_AUDIO_SANDBOXED, OPSTR_RECEIVE_SANDBOX_TRIGGER_AUDIO, - OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, OPSTR_CREATE_ACCESSIBILITY_OVERLAY, OPSTR_MEDIA_ROUTING_CONTROL, OPSTR_ENABLE_MOBILE_DATA_BY_USER, @@ -2395,13 +2392,10 @@ public class AppOpsManager { "android:receive_sandbox_trigger_audio"; /** - * Allows the privileged assistant app to receive training data from the sandboxed hotword - * detection service. - * + * App op has been deprecated. * @hide */ - public static final String OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA = - "android:RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA"; + public static final String OPSTR_DEPRECATED_3 = "android:deprecated_3"; /** * Creation of an overlay using accessibility services @@ -2582,7 +2576,6 @@ public class AppOpsManager { OP_CAPTURE_CONSENTLESS_BUGREPORT_ON_USERDEBUG_BUILD, OP_USE_FULL_SCREEN_INTENT, OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, - OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, OP_MEDIA_ROUTING_CONTROL, OP_READ_SYSTEM_GRAMMATICAL_GENDER, OP_RUN_BACKUP_JOBS, @@ -3021,11 +3014,8 @@ public class AppOpsManager { "RECEIVE_SANDBOX_TRIGGER_AUDIO") .setPermission(Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO) .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(), - new AppOpInfo.Builder(OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, - OPSTR_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA, - "RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA") - .setPermission(Manifest.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA) - .setDefaultMode(AppOpsManager.MODE_DEFAULT).build(), + new AppOpInfo.Builder(OP_DEPRECATED_3, OPSTR_DEPRECATED_3, "DEPRECATED_3") + .setDefaultMode(AppOpsManager.MODE_IGNORED).build(), new AppOpInfo.Builder(OP_CREATE_ACCESSIBILITY_OVERLAY, OPSTR_CREATE_ACCESSIBILITY_OVERLAY, "CREATE_ACCESSIBILITY_OVERLAY") diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java index 42e82f6d77c0..ea6f45e8e201 100644 --- a/core/java/android/app/admin/DevicePolicyManager.java +++ b/core/java/android/app/admin/DevicePolicyManager.java @@ -10284,6 +10284,16 @@ public class DevicePolicyManager { * get the list of app restrictions set by each admin via * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin}. * + * <p>Starting from Android Version {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM}, + * the device policy management role holder can also set app restrictions on any applications + * in the calling user, as well as the parent user of an organization-owned managed profile via + * the {@link DevicePolicyManager} instance returned by + * {@link #getParentProfileInstance(ComponentName)}. App restrictions set by the device policy + * management role holder are not returned by + * {@link UserManager#getApplicationRestrictions(String)}. The target application should use + * {@link android.content.RestrictionsManager#getApplicationRestrictionsPerAdmin} to retrieve + * them, alongside any app restrictions the profile or device owner might have set. + * * <p>NOTE: The method performs disk I/O and shouldn't be called on the main thread * * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or @@ -10299,11 +10309,14 @@ public class DevicePolicyManager { @WorkerThread public void setApplicationRestrictions(@Nullable ComponentName admin, String packageName, Bundle settings) { - throwIfParentInstance("setApplicationRestrictions"); + if (!Flags.dmrhCanSetAppRestriction()) { + throwIfParentInstance("setApplicationRestrictions"); + } + if (mService != null) { try { mService.setApplicationRestrictions(admin, mContext.getPackageName(), packageName, - settings); + settings, mParentInstance); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -11704,11 +11717,14 @@ public class DevicePolicyManager { @WorkerThread public @NonNull Bundle getApplicationRestrictions( @Nullable ComponentName admin, String packageName) { - throwIfParentInstance("getApplicationRestrictions"); + if (!Flags.dmrhCanSetAppRestriction()) { + throwIfParentInstance("getApplicationRestrictions"); + } + if (mService != null) { try { return mService.getApplicationRestrictions(admin, mContext.getPackageName(), - packageName); + packageName, mParentInstance); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -13986,8 +14002,15 @@ public class DevicePolicyManager { public @NonNull DevicePolicyManager getParentProfileInstance(@NonNull ComponentName admin) { throwIfParentInstance("getParentProfileInstance"); try { - if (!mService.isManagedProfile(admin)) { - throw new SecurityException("The current user does not have a parent profile."); + if (Flags.dmrhCanSetAppRestriction()) { + UserManager um = mContext.getSystemService(UserManager.class); + if (!um.isManagedProfile()) { + throw new SecurityException("The current user does not have a parent profile."); + } + } else { + if (!mService.isManagedProfile(admin)) { + throw new SecurityException("The current user does not have a parent profile."); + } } return new DevicePolicyManager(mContext, mService, true); } catch (RemoteException e) { diff --git a/core/java/android/app/admin/IDevicePolicyManager.aidl b/core/java/android/app/admin/IDevicePolicyManager.aidl index d4589dc6d453..2002326d76bd 100644 --- a/core/java/android/app/admin/IDevicePolicyManager.aidl +++ b/core/java/android/app/admin/IDevicePolicyManager.aidl @@ -244,8 +244,8 @@ interface IDevicePolicyManager { void setDefaultSmsApplication(in ComponentName admin, String callerPackageName, String packageName, boolean parent); void setDefaultDialerApplication(String packageName); - void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings); - Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName); + void setApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in Bundle settings, in boolean parent); + Bundle getApplicationRestrictions(in ComponentName who, in String callerPackage, in String packageName, in boolean parent); boolean setApplicationRestrictionsManagingPackage(in ComponentName admin, in String packageName); String getApplicationRestrictionsManagingPackage(in ComponentName admin); boolean isCallerApplicationRestrictionsManagingPackage(in String callerPackage); diff --git a/core/java/android/app/admin/flags/flags.aconfig b/core/java/android/app/admin/flags/flags.aconfig index 6b2baa7ceb61..56fb4aa45fb3 100644 --- a/core/java/android/app/admin/flags/flags.aconfig +++ b/core/java/android/app/admin/flags/flags.aconfig @@ -204,6 +204,13 @@ flag { } flag { + name: "dmrh_can_set_app_restriction" + namespace: "enterprise" + description: "Allow DMRH to set application restrictions (both on the profile and the parent)" + bug: "328758346" +} + +flag { name: "allow_screen_brightness_control_on_cope" namespace: "enterprise" description: "Allow COPE admin to control screen brightness and timeout." diff --git a/core/java/android/hardware/biometrics/BiometricPrompt.java b/core/java/android/hardware/biometrics/BiometricPrompt.java index 90b7869e1c5d..a0e40f6390ee 100644 --- a/core/java/android/hardware/biometrics/BiometricPrompt.java +++ b/core/java/android/hardware/biometrics/BiometricPrompt.java @@ -52,6 +52,7 @@ import android.text.TextUtils; import android.util.Log; import com.android.internal.R; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.FrameworkStatsLog; import java.lang.annotation.Retention; @@ -70,7 +71,8 @@ import javax.crypto.Mac; public class BiometricPrompt implements BiometricAuthenticator, BiometricConstants { private static final String TAG = "BiometricPrompt"; - private static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30; + @VisibleForTesting + static final int MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER = 30; /** * Error/help message will show for this amount of time. @@ -223,8 +225,8 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan * * @param logoDescription The logo description text that will be shown on the prompt. * @return This builder. - * @throws IllegalStateException If logo description is null or exceeds certain character - * limit. + * @throws IllegalArgumentException If logo description is null or exceeds certain character + * limit. */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED) @@ -232,7 +234,7 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan public BiometricPrompt.Builder setLogoDescription(@NonNull String logoDescription) { if (logoDescription == null || logoDescription.length() > MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER) { - throw new IllegalStateException( + throw new IllegalArgumentException( "Logo description passed in can not be null or exceed " + MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + " character number."); } @@ -240,7 +242,6 @@ public class BiometricPrompt implements BiometricAuthenticator, BiometricConstan return this; } - /** * Required: Sets the title that will be shown on the prompt. * @param title The title to display. diff --git a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java index 853d86cf94dc..a9eca3f87fc3 100644 --- a/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java +++ b/core/java/android/hardware/biometrics/PromptContentViewWithMoreOptionsButton.java @@ -29,19 +29,22 @@ import android.hardware.biometrics.BiometricPrompt.ButtonInfo; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + import java.util.concurrent.Executor; /** - * Contains the information of the template of content view with a more options button for Biometric - * Prompt. + * Contains the information of the template of content view with a more options button for + * Biometric Prompt. + * <p> * This button should be used to provide more options for sign in or other purposes, such as when a * user needs to select between multiple app-specific accounts or profiles that are available for - * sign in. This is not common and apps should avoid using it if there is only one choice available - * or if the user has already selected the appropriate account to use before invoking - * BiometricPrompt because it will create additional steps that the user must navigate through. - * Clicking the more options button will dismiss the prompt, provide the app an opportunity to ask - * the user for the correct account, and finally allow the app to decide how to proceed once - * selected. + * sign in. + * <p> + * Apps should avoid using this when possible because it will create additional steps that the user + * must navigate through - clicking the more options button will dismiss the prompt, provide the app + * an opportunity to ask the user for the correct option, and finally allow the app to decide how to + * proceed once selected. * * <p> * Here's how you'd set a <code>PromptContentViewWithMoreOptionsButton</code> on a Biometric @@ -59,7 +62,8 @@ import java.util.concurrent.Executor; */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) public final class PromptContentViewWithMoreOptionsButton implements PromptContentViewParcelable { - private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225; + @VisibleForTesting + static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225; private final String mDescription; private DialogInterface.OnClickListener mListener; @@ -132,14 +136,16 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte } }; + /** + * A builder that collects arguments to be shown on the content view with more options button. + */ public static final class Builder { private String mDescription; private Executor mExecutor; private DialogInterface.OnClickListener mListener; /** - * Optional: Sets a description that will be shown on the content view. Note that there are - * limits on the number of characters allowed for description. + * Optional: Sets a description that will be shown on the content view. * * @param description The description to display. * @return This builder. @@ -149,7 +155,7 @@ public final class PromptContentViewWithMoreOptionsButton implements PromptConte @RequiresPermission(SET_BIOMETRIC_DIALOG_ADVANCED) public Builder setDescription(@NonNull String description) { if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) { - throw new IllegalStateException("The character number of description exceeds " + throw new IllegalArgumentException("The character number of description exceeds " + MAX_DESCRIPTION_CHARACTER_NUMBER); } mDescription = description; diff --git a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java index 02b2a50ade3c..d8b28673f8ae 100644 --- a/core/java/android/hardware/biometrics/PromptVerticalListContentView.java +++ b/core/java/android/hardware/biometrics/PromptVerticalListContentView.java @@ -24,6 +24,8 @@ import android.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; +import com.android.internal.annotations.VisibleForTesting; + import java.util.ArrayList; import java.util.List; @@ -47,9 +49,12 @@ import java.util.List; */ @FlaggedApi(FLAG_CUSTOM_BIOMETRIC_PROMPT) public final class PromptVerticalListContentView implements PromptContentViewParcelable { - private static final int MAX_ITEM_NUMBER = 20; - private static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640; - private static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225; + @VisibleForTesting + static final int MAX_ITEM_NUMBER = 20; + @VisibleForTesting + static final int MAX_EACH_ITEM_CHARACTER_NUMBER = 640; + @VisibleForTesting + static final int MAX_DESCRIPTION_CHARACTER_NUMBER = 225; private final List<PromptContentItemParcelable> mContentList; private final String mDescription; @@ -155,7 +160,7 @@ public final class PromptVerticalListContentView implements PromptContentViewPar @NonNull public Builder setDescription(@NonNull String description) { if (description.length() > MAX_DESCRIPTION_CHARACTER_NUMBER) { - throw new IllegalStateException("The character number of description exceeds " + throw new IllegalArgumentException("The character number of description exceeds " + MAX_DESCRIPTION_CHARACTER_NUMBER); } mDescription = description; @@ -195,12 +200,12 @@ public final class PromptVerticalListContentView implements PromptContentViewPar private void checkItemLimits(@NonNull PromptContentItem listItem) { if (doesListItemExceedsCharLimit(listItem)) { - throw new IllegalStateException( + throw new IllegalArgumentException( "The character number of list item exceeds " + MAX_EACH_ITEM_CHARACTER_NUMBER); } if (mContentList.size() > MAX_ITEM_NUMBER) { - throw new IllegalStateException( + throw new IllegalArgumentException( "The number of list items exceeds " + MAX_ITEM_NUMBER); } } diff --git a/core/java/android/hardware/camera2/CameraDevice.java b/core/java/android/hardware/camera2/CameraDevice.java index ec9b013c34cc..dca663d206d3 100644 --- a/core/java/android/hardware/camera2/CameraDevice.java +++ b/core/java/android/hardware/camera2/CameraDevice.java @@ -23,12 +23,14 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.RequiresPermission; import android.annotation.SystemApi; +import android.graphics.SurfaceTexture; import android.hardware.camera2.params.ExtensionSessionConfiguration; import android.hardware.camera2.params.InputConfiguration; import android.hardware.camera2.params.OutputConfiguration; import android.hardware.camera2.params.SessionConfiguration; import android.hardware.camera2.params.StreamConfigurationMap; import android.os.Handler; +import android.util.Size; import android.view.Surface; import com.android.internal.camera.flags.Flags; @@ -530,9 +532,10 @@ public abstract class CameraDevice implements AutoCloseable { * SurfaceTexture}: Set the size of the SurfaceTexture with {@link * android.graphics.SurfaceTexture#setDefaultBufferSize} to be one of the sizes returned by * {@link StreamConfigurationMap#getOutputSizes(Class) getOutputSizes(SurfaceTexture.class)} - * before creating a Surface from the SurfaceTexture with {@link Surface#Surface}. If the size - * is not set by the application, it will be set to be the smallest supported size less than - * 1080p, by the camera device.</li> + * before creating a Surface from the SurfaceTexture with + * {@link Surface#Surface(SurfaceTexture)}. If the size is not set by the application, + * it will be set to be the smallest supported size less than 1080p, by the camera + * device.</li> * * <li>For recording with {@link android.media.MediaCodec}: Call * {@link android.media.MediaCodec#createInputSurface} after configuring @@ -1405,10 +1408,16 @@ public abstract class CameraDevice implements AutoCloseable { * * <p><b>NOTE:</b> * For apps targeting {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} and above, - * this method will ensure session parameters set through calls to - * {@link SessionConfiguration#setSessionParameters} are also supported if the Camera Device - * supports it. For apps targeting {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} and - * below, session parameters will be ignored.</p> + * this method will automatically delegate to + * {@link CameraDeviceSetup#isSessionConfigurationSupported} whenever possible. This + * means that the output of this method will consider parameters set through + * {@link SessionConfiguration#setSessionParameters} as well. + * </p> + * + * <p>Session Parameters will be ignored for apps targeting <= + * {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE}, or if + * {@link CameraManager#isCameraDeviceSetupSupported} returns false for the camera id + * associated with this {@code CameraDevice}.</p> * * @return {@code true} if the given session configuration is supported by the camera device * {@code false} otherwise. @@ -1419,6 +1428,8 @@ public abstract class CameraDevice implements AutoCloseable { * encountered a fatal error * @throws IllegalStateException if the camera device has been closed * + * @see CameraManager#isCameraDeviceSetupSupported(String) + * @see CameraDeviceSetup#isSessionConfigurationSupported(SessionConfiguration) */ public boolean isSessionConfigurationSupported( @NonNull SessionConfiguration sessionConfig) throws CameraAccessException { @@ -1703,7 +1714,7 @@ public abstract class CameraDevice implements AutoCloseable { * SessionConfiguration} can then be created using the OutputConfiguration objects and * be used to query whether it's supported by the camera device. To create the * CameraCaptureSession, the application still needs to make sure all output surfaces - * are added via {@link OutputConfiguration#addSurfaces} with the exception of deferred + * are added via {@link OutputConfiguration#addSurface} with the exception of deferred * surfaces for {@link android.view.SurfaceView} and * {@link android.graphics.SurfaceTexture}.</li> * </ul> @@ -1751,7 +1762,7 @@ public abstract class CameraDevice implements AutoCloseable { * SessionConfiguration} can then be created using the OutputConfiguration objects and * be used for this function. To create the CameraCaptureSession, the application still * needs to make sure all output surfaces are added via {@link - * OutputConfiguration#addSurfaces} with the exception of deferred surfaces for {@link + * OutputConfiguration#addSurface} with the exception of deferred surfaces for {@link * android.view.SurfaceView} and {@link android.graphics.SurfaceTexture}.</p> * * @param sessionConfig The session configuration for which characteristics are fetched. diff --git a/core/java/android/hardware/devicestate/DeviceState.java b/core/java/android/hardware/devicestate/DeviceState.java index 64fc4c29db90..e583627c0960 100644 --- a/core/java/android/hardware/devicestate/DeviceState.java +++ b/core/java/android/hardware/devicestate/DeviceState.java @@ -399,16 +399,8 @@ public final class DeviceState { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeInt(mIdentifier); dest.writeString8(mName); - - dest.writeInt(mSystemProperties.size()); - for (int i = 0; i < mSystemProperties.size(); i++) { - dest.writeInt(mSystemProperties.valueAt(i)); - } - - dest.writeInt(mPhysicalProperties.size()); - for (int i = 0; i < mPhysicalProperties.size(); i++) { - dest.writeInt(mPhysicalProperties.valueAt(i)); - } + dest.writeArraySet(mSystemProperties); + dest.writeArraySet(mPhysicalProperties); } @NonNull @@ -417,16 +409,11 @@ public final class DeviceState { public DeviceState.Configuration createFromParcel(Parcel source) { int identifier = source.readInt(); String name = source.readString8(); - ArraySet<@DeviceStateProperties Integer> systemProperties = new ArraySet<>(); - int systemPropertySize = source.readInt(); - for (int i = 0; i < systemPropertySize; i++) { - systemProperties.add(source.readInt()); - } - ArraySet<@DeviceStateProperties Integer> physicalProperties = new ArraySet<>(); - int physicalPropertySize = source.readInt(); - for (int j = 0; j < physicalPropertySize; j++) { - physicalProperties.add(source.readInt()); - } + ArraySet<@SystemDeviceStateProperties Integer> systemProperties = + (ArraySet<Integer>) source.readArraySet(null /* classLoader */); + ArraySet<@PhysicalDeviceStateProperties Integer> physicalProperties = + (ArraySet<Integer>) source.readArraySet(null /* classLoader */); + return new DeviceState.Configuration(identifier, name, systemProperties, physicalProperties); } diff --git a/core/java/android/hardware/devicestate/OWNERS b/core/java/android/hardware/devicestate/OWNERS new file mode 100644 index 000000000000..d9b0e2e5ffa5 --- /dev/null +++ b/core/java/android/hardware/devicestate/OWNERS @@ -0,0 +1 @@ +include /services/core/java/com/android/server/devicestate/OWNERS diff --git a/core/java/android/permission/TEST_MAPPING b/core/java/android/permission/TEST_MAPPING index 69113ef8f946..a15d9bc1b485 100644 --- a/core/java/android/permission/TEST_MAPPING +++ b/core/java/android/permission/TEST_MAPPING @@ -11,5 +11,29 @@ } ] } + ], + "postsubmit": [ + { + "name": "CtsVirtualDevicesAudioTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest" + } + ] + }, + { + "name": "CtsVirtualDevicesAppLaunchTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest" + } + ] + } ] }
\ No newline at end of file diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 4e521d62684a..353828c105bb 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -17,6 +17,7 @@ package android.service.dreams; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.service.dreams.Flags.dreamHandlesConfirmKeys; import android.annotation.FlaggedApi; import android.annotation.IdRes; @@ -29,6 +30,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.TestApi; import android.app.Activity; import android.app.AlarmManager; +import android.app.KeyguardManager; import android.app.Service; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -280,6 +282,8 @@ public class DreamService extends Service implements Window.Callback { private IDreamOverlayCallback mOverlayCallback; + private Integer mTrackingConfirmKey = null; + public DreamService() { mDreamManager = IDreamManager.Stub.asInterface(ServiceManager.getService(DREAM_SERVICE)); @@ -296,7 +300,54 @@ public class DreamService extends Service implements Window.Callback { /** {@inheritDoc} */ @Override public boolean dispatchKeyEvent(KeyEvent event) { - // TODO: create more flexible version of mInteractive that allows use of KEYCODE_BACK + if (dreamHandlesConfirmKeys()) { + // In the case of an interactive dream that consumes the event, do not process further. + if (mInteractive && mWindow.superDispatchKeyEvent(event)) { + return true; + } + + // If the key is a confirm key and on up, either unlock (no auth) or show bouncer. + if (KeyEvent.isConfirmKey(event.getKeyCode())) { + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN -> { + if (mTrackingConfirmKey != null) { + return true; + } + + mTrackingConfirmKey = event.getKeyCode(); + } + case KeyEvent.ACTION_UP -> { + if (mTrackingConfirmKey != event.getKeyCode()) { + return true; + } + + mTrackingConfirmKey = null; + + final KeyguardManager keyguardManager = + getSystemService(KeyguardManager.class); + + // Simply wake up in the case the device is not locked. + if (!keyguardManager.isKeyguardLocked()) { + wakeUp(); + return true; + } + + keyguardManager.requestDismissKeyguard(getActivity(), + new KeyguardManager.KeyguardDismissCallback() { + @Override + public void onDismissError() { + Log.e(TAG, "Could not dismiss keyguard on confirm key"); + } + }); + } + } + + // All key events for matching key codes should be consumed to prevent other actions + // from triggering. + return true; + } + } + if (!mInteractive) { if (mDebug) Slog.v(mTag, "Waking up on keyEvent"); wakeUp(); diff --git a/core/java/android/service/dreams/flags.aconfig b/core/java/android/service/dreams/flags.aconfig index 2e16a036618d..2f45f34b8da2 100644 --- a/core/java/android/service/dreams/flags.aconfig +++ b/core/java/android/service/dreams/flags.aconfig @@ -8,3 +8,14 @@ flag { "relying on the dream's window" bug: "291990564" } + +flag { + name: "dream_handles_confirm_keys" + namespace: "dreams" + description: "This flag enables dreams processing confirm keys to show the bouncer or dismiss " + "the keyguard" + bug: "326975875" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 2402a0b16b16..b52003f437da 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -31,7 +31,6 @@ import static android.view.WindowInsets.Type.ime; import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE; -import android.animation.AnimationHandler; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.TypeEvaluator; @@ -69,7 +68,6 @@ import android.view.inputmethod.ImeTracker.InputMethodJankContext; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; -import com.android.internal.graphics.SfVsyncFrameCallbackProvider; import com.android.internal.inputmethod.ImeTracing; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.util.function.TriFunction; @@ -379,16 +377,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private final WindowInsetsAnimationControlListener mLoggingListener; private final InputMethodJankContext mInputMethodJankContext; - private final ThreadLocal<AnimationHandler> mSfAnimationHandlerThreadLocal = - new ThreadLocal<AnimationHandler>() { - @Override - protected AnimationHandler initialValue() { - AnimationHandler handler = new AnimationHandler(); - handler.setProvider(new SfVsyncFrameCallbackProvider()); - return handler; - } - }; - public InternalAnimationControlListener(boolean show, boolean hasAnimationCallbacks, @InsetsType int requestedTypes, @Behavior int behavior, boolean disable, int floatingImeBottomInset, WindowInsetsAnimationControlListener loggingListener, @@ -470,9 +458,6 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation ImeTracker.forJank().onFinishAnimation(getAnimationType()); } }); - if (!mHasAnimationCallbacks) { - mAnimator.setAnimationHandler(mSfAnimationHandlerThreadLocal.get()); - } mAnimator.start(); } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 6c6e8b247886..188ad8f7e47c 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -203,9 +203,7 @@ public class Surface implements Parcelable { /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(prefix = {"FRAME_RATE_COMPATIBILITY_"}, - value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE, - FRAME_RATE_COMPATIBILITY_EXACT, FRAME_RATE_COMPATIBILITY_NO_VOTE, - FRAME_RATE_COMPATIBILITY_MIN, FRAME_RATE_COMPATIBILITY_GTE}) + value = {FRAME_RATE_COMPATIBILITY_DEFAULT, FRAME_RATE_COMPATIBILITY_FIXED_SOURCE}) public @interface FrameRateCompatibility {} // From native_window.h. Keep these in sync. diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java index cfdf8fab05c2..1cd7d349a9af 100644 --- a/core/java/android/view/SurfaceControl.java +++ b/core/java/android/view/SurfaceControl.java @@ -1272,7 +1272,7 @@ public final class SurfaceControl implements Parcelable { * surface has no buffer or crop, the surface is boundless and only constrained * by the size of its parent bounds. * - * @param session The surface session, must not be null. + * @param session The surface session. * @param name The surface name, must not be null. * @param w The surface initial width. * @param h The surface initial height. diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index c8cb1a7c8773..db1b73f5352d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -110,6 +110,7 @@ import static android.view.flags.Flags.toolkitFrameRateVelocityMappingReadOnly; import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision; import static android.view.flags.Flags.toolkitSetFrameRateReadOnly; import static android.view.flags.Flags.toolkitFrameRateFunctionEnablingReadOnly; +import static android.view.flags.Flags.toolkitFrameRateViewEnablingReadOnly; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER; @@ -1153,6 +1154,7 @@ public final class ViewRootImpl implements ViewParent, private static boolean sToolkitFrameRateFunctionEnablingReadOnlyFlagValue; private static boolean sToolkitMetricsForFrameRateDecisionFlagValue; private static boolean sToolkitFrameRateTypingReadOnlyFlagValue; + private static final boolean sToolkitFrameRateViewEnablingReadOnlyFlagValue; private static boolean sToolkitFrameRateVelocityMappingReadOnlyFlagValue = toolkitFrameRateVelocityMappingReadOnly();; @@ -1162,6 +1164,8 @@ public final class ViewRootImpl implements ViewParent, sToolkitFrameRateTypingReadOnlyFlagValue = toolkitFrameRateTypingReadOnly(); sToolkitFrameRateFunctionEnablingReadOnlyFlagValue = toolkitFrameRateFunctionEnablingReadOnly(); + sToolkitFrameRateViewEnablingReadOnlyFlagValue = + toolkitFrameRateViewEnablingReadOnly(); } // The latest input event from the gesture that was used to resolve the pointer icon. @@ -2624,8 +2628,10 @@ public final class ViewRootImpl implements ViewParent, // no longer needed if the dVRR feature is disabled. if (shouldEnableDvrr()) { try { - mFrameRateTransaction.setFrameRateSelectionStrategy(sc, + if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) { + mFrameRateTransaction.setFrameRateSelectionStrategy(sc, sc.FRAME_RATE_SELECTION_STRATEGY_SELF).applyAsyncUnsafe(); + } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate selection strategy ", e); } @@ -12536,9 +12542,11 @@ public final class ViewRootImpl implements ViewParent, + category + ", reason " + reason + ", " + sourceView); } - mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, + if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) { + mFrameRateTransaction.setFrameRateCategory(mSurfaceControl, frameRateCategory, false).applyAsyncUnsafe(); - mLastPreferredFrameRateCategory = frameRateCategory; + mLastPreferredFrameRateCategory = frameRateCategory; + } } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate category", e); @@ -12595,9 +12603,11 @@ public final class ViewRootImpl implements ViewParent, + preferredFrameRate + " compatibility " + mFrameRateCompatibility); } - mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate, + if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) { + mFrameRateTransaction.setFrameRate(mSurfaceControl, preferredFrameRate, mFrameRateCompatibility).applyAsyncUnsafe(); - mLastPreferredFrameRate = preferredFrameRate; + mLastPreferredFrameRate = preferredFrameRate; + } } } catch (Exception e) { Log.e(mTag, "Unable to set frame rate", e); @@ -12824,7 +12834,7 @@ public final class ViewRootImpl implements ViewParent, private boolean shouldEnableDvrr() { // uncomment this when we are ready for enabling dVRR - if (sToolkitFrameRateFunctionEnablingReadOnlyFlagValue) { + if (sToolkitFrameRateViewEnablingReadOnlyFlagValue) { return sToolkitSetFrameRateReadOnlyFlagValue && isFrameRatePowerSavingsBalanced(); } return false; diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index 4402ac712d42..dd6b772ab871 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -9,6 +9,16 @@ flag { } flag { + name: "wait_for_transition_on_display_switch" + namespace: "windowing_frontend" + description: "Waits for Shell transition to start before unblocking the screen after display switch" + bug: "301420598" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "edge_to_edge_by_default" namespace: "windowing_frontend" description: "Make app go edge-to-edge by default when targeting SDK 35 or greater" diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 9481dc91bcc4..a0c405e31e79 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -53,7 +53,6 @@ import android.util.Slog; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.Flags; import android.widget.Toast; import com.android.internal.R; @@ -369,23 +368,17 @@ public class AccessibilityShortcutController { }) .setPositiveButton(R.string.accessibility_shortcut_off, (DialogInterface d, int which) -> { - if (Flags.updateAlwaysOnA11yService()) { - Set<String> targetServices = - ShortcutUtils.getShortcutTargetsFromSettings( - mContext, - HARDWARE, - userId); - - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", - userId); - ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - mContext, targetServices, userId); - } else { - Settings.Secure.putStringForUser(mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", - userId); - } + Set<String> targetServices = + ShortcutUtils.getShortcutTargetsFromSettings( + mContext, + HARDWARE, + userId); + + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ACCESSIBILITY_SHORTCUT_TARGET_SERVICE, "", + userId); + ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( + mContext, targetServices, userId); // If canceled, treat as if the dialog has never been shown Settings.Secure.putIntForUser(mContext.getContentResolver(), diff --git a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java index 7831afb8798e..209778808764 100644 --- a/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java +++ b/core/java/com/android/internal/accessibility/dialog/InvisibleToggleAccessibilityServiceTarget.java @@ -16,17 +16,11 @@ package com.android.internal.accessibility.dialog; -import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.HARDWARE; -import static com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType.SOFTWARE; -import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState; -import static com.android.internal.accessibility.util.ShortcutUtils.isComponentIdExistingInSettings; - import android.accessibilityservice.AccessibilityServiceInfo; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; import android.os.UserHandle; -import android.view.accessibility.Flags; import com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType; import com.android.internal.accessibility.common.ShortcutConstants.UserShortcutType; @@ -53,31 +47,9 @@ public class InvisibleToggleAccessibilityServiceTarget extends AccessibilityServ @Override public void onCheckedChanged(boolean isChecked) { + super.onCheckedChanged(isChecked); final ComponentName componentName = ComponentName.unflattenFromString(getId()); - - if (Flags.updateAlwaysOnA11yService()) { - super.onCheckedChanged(isChecked); - ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( - getContext(), Set.of(componentName.flattenToString()), UserHandle.myUserId()); - } else { - if (!isComponentIdExistingInOtherShortcut()) { - setAccessibilityServiceState(getContext(), componentName, isChecked); - } - - super.onCheckedChanged(isChecked); - } - } - - private boolean isComponentIdExistingInOtherShortcut() { - switch (getShortcutType()) { - case SOFTWARE: - return isComponentIdExistingInSettings(getContext(), UserShortcutType.HARDWARE, - getId()); - case HARDWARE: - return isComponentIdExistingInSettings(getContext(), UserShortcutType.SOFTWARE, - getId()); - default: - throw new IllegalStateException("Unexpected shortcut type"); - } + ShortcutUtils.updateInvisibleToggleAccessibilityServiceEnableState( + getContext(), Set.of(componentName.flattenToString()), UserHandle.myUserId()); } } diff --git a/core/jni/android_tracing_PerfettoDataSource.cpp b/core/jni/android_tracing_PerfettoDataSource.cpp index 1eff5ce8eaa3..25ff853ae7e4 100644 --- a/core/jni/android_tracing_PerfettoDataSource.cpp +++ b/core/jni/android_tracing_PerfettoDataSource.cpp @@ -213,7 +213,7 @@ void PerfettoDataSource::flushAll() { PerfettoDataSource::~PerfettoDataSource() { JNIEnv* env = AndroidRuntime::getJNIEnv(); - env->DeleteWeakGlobalRef(mJavaDataSource); + env->DeleteGlobalRef(mJavaDataSource); } jlong nativeCreate(JNIEnv* env, jclass clazz, jobject javaDataSource, jstring name) { diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index d4256ca316c2..f74329903690 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -7507,16 +7507,6 @@ <permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO" android:protectionLevel="signature|privileged|appop" /> - <!-- @SystemApi Required for the privileged assistant apps targeting - {@link android.os.Build.VERSION_CODES#VANILLA_ICE_CREAM} - that receive training data from a sandboxed {@link HotwordDetectionService} or - {@link VisualQueryDetectionService}. - <p>Protection level: internal|appop - @FlaggedApi("android.permission.flags.voice_activation_permission_apis") - @hide --> - <permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA" - android:protectionLevel="internal|appop" /> - <!-- @SystemApi Allows requesting the framework broadcast the {@link Intent#ACTION_DEVICE_CUSTOMIZATION_READY} intent. @hide --> diff --git a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java index 66f3bca72aeb..ca9154280a10 100644 --- a/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java +++ b/core/tests/coretests/src/android/hardware/biometrics/BiometricPromptTest.java @@ -16,6 +16,14 @@ package android.hardware.biometrics; +import static android.hardware.biometrics.BiometricPrompt.MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER; +import static android.hardware.biometrics.PromptContentViewWithMoreOptionsButton.MAX_DESCRIPTION_CHARACTER_NUMBER; +import static android.hardware.biometrics.PromptVerticalListContentView.MAX_EACH_ITEM_CHARACTER_NUMBER; +import static android.hardware.biometrics.PromptVerticalListContentView.MAX_ITEM_NUMBER; + +import static com.google.common.truth.Truth.assertThat; + +import static org.junit.Assert.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; @@ -40,6 +48,7 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoRule; +import java.util.Random; import java.util.concurrent.Executor; @@ -83,10 +92,11 @@ public class BiometricPromptTest { ArgumentCaptor.forClass(IBiometricServiceReceiver.class); BiometricPrompt.AuthenticationCallback callback = new BiometricPrompt.AuthenticationCallback() { - @Override - public void onAuthenticationError(int errorCode, CharSequence errString) { - super.onAuthenticationError(errorCode, errString); - }}; + @Override + public void onAuthenticationError(int errorCode, CharSequence errString) { + super.onAuthenticationError(errorCode, errString); + } + }; mBiometricPrompt.authenticate(mCancellationSignal, mExecutor, callback); mLooper.dispatchAll(); @@ -99,4 +109,112 @@ public class BiometricPromptTest { verify(mService).cancelAuthentication(any(), anyString(), anyLong()); } + + @Test + public void testLogoDescription_null() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> new BiometricPrompt.Builder(mContext).setLogoDescription(null) + ); + + assertThat(e).hasMessageThat().contains( + "Logo description passed in can not be null or exceed"); + } + + @Test + public void testLogoDescription_charLimit() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> new BiometricPrompt.Builder(mContext).setLogoDescription( + generateRandomString(MAX_LOGO_DESCRIPTION_CHARACTER_NUMBER + 1)) + ); + + assertThat(e).hasMessageThat().contains( + "Logo description passed in can not be null or exceed"); + } + + @Test + public void testMoreOptionsButton_descriptionCharLimit() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> new PromptContentViewWithMoreOptionsButton.Builder().setDescription( + generateRandomString(MAX_DESCRIPTION_CHARACTER_NUMBER + 1)) + ); + + assertThat(e).hasMessageThat().contains( + "The character number of description exceeds "); + } + + @Test + public void testMoreOptionsButton_ExecutorNull() { + PromptContentViewWithMoreOptionsButton.Builder builder = + new PromptContentViewWithMoreOptionsButton.Builder().setMoreOptionsButtonListener( + null, null); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + builder::build + ); + + assertThat(e).hasMessageThat().contains( + "The executor for the listener of more options button on prompt content must be " + + "set"); + } + + @Test + public void testMoreOptionsButton_ListenerNull() { + PromptContentViewWithMoreOptionsButton.Builder builder = + new PromptContentViewWithMoreOptionsButton.Builder().setMoreOptionsButtonListener( + mExecutor, null); + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + builder::build + ); + + assertThat(e).hasMessageThat().contains( + "The listener of more options button on prompt content must be set"); + } + + @Test + public void testVerticalList_descriptionCharLimit() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> new PromptVerticalListContentView.Builder().setDescription( + generateRandomString(MAX_DESCRIPTION_CHARACTER_NUMBER + 1)) + ); + + assertThat(e).hasMessageThat().contains( + "The character number of description exceeds "); + } + + @Test + public void testVerticalList_itemCharLimit() { + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> new PromptVerticalListContentView.Builder().addListItem( + new PromptContentItemBulletedText( + generateRandomString(MAX_EACH_ITEM_CHARACTER_NUMBER + 1))) + ); + + assertThat(e).hasMessageThat().contains( + "The character number of list item exceeds "); + } + + @Test + public void testVerticalList_itemNumLimit() { + PromptVerticalListContentView.Builder builder = new PromptVerticalListContentView.Builder(); + + for (int i = 0; i < MAX_ITEM_NUMBER; i++) { + builder.addListItem(new PromptContentItemBulletedText(generateRandomString(10))); + } + + IllegalArgumentException e = assertThrows(IllegalArgumentException.class, + () -> builder.addListItem( + new PromptContentItemBulletedText(generateRandomString(10))) + ); + + assertThat(e).hasMessageThat().contains( + "The number of list items exceeds "); + } + + private String generateRandomString(int charNum) { + final Random random = new Random(); + final StringBuilder longString = new StringBuilder(charNum); + for (int j = 0; j < charNum; j++) { + longString.append(random.nextInt(10)); + } + return longString.toString(); + } } diff --git a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java index 180521ba7b70..365f3485d65c 100644 --- a/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java +++ b/core/tests/coretests/src/com/android/internal/accessibility/AccessibilityShortcutControllerTest.java @@ -64,9 +64,6 @@ import android.os.Build; import android.os.Handler; import android.os.Message; import android.os.Vibrator; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; import android.provider.Settings; import android.speech.tts.TextToSpeech; import android.speech.tts.Voice; @@ -74,7 +71,6 @@ import android.test.mock.MockContentResolver; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityManager; -import android.view.accessibility.Flags; import android.view.accessibility.IAccessibilityManager; import android.widget.Toast; @@ -87,7 +83,6 @@ import com.android.internal.util.test.FakeSettingsProvider; import org.junit.AfterClass; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -103,9 +98,6 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) public class AccessibilityShortcutControllerTest { - @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); - private static final String SERVICE_NAME_STRING = "fake.package/fake.service.name"; private static final CharSequence PACKAGE_NAME_STRING = "Service name"; private static final String SERVICE_NAME_SUMMARY = "Summary"; @@ -440,7 +432,6 @@ public class AccessibilityShortcutControllerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE) public void turnOffVolumeShortcutForAlwaysOnA11yService_shouldTurnOffA11yService() throws Exception { configureApplicationTargetSdkVersion(Build.VERSION_CODES.R); @@ -452,7 +443,6 @@ public class AccessibilityShortcutControllerTest { } @Test - @RequiresFlagsEnabled(Flags.FLAG_UPDATE_ALWAYS_ON_A11Y_SERVICE) public void turnOffVolumeShortcutForAlwaysOnA11yService_hasOtherTypesShortcut_shouldNotTurnOffA11yService() throws Exception { configureApplicationTargetSdkVersion(Build.VERSION_CODES.R); diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp index b749a06bd516..5c978e21b9bd 100644 --- a/libs/WindowManager/Shell/Android.bp +++ b/libs/WindowManager/Shell/Android.bp @@ -210,7 +210,7 @@ android_library { "androidx.recyclerview_recyclerview", "kotlinx-coroutines-android", "kotlinx-coroutines-core", - "iconloader_base", + "//frameworks/libs/systemui:iconloader_base", "com_android_wm_shell_flags_lib", "com.android.window.flags.window-aconfig-java", "WindowManager-Shell-proto", diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml index fa6dd3914ddd..bf654d979856 100644 --- a/libs/WindowManager/Shell/res/values/strings.xml +++ b/libs/WindowManager/Shell/res/values/strings.xml @@ -282,6 +282,6 @@ <string name="expand_menu_text">Open Menu</string> <!-- Maximize menu maximize button string. --> <string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string> - <!-- Maximize menu maximize button string. --> + <!-- Maximize menu snap buttons string. --> <string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string> </resources> diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java new file mode 100644 index 000000000000..03547a55fa27 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipInputConsumer.java @@ -0,0 +1,188 @@ +/* + * 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.wm.shell.pip2.phone; + +import static android.view.Display.DEFAULT_DISPLAY; + +import android.os.Binder; +import android.os.IBinder; +import android.os.Looper; +import android.os.RemoteException; +import android.view.BatchedInputEventReceiver; +import android.view.Choreographer; +import android.view.IWindowManager; +import android.view.InputChannel; +import android.view.InputEvent; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.io.PrintWriter; + +/** + * Manages the input consumer that allows the Shell to directly receive input. + */ +public class PipInputConsumer { + + private static final String TAG = PipInputConsumer.class.getSimpleName(); + + /** + * Listener interface for callers to subscribe to input events. + */ + public interface InputListener { + /** Handles any input event. */ + boolean onInputEvent(InputEvent ev); + } + + /** + * Listener interface for callers to learn when this class is registered or unregistered with + * window manager + */ + private interface RegistrationListener { + void onRegistrationChanged(boolean isRegistered); + } + + /** + * Input handler used for the input consumer. Input events are batched and consumed with the + * SurfaceFlinger vsync. + */ + private final class InputEventReceiver extends BatchedInputEventReceiver { + + InputEventReceiver(InputChannel inputChannel, Looper looper, + Choreographer choreographer) { + super(inputChannel, looper, choreographer); + } + + @Override + public void onInputEvent(InputEvent event) { + boolean handled = true; + try { + if (mListener != null) { + handled = mListener.onInputEvent(event); + } + } finally { + finishInputEvent(event, handled); + } + } + } + + private final IWindowManager mWindowManager; + private final IBinder mToken; + private final String mName; + private final ShellExecutor mMainExecutor; + + private InputEventReceiver mInputEventReceiver; + private InputListener mListener; + private RegistrationListener mRegistrationListener; + + /** + * @param name the name corresponding to the input consumer that is defined in the system. + */ + public PipInputConsumer(IWindowManager windowManager, String name, + ShellExecutor mainExecutor) { + mWindowManager = windowManager; + mToken = new Binder(); + mName = name; + mMainExecutor = mainExecutor; + } + + /** + * Sets the input listener. + */ + public void setInputListener(InputListener listener) { + mListener = listener; + } + + /** + * Sets the registration listener. + */ + public void setRegistrationListener(RegistrationListener listener) { + mRegistrationListener = listener; + mMainExecutor.execute(() -> { + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(mInputEventReceiver != null); + } + }); + } + + /** + * Check if the InputConsumer is currently registered with WindowManager + * + * @return {@code true} if registered, {@code false} if not. + */ + public boolean isRegistered() { + return mInputEventReceiver != null; + } + + /** + * Registers the input consumer. + */ + public void registerInputConsumer() { + if (mInputEventReceiver != null) { + return; + } + final InputChannel inputChannel = new InputChannel(); + try { + // TODO(b/113087003): Support Picture-in-picture in multi-display. + mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); + mWindowManager.createInputConsumer(mToken, mName, DEFAULT_DISPLAY, inputChannel); + } catch (RemoteException e) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Failed to create input consumer, %s", TAG, e); + } + mMainExecutor.execute(() -> { + mInputEventReceiver = new InputEventReceiver(inputChannel, + Looper.myLooper(), Choreographer.getInstance()); + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(true /* isRegistered */); + } + }); + } + + /** + * Unregisters the input consumer. + */ + public void unregisterInputConsumer() { + if (mInputEventReceiver == null) { + return; + } + try { + // TODO(b/113087003): Support Picture-in-picture in multi-display. + mWindowManager.destroyInputConsumer(mToken, DEFAULT_DISPLAY); + } catch (RemoteException e) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Failed to destroy input consumer, %s", TAG, e); + } + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + mMainExecutor.execute(() -> { + if (mRegistrationListener != null) { + mRegistrationListener.onRegistrationChanged(false /* isRegistered */); + } + }); + } + + /** + * Dumps the {@link PipInputConsumer} state. + */ + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "registered=" + (mInputEventReceiver != null)); + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java new file mode 100644 index 000000000000..04cf350ddd3e --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipResizeGestureHandler.java @@ -0,0 +1,538 @@ +/* + * 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.wm.shell.pip2.phone; + +import static com.android.internal.policy.TaskResizingAlgorithm.CTRL_NONE; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.PointF; +import android.graphics.Rect; +import android.hardware.input.InputManager; +import android.os.Looper; +import android.view.BatchedInputEventReceiver; +import android.view.Choreographer; +import android.view.InputChannel; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.InputMonitor; +import android.view.MotionEvent; +import android.view.ViewConfiguration; + +import androidx.annotation.VisibleForTesting; + +import com.android.wm.shell.R; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.common.pip.PipBoundsAlgorithm; +import com.android.wm.shell.common.pip.PipBoundsState; +import com.android.wm.shell.common.pip.PipPerfHintController; +import com.android.wm.shell.common.pip.PipPinchResizingAlgorithm; +import com.android.wm.shell.common.pip.PipUiEventLogger; + +import java.io.PrintWriter; +import java.util.function.Consumer; + +/** + * Helper on top of PipTouchHandler that handles inputs OUTSIDE of the PIP window, which is used to + * trigger dynamic resize. + */ +public class PipResizeGestureHandler { + + private static final String TAG = "PipResizeGestureHandler"; + private static final int PINCH_RESIZE_SNAP_DURATION = 250; + private static final float PINCH_RESIZE_AUTO_MAX_RATIO = 0.9f; + + private final Context mContext; + private final PipBoundsAlgorithm mPipBoundsAlgorithm; + private final PipBoundsState mPipBoundsState; + private final PipTouchState mPipTouchState; + private final PhonePipMenuController mPhonePipMenuController; + private final PipUiEventLogger mPipUiEventLogger; + private final PipPinchResizingAlgorithm mPinchResizingAlgorithm; + private final int mDisplayId; + private final ShellExecutor mMainExecutor; + + private final PointF mDownPoint = new PointF(); + private final PointF mDownSecondPoint = new PointF(); + private final PointF mLastPoint = new PointF(); + private final PointF mLastSecondPoint = new PointF(); + private final Point mMaxSize = new Point(); + private final Point mMinSize = new Point(); + private final Rect mLastResizeBounds = new Rect(); + private final Rect mUserResizeBounds = new Rect(); + private final Rect mDownBounds = new Rect(); + private final Runnable mUpdateMovementBoundsRunnable; + private final Consumer<Rect> mUpdateResizeBoundsCallback; + + private float mTouchSlop; + + private boolean mAllowGesture; + private boolean mIsAttached; + private boolean mIsEnabled; + private boolean mEnablePinchResize; + private boolean mIsSysUiStateValid; + private boolean mThresholdCrossed; + private boolean mOngoingPinchToResize = false; + private float mAngle = 0; + int mFirstIndex = -1; + int mSecondIndex = -1; + + private InputMonitor mInputMonitor; + private InputEventReceiver mInputEventReceiver; + + @Nullable + private final PipPerfHintController mPipPerfHintController; + + @Nullable + private PipPerfHintController.PipHighPerfSession mPipHighPerfSession; + + private int mCtrlType; + private int mOhmOffset; + + public PipResizeGestureHandler(Context context, PipBoundsAlgorithm pipBoundsAlgorithm, + PipBoundsState pipBoundsState, PipTouchState pipTouchState, + Runnable updateMovementBoundsRunnable, + PipUiEventLogger pipUiEventLogger, PhonePipMenuController menuActivityController, + ShellExecutor mainExecutor, @Nullable PipPerfHintController pipPerfHintController) { + mContext = context; + mDisplayId = context.getDisplayId(); + mMainExecutor = mainExecutor; + mPipPerfHintController = pipPerfHintController; + mPipBoundsAlgorithm = pipBoundsAlgorithm; + mPipBoundsState = pipBoundsState; + mPipTouchState = pipTouchState; + mUpdateMovementBoundsRunnable = updateMovementBoundsRunnable; + mPhonePipMenuController = menuActivityController; + mPipUiEventLogger = pipUiEventLogger; + mPinchResizingAlgorithm = new PipPinchResizingAlgorithm(); + + mUpdateResizeBoundsCallback = (rect) -> { + mUserResizeBounds.set(rect); + // mMotionHelper.synchronizePinnedStackBounds(); + mUpdateMovementBoundsRunnable.run(); + resetState(); + }; + } + + void init() { + mContext.getDisplay().getRealSize(mMaxSize); + reloadResources(); + + final Resources res = mContext.getResources(); + mEnablePinchResize = res.getBoolean(R.bool.config_pipEnablePinchResize); + } + + void onConfigurationChanged() { + reloadResources(); + } + + /** + * Called when SysUI state changed. + * + * @param isSysUiStateValid Is SysUI valid or not. + */ + public void onSystemUiStateChanged(boolean isSysUiStateValid) { + mIsSysUiStateValid = isSysUiStateValid; + } + + private void reloadResources() { + mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); + } + + private void disposeInputChannel() { + if (mInputEventReceiver != null) { + mInputEventReceiver.dispose(); + mInputEventReceiver = null; + } + if (mInputMonitor != null) { + mInputMonitor.dispose(); + mInputMonitor = null; + } + } + + void onActivityPinned() { + mIsAttached = true; + updateIsEnabled(); + } + + void onActivityUnpinned() { + mIsAttached = false; + mUserResizeBounds.setEmpty(); + updateIsEnabled(); + } + + private void updateIsEnabled() { + boolean isEnabled = mIsAttached; + if (isEnabled == mIsEnabled) { + return; + } + mIsEnabled = isEnabled; + disposeInputChannel(); + + if (mIsEnabled) { + // Register input event receiver + mInputMonitor = mContext.getSystemService(InputManager.class).monitorGestureInput( + "pip-resize", mDisplayId); + try { + mMainExecutor.executeBlocking(() -> { + mInputEventReceiver = new PipResizeInputEventReceiver( + mInputMonitor.getInputChannel(), Looper.myLooper()); + }); + } catch (InterruptedException e) { + throw new RuntimeException("Failed to create input event receiver", e); + } + } + } + + @VisibleForTesting + void onInputEvent(InputEvent ev) { + if (!mEnablePinchResize) { + // No need to handle anything if neither form of resizing is enabled. + return; + } + + if (!mPipTouchState.getAllowInputEvents()) { + // No need to handle anything if touches are not enabled + return; + } + + // Don't allow resize when PiP is stashed. + if (mPipBoundsState.isStashed()) { + return; + } + + if (ev instanceof MotionEvent) { + MotionEvent mv = (MotionEvent) ev; + int action = mv.getActionMasked(); + final Rect pipBounds = mPipBoundsState.getBounds(); + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + if (!pipBounds.contains((int) mv.getRawX(), (int) mv.getRawY()) + && mPhonePipMenuController.isMenuVisible()) { + mPhonePipMenuController.hideMenu(); + } + } + + if (mEnablePinchResize && mOngoingPinchToResize) { + onPinchResize(mv); + } + } + } + + /** + * Checks if there is currently an on-going gesture, either drag-resize or pinch-resize. + */ + public boolean hasOngoingGesture() { + return mCtrlType != CTRL_NONE || mOngoingPinchToResize; + } + + public boolean isUsingPinchToZoom() { + return mEnablePinchResize; + } + + public boolean isResizing() { + return mAllowGesture; + } + + boolean willStartResizeGesture(MotionEvent ev) { + if (isInValidSysUiState()) { + if (ev.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) { + if (mEnablePinchResize && ev.getPointerCount() == 2) { + onPinchResize(ev); + mOngoingPinchToResize = mAllowGesture; + return mAllowGesture; + } + } + } + return false; + } + + private boolean isInValidSysUiState() { + return mIsSysUiStateValid; + } + + private void onHighPerfSessionTimeout(PipPerfHintController.PipHighPerfSession session) {} + + private void cleanUpHighPerfSessionMaybe() { + if (mPipHighPerfSession != null) { + // Close the high perf session once pointer interactions are over; + mPipHighPerfSession.close(); + mPipHighPerfSession = null; + } + } + + @VisibleForTesting + void onPinchResize(MotionEvent ev) { + int action = ev.getActionMasked(); + + if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { + mFirstIndex = -1; + mSecondIndex = -1; + mAllowGesture = false; + finishResize(); + cleanUpHighPerfSessionMaybe(); + } + + if (ev.getPointerCount() != 2) { + return; + } + + final Rect pipBounds = mPipBoundsState.getBounds(); + if (action == MotionEvent.ACTION_POINTER_DOWN) { + if (mFirstIndex == -1 && mSecondIndex == -1 + && pipBounds.contains((int) ev.getRawX(0), (int) ev.getRawY(0)) + && pipBounds.contains((int) ev.getRawX(1), (int) ev.getRawY(1))) { + mAllowGesture = true; + mFirstIndex = 0; + mSecondIndex = 1; + mDownPoint.set(ev.getRawX(mFirstIndex), ev.getRawY(mFirstIndex)); + mDownSecondPoint.set(ev.getRawX(mSecondIndex), ev.getRawY(mSecondIndex)); + mDownBounds.set(pipBounds); + + mLastPoint.set(mDownPoint); + mLastSecondPoint.set(mLastSecondPoint); + mLastResizeBounds.set(mDownBounds); + + // start the high perf session as the second pointer gets detected + if (mPipPerfHintController != null) { + mPipHighPerfSession = mPipPerfHintController.startSession( + this::onHighPerfSessionTimeout, "onPinchResize"); + } + } + } + + if (action == MotionEvent.ACTION_MOVE) { + if (mFirstIndex == -1 || mSecondIndex == -1) { + return; + } + + float x0 = ev.getRawX(mFirstIndex); + float y0 = ev.getRawY(mFirstIndex); + float x1 = ev.getRawX(mSecondIndex); + float y1 = ev.getRawY(mSecondIndex); + mLastPoint.set(x0, y0); + mLastSecondPoint.set(x1, y1); + + // Capture inputs + if (!mThresholdCrossed + && (distanceBetween(mDownSecondPoint, mLastSecondPoint) > mTouchSlop + || distanceBetween(mDownPoint, mLastPoint) > mTouchSlop)) { + pilferPointers(); + mThresholdCrossed = true; + // Reset the down to begin resizing from this point + mDownPoint.set(mLastPoint); + mDownSecondPoint.set(mLastSecondPoint); + + if (mPhonePipMenuController.isMenuVisible()) { + mPhonePipMenuController.hideMenu(); + } + } + + if (mThresholdCrossed) { + mAngle = mPinchResizingAlgorithm.calculateBoundsAndAngle(mDownPoint, + mDownSecondPoint, mLastPoint, mLastSecondPoint, mMinSize, mMaxSize, + mDownBounds, mLastResizeBounds); + + /* + mPipTaskOrganizer.scheduleUserResizePip(mDownBounds, mLastResizeBounds, + mAngle, null); + */ + mPipBoundsState.setHasUserResizedPip(true); + } + } + } + + private void snapToMovementBoundsEdge(Rect bounds, Rect movementBounds) { + final int leftEdge = bounds.left; + + + final int fromLeft = Math.abs(leftEdge - movementBounds.left); + final int fromRight = Math.abs(movementBounds.right - leftEdge); + + // The PIP will be snapped to either the right or left edge, so calculate which one + // is closest to the current position. + final int newLeft = fromLeft < fromRight + ? movementBounds.left : movementBounds.right; + + bounds.offsetTo(newLeft, mLastResizeBounds.top); + } + + /** + * Resizes the pip window and updates user-resized bounds. + * + * @param bounds target bounds to resize to + * @param snapFraction snap fraction to apply after resizing + */ + void userResizeTo(Rect bounds, float snapFraction) { + Rect finalBounds = new Rect(bounds); + + // get the current movement bounds + final Rect movementBounds = mPipBoundsAlgorithm.getMovementBounds(finalBounds); + + // snap the target bounds to the either left or right edge, by choosing the closer one + snapToMovementBoundsEdge(finalBounds, movementBounds); + + // apply the requested snap fraction onto the target bounds + mPipBoundsAlgorithm.applySnapFraction(finalBounds, snapFraction); + + // resize from current bounds to target bounds without animation + // mPipTaskOrganizer.scheduleUserResizePip(mPipBoundsState.getBounds(), finalBounds, null); + // set the flag that pip has been resized + mPipBoundsState.setHasUserResizedPip(true); + + // finish the resize operation and update the state of the bounds + // mPipTaskOrganizer.scheduleFinishResizePip(finalBounds, mUpdateResizeBoundsCallback); + } + + private void finishResize() { + if (!mLastResizeBounds.isEmpty()) { + // Pinch-to-resize needs to re-calculate snap fraction and animate to the snapped + // position correctly. Drag-resize does not need to move, so just finalize resize. + if (mOngoingPinchToResize) { + final Rect startBounds = new Rect(mLastResizeBounds); + // If user resize is pretty close to max size, just auto resize to max. + if (mLastResizeBounds.width() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.x + || mLastResizeBounds.height() >= PINCH_RESIZE_AUTO_MAX_RATIO * mMaxSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMaxSize.x, mMaxSize.y); + } + + // If user resize is smaller than min size, auto resize to min + if (mLastResizeBounds.width() < mMinSize.x + || mLastResizeBounds.height() < mMinSize.y) { + resizeRectAboutCenter(mLastResizeBounds, mMinSize.x, mMinSize.y); + } + + // get the current movement bounds + final Rect movementBounds = mPipBoundsAlgorithm + .getMovementBounds(mLastResizeBounds); + + // snap mLastResizeBounds to the correct edge based on movement bounds + snapToMovementBoundsEdge(mLastResizeBounds, movementBounds); + + final float snapFraction = mPipBoundsAlgorithm.getSnapFraction( + mLastResizeBounds, movementBounds); + mPipBoundsAlgorithm.applySnapFraction(mLastResizeBounds, snapFraction); + + // disable any touch events beyond resizing too + mPipTouchState.setAllowInputEvents(false); + + /* + mPipTaskOrganizer.scheduleAnimateResizePip(startBounds, mLastResizeBounds, + PINCH_RESIZE_SNAP_DURATION, mAngle, mUpdateResizeBoundsCallback, () -> { + // enable touch events + mPipTouchState.setAllowInputEvents(true); + }); + */ + } else { + /* + mPipTaskOrganizer.scheduleFinishResizePip(mLastResizeBounds, + TRANSITION_DIRECTION_USER_RESIZE, + mUpdateResizeBoundsCallback); + */ + } + final float magnetRadiusPercent = (float) mLastResizeBounds.width() / mMinSize.x / 2.f; + mPipUiEventLogger.log( + PipUiEventLogger.PipUiEventEnum.PICTURE_IN_PICTURE_RESIZE); + } else { + resetState(); + } + } + + private void resetState() { + mCtrlType = CTRL_NONE; + mAngle = 0; + mOngoingPinchToResize = false; + mAllowGesture = false; + mThresholdCrossed = false; + } + + void setUserResizeBounds(Rect bounds) { + mUserResizeBounds.set(bounds); + } + + void invalidateUserResizeBounds() { + mUserResizeBounds.setEmpty(); + } + + Rect getUserResizeBounds() { + return mUserResizeBounds; + } + + @VisibleForTesting + Rect getLastResizeBounds() { + return mLastResizeBounds; + } + + @VisibleForTesting + void pilferPointers() { + mInputMonitor.pilferPointers(); + } + + + void updateMaxSize(int maxX, int maxY) { + mMaxSize.set(maxX, maxY); + } + + void updateMinSize(int minX, int minY) { + mMinSize.set(minX, minY); + } + + void setOhmOffset(int offset) { + mOhmOffset = offset; + } + + private float distanceBetween(PointF p1, PointF p2) { + return (float) Math.hypot(p2.x - p1.x, p2.y - p1.y); + } + + private void resizeRectAboutCenter(Rect rect, int w, int h) { + int cx = rect.centerX(); + int cy = rect.centerY(); + int l = cx - w / 2; + int r = l + w; + int t = cy - h / 2; + int b = t + h; + rect.set(l, t, r, b); + } + + /** + * Dumps the {@link PipResizeGestureHandler} state. + */ + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "mAllowGesture=" + mAllowGesture); + pw.println(innerPrefix + "mIsAttached=" + mIsAttached); + pw.println(innerPrefix + "mIsEnabled=" + mIsEnabled); + pw.println(innerPrefix + "mEnablePinchResize=" + mEnablePinchResize); + pw.println(innerPrefix + "mThresholdCrossed=" + mThresholdCrossed); + pw.println(innerPrefix + "mOhmOffset=" + mOhmOffset); + pw.println(innerPrefix + "mMinSize=" + mMinSize); + pw.println(innerPrefix + "mMaxSize=" + mMaxSize); + } + + class PipResizeInputEventReceiver extends BatchedInputEventReceiver { + PipResizeInputEventReceiver(InputChannel channel, Looper looper) { + super(channel, looper, Choreographer.getInstance()); + } + + public void onInputEvent(InputEvent event) { + PipResizeGestureHandler.this.onInputEvent(event); + finishInputEvent(event, true); + } + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java new file mode 100644 index 000000000000..efa5fc8bf8b1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchGesture.java @@ -0,0 +1,47 @@ +/* + * 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.wm.shell.pip2.phone; + +/** + * A generic interface for a touch gesture. + */ +public abstract class PipTouchGesture { + + /** + * Handle the touch down. + */ + public void onDown(PipTouchState touchState) {} + + /** + * Handle the touch move, and return whether the event was consumed. + */ + public boolean onMove(PipTouchState touchState) { + return false; + } + + /** + * Handle the touch up, and return whether the gesture was consumed. + */ + public boolean onUp(PipTouchState touchState) { + return false; + } + + /** + * Cleans up the high performance hint session if needed. + */ + public void cleanUpHighPerfSessionMaybe() {} +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java new file mode 100644 index 000000000000..d093f1e5ccc1 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipTouchState.java @@ -0,0 +1,427 @@ +/* + * 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.wm.shell.pip2.phone; + +import android.graphics.PointF; +import android.view.Display; +import android.view.MotionEvent; +import android.view.VelocityTracker; +import android.view.ViewConfiguration; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +import java.io.PrintWriter; + +/** + * This keeps track of the touch state throughout the current touch gesture. + */ +public class PipTouchState { + private static final String TAG = "PipTouchState"; + private static final boolean DEBUG = false; + + @VisibleForTesting + public static final long DOUBLE_TAP_TIMEOUT = ViewConfiguration.getDoubleTapTimeout(); + static final long HOVER_EXIT_TIMEOUT = 50; + + private final ShellExecutor mMainExecutor; + private final ViewConfiguration mViewConfig; + private final Runnable mDoubleTapTimeoutCallback; + private final Runnable mHoverExitTimeoutCallback; + + private VelocityTracker mVelocityTracker; + private long mDownTouchTime = 0; + private long mLastDownTouchTime = 0; + private long mUpTouchTime = 0; + private final PointF mDownTouch = new PointF(); + private final PointF mDownDelta = new PointF(); + private final PointF mLastTouch = new PointF(); + private final PointF mLastDelta = new PointF(); + private final PointF mVelocity = new PointF(); + private boolean mAllowTouches = true; + + // Set to false to block both PipTouchHandler and PipResizeGestureHandler's input processing + private boolean mAllowInputEvents = true; + private boolean mIsUserInteracting = false; + // Set to true only if the multiple taps occur within the double tap timeout + private boolean mIsDoubleTap = false; + // Set to true only if a gesture + private boolean mIsWaitingForDoubleTap = false; + private boolean mIsDragging = false; + // The previous gesture was a drag + private boolean mPreviouslyDragging = false; + private boolean mStartedDragging = false; + private boolean mAllowDraggingOffscreen = false; + private int mActivePointerId; + private int mLastTouchDisplayId = Display.INVALID_DISPLAY; + + public PipTouchState(ViewConfiguration viewConfig, Runnable doubleTapTimeoutCallback, + Runnable hoverExitTimeoutCallback, ShellExecutor mainExecutor) { + mViewConfig = viewConfig; + mDoubleTapTimeoutCallback = doubleTapTimeoutCallback; + mHoverExitTimeoutCallback = hoverExitTimeoutCallback; + mMainExecutor = mainExecutor; + } + + /** + * @return true if input processing is enabled for PiP in general. + */ + public boolean getAllowInputEvents() { + return mAllowInputEvents; + } + + /** + * @param allowInputEvents true to enable input processing for PiP in general. + */ + public void setAllowInputEvents(boolean allowInputEvents) { + mAllowInputEvents = allowInputEvents; + } + + /** + * Resets this state. + */ + public void reset() { + mAllowDraggingOffscreen = false; + mIsDragging = false; + mStartedDragging = false; + mIsUserInteracting = false; + mLastTouchDisplayId = Display.INVALID_DISPLAY; + } + + /** + * Processes a given touch event and updates the state. + */ + public void onTouchEvent(MotionEvent ev) { + mLastTouchDisplayId = ev.getDisplayId(); + switch (ev.getActionMasked()) { + case MotionEvent.ACTION_DOWN: { + if (!mAllowTouches) { + return; + } + + // Initialize the velocity tracker + initOrResetVelocityTracker(); + addMovementToVelocityTracker(ev); + + mActivePointerId = ev.getPointerId(0); + if (DEBUG) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Setting active pointer id on DOWN: %d", TAG, mActivePointerId); + } + mLastTouch.set(ev.getRawX(), ev.getRawY()); + mDownTouch.set(mLastTouch); + mAllowDraggingOffscreen = true; + mIsUserInteracting = true; + mDownTouchTime = ev.getEventTime(); + mIsDoubleTap = !mPreviouslyDragging + && (mDownTouchTime - mLastDownTouchTime) < DOUBLE_TAP_TIMEOUT; + mIsWaitingForDoubleTap = false; + mIsDragging = false; + mLastDownTouchTime = mDownTouchTime; + if (mDoubleTapTimeoutCallback != null) { + mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback); + } + break; + } + case MotionEvent.ACTION_MOVE: { + // Skip event if we did not start processing this touch gesture + if (!mIsUserInteracting) { + break; + } + + // Update the velocity tracker + addMovementToVelocityTracker(ev); + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == -1) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Invalid active pointer id on MOVE: %d", TAG, mActivePointerId); + break; + } + + float x = ev.getRawX(pointerIndex); + float y = ev.getRawY(pointerIndex); + mLastDelta.set(x - mLastTouch.x, y - mLastTouch.y); + mDownDelta.set(x - mDownTouch.x, y - mDownTouch.y); + + boolean hasMovedBeyondTap = mDownDelta.length() > mViewConfig.getScaledTouchSlop(); + if (!mIsDragging) { + if (hasMovedBeyondTap) { + mIsDragging = true; + mStartedDragging = true; + } + } else { + mStartedDragging = false; + } + mLastTouch.set(x, y); + break; + } + case MotionEvent.ACTION_POINTER_UP: { + // Skip event if we did not start processing this touch gesture + if (!mIsUserInteracting) { + break; + } + + // Update the velocity tracker + addMovementToVelocityTracker(ev); + + int pointerIndex = ev.getActionIndex(); + int pointerId = ev.getPointerId(pointerIndex); + if (pointerId == mActivePointerId) { + // Select a new active pointer id and reset the movement state + final int newPointerIndex = (pointerIndex == 0) ? 1 : 0; + mActivePointerId = ev.getPointerId(newPointerIndex); + if (DEBUG) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Relinquish active pointer id on POINTER_UP: %d", + TAG, mActivePointerId); + } + mLastTouch.set(ev.getRawX(newPointerIndex), ev.getRawY(newPointerIndex)); + } + break; + } + case MotionEvent.ACTION_UP: { + // Skip event if we did not start processing this touch gesture + if (!mIsUserInteracting) { + break; + } + + // Update the velocity tracker + addMovementToVelocityTracker(ev); + mVelocityTracker.computeCurrentVelocity(1000, + mViewConfig.getScaledMaximumFlingVelocity()); + mVelocity.set(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity()); + + int pointerIndex = ev.findPointerIndex(mActivePointerId); + if (pointerIndex == -1) { + ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE, + "%s: Invalid active pointer id on UP: %d", TAG, mActivePointerId); + break; + } + + mUpTouchTime = ev.getEventTime(); + mLastTouch.set(ev.getRawX(pointerIndex), ev.getRawY(pointerIndex)); + mPreviouslyDragging = mIsDragging; + mIsWaitingForDoubleTap = !mIsDoubleTap && !mIsDragging + && (mUpTouchTime - mDownTouchTime) < DOUBLE_TAP_TIMEOUT; + + } + // fall through to clean up + case MotionEvent.ACTION_CANCEL: { + recycleVelocityTracker(); + break; + } + case MotionEvent.ACTION_BUTTON_PRESS: { + removeHoverExitTimeoutCallback(); + break; + } + } + } + + /** + * @return the velocity of the active touch pointer at the point it is lifted off the screen. + */ + public PointF getVelocity() { + return mVelocity; + } + + /** + * @return the last touch position of the active pointer. + */ + public PointF getLastTouchPosition() { + return mLastTouch; + } + + /** + * @return the movement delta between the last handled touch event and the previous touch + * position. + */ + public PointF getLastTouchDelta() { + return mLastDelta; + } + + /** + * @return the down touch position. + */ + public PointF getDownTouchPosition() { + return mDownTouch; + } + + /** + * @return the movement delta between the last handled touch event and the down touch + * position. + */ + public PointF getDownTouchDelta() { + return mDownDelta; + } + + /** + * @return whether the user has started dragging. + */ + public boolean isDragging() { + return mIsDragging; + } + + /** + * @return whether the user is currently interacting with the PiP. + */ + public boolean isUserInteracting() { + return mIsUserInteracting; + } + + /** + * @return whether the user has started dragging just in the last handled touch event. + */ + public boolean startedDragging() { + return mStartedDragging; + } + + /** + * @return Display ID of the last touch event. + */ + public int getLastTouchDisplayId() { + return mLastTouchDisplayId; + } + + /** + * Sets whether touching is currently allowed. + */ + public void setAllowTouches(boolean allowTouches) { + mAllowTouches = allowTouches; + + // If the user happens to touch down before this is sent from the system during a transition + // then block any additional handling by resetting the state now + if (mIsUserInteracting) { + reset(); + } + } + + /** + * Disallows dragging offscreen for the duration of the current gesture. + */ + public void setDisallowDraggingOffscreen() { + mAllowDraggingOffscreen = false; + } + + /** + * @return whether dragging offscreen is allowed during this gesture. + */ + public boolean allowDraggingOffscreen() { + return mAllowDraggingOffscreen; + } + + /** + * @return whether this gesture is a double-tap. + */ + public boolean isDoubleTap() { + return mIsDoubleTap; + } + + /** + * @return whether this gesture will potentially lead to a following double-tap. + */ + public boolean isWaitingForDoubleTap() { + return mIsWaitingForDoubleTap; + } + + /** + * Schedules the callback to run if the next double tap does not occur. Only runs if + * isWaitingForDoubleTap() is true. + */ + public void scheduleDoubleTapTimeoutCallback() { + if (mIsWaitingForDoubleTap) { + long delay = getDoubleTapTimeoutCallbackDelay(); + mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback); + mMainExecutor.executeDelayed(mDoubleTapTimeoutCallback, delay); + } + } + + long getDoubleTapTimeoutCallbackDelay() { + if (mIsWaitingForDoubleTap) { + return Math.max(0, DOUBLE_TAP_TIMEOUT - (mUpTouchTime - mDownTouchTime)); + } + return -1; + } + + /** + * Removes the timeout callback if it's in queue. + */ + public void removeDoubleTapTimeoutCallback() { + mIsWaitingForDoubleTap = false; + mMainExecutor.removeCallbacks(mDoubleTapTimeoutCallback); + } + + void scheduleHoverExitTimeoutCallback() { + mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback); + mMainExecutor.executeDelayed(mHoverExitTimeoutCallback, HOVER_EXIT_TIMEOUT); + } + + void removeHoverExitTimeoutCallback() { + mMainExecutor.removeCallbacks(mHoverExitTimeoutCallback); + } + + void addMovementToVelocityTracker(MotionEvent event) { + if (mVelocityTracker == null) { + return; + } + + // Add movement to velocity tracker using raw screen X and Y coordinates instead + // of window coordinates because the window frame may be moving at the same time. + float deltaX = event.getRawX() - event.getX(); + float deltaY = event.getRawY() - event.getY(); + event.offsetLocation(deltaX, deltaY); + mVelocityTracker.addMovement(event); + event.offsetLocation(-deltaX, -deltaY); + } + + private void initOrResetVelocityTracker() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } else { + mVelocityTracker.clear(); + } + } + + private void recycleVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker.recycle(); + mVelocityTracker = null; + } + } + + /** + * Dumps the {@link PipTouchState}. + */ + public void dump(PrintWriter pw, String prefix) { + final String innerPrefix = prefix + " "; + pw.println(prefix + TAG); + pw.println(innerPrefix + "mAllowTouches=" + mAllowTouches); + pw.println(innerPrefix + "mAllowInputEvents=" + mAllowInputEvents); + pw.println(innerPrefix + "mActivePointerId=" + mActivePointerId); + pw.println(innerPrefix + "mLastTouchDisplayId=" + mLastTouchDisplayId); + pw.println(innerPrefix + "mDownTouch=" + mDownTouch); + pw.println(innerPrefix + "mDownDelta=" + mDownDelta); + pw.println(innerPrefix + "mLastTouch=" + mLastTouch); + pw.println(innerPrefix + "mLastDelta=" + mLastDelta); + pw.println(innerPrefix + "mVelocity=" + mVelocity); + pw.println(innerPrefix + "mIsUserInteracting=" + mIsUserInteracting); + pw.println(innerPrefix + "mIsDragging=" + mIsDragging); + pw.println(innerPrefix + "mStartedDragging=" + mStartedDragging); + pw.println(innerPrefix + "mAllowDraggingOffscreen=" + mAllowDraggingOffscreen); + } +} diff --git a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml index b3eb2bfd9e9d..f5a8655b81f0 100644 --- a/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml +++ b/libs/WindowManager/Shell/tests/flicker/pip/csuiteDefaultTemplate.xml @@ -38,8 +38,6 @@ <!-- Increase trace size: 20mb for WM and 80mb for SF --> <option name="run-command" value="cmd window tracing size 20480"/> <option name="run-command" value="su root service call SurfaceFlinger 1029 i32 81920"/> - <!-- uninstall Maps, so that latest version can be installed from pStash directly --> - <option name="run-command" value="su root pm uninstall -k --user 0 com.google.android.apps.maps"/> </target_preparer> <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> <option name="test-user-token" value="%TEST_USER%"/> @@ -71,7 +69,6 @@ <option name="install-arg" value="-g"/> <option name="install-arg" value="-r"/> <option name="test-file-name" value="pstash://com.netflix.mediaclient"/> - <option name="test-file-name" value="pstash://com.google.android.apps.maps"/> </target_preparer> <!-- Enable mocking GPS location by the test app --> diff --git a/libs/androidfw/ZipFileRO.cpp b/libs/androidfw/ZipFileRO.cpp index d7b5914130ee..9d4b426a6759 100644 --- a/libs/androidfw/ZipFileRO.cpp +++ b/libs/androidfw/ZipFileRO.cpp @@ -304,7 +304,7 @@ bool ZipFileRO::uncompressEntry(ZipEntryRO entry, int fd) const _ZipEntryRO *zipEntry = reinterpret_cast<_ZipEntryRO*>(entry); const int32_t error = ExtractEntryToFile(mHandle, &(zipEntry->entry), fd); if (error) { - ALOGW("ExtractToMemory failed with %s", ErrorCodeString(error)); + ALOGW("ExtractToFile failed with %s", ErrorCodeString(error)); return false; } diff --git a/libs/dream/lowlight/tests/Android.bp b/libs/dream/lowlight/tests/Android.bp index 4dafd0aa6df4..42547832133b 100644 --- a/libs/dream/lowlight/tests/Android.bp +++ b/libs/dream/lowlight/tests/Android.bp @@ -27,7 +27,7 @@ android_test { "androidx.test.runner", "androidx.test.rules", "androidx.test.ext.junit", - "animationlib", + "//frameworks/libs/systemui:animationlib", "frameworks-base-testutils", "junit", "kotlinx_coroutines_test", diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig index c1be6b5473d4..50fdb8aaf3cd 100644 --- a/media/java/android/media/flags/media_better_together.aconfig +++ b/media/java/android/media/flags/media_better_together.aconfig @@ -121,3 +121,13 @@ flag { description: "Enables apps owning a MediaBrowserService to disconnect all connected browsers." bug: "185136506" } + +flag { + name: "enable_prevention_of_manager_scans_when_no_apps_scan" + namespace: "media_solutions" + description: "Prevents waking up route providers when no apps are scanning, even if SysUI or Settings are scanning." + bug: "319604673" + metadata { + purpose: PURPOSE_BUGFIX + } +} diff --git a/media/java/android/media/projection/MediaProjectionManager.java b/media/java/android/media/projection/MediaProjectionManager.java index 7ed67dcde913..2a0648d87c85 100644 --- a/media/java/android/media/projection/MediaProjectionManager.java +++ b/media/java/android/media/projection/MediaProjectionManager.java @@ -23,9 +23,6 @@ import android.annotation.SystemService; import android.annotation.TestApi; import android.app.Activity; import android.app.ActivityOptions.LaunchCookie; -import android.compat.annotation.ChangeId; -import android.compat.annotation.Disabled; -import android.compat.annotation.Overridable; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -69,18 +66,6 @@ public final class MediaProjectionManager { private static final String TAG = "MediaProjectionManager"; /** - * This change id ensures that users are presented with a choice of capturing a single app - * or the entire screen when initiating a MediaProjection session, overriding the usage of - * MediaProjectionConfig#createConfigForDefaultDisplay. - * - * @hide - */ - @ChangeId - @Overridable - @Disabled - public static final long OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION = 316897322L; - - /** * Intent extra to customize the permission dialog based on the host app's preferences. * @hide */ diff --git a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java index 7093ba42f40d..f86eb61c365f 100644 --- a/packages/CrashRecovery/services/java/com/android/server/RescueParty.java +++ b/packages/CrashRecovery/services/java/com/android/server/RescueParty.java @@ -139,7 +139,7 @@ public class RescueParty { static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = "namespace_to_package_mapping"; @VisibleForTesting - static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 10; + static final long DEFAULT_FACTORY_RESET_THROTTLE_DURATION_MIN = 1440; private static final String NAME = "rescue-party-observer"; diff --git a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt index d21077ee7c5a..a30956ecf5a5 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/common/BiometricHandler.kt @@ -98,7 +98,8 @@ fun runBiometricFlow( context: Context, openMoreOptionsPage: () -> Unit, sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit, - onCancelFlowAndFinish: (String) -> Unit, + onCancelFlowAndFinish: () -> Unit, + onIllegalStateAndFinish: (String) -> Unit, getRequestDisplayInfo: RequestDisplayInfo? = null, getProviderInfoList: List<ProviderInfo>? = null, getProviderDisplayInfo: ProviderDisplayInfo? = null, @@ -131,7 +132,7 @@ fun runBiometricFlow( val callback: BiometricPrompt.AuthenticationCallback = setupBiometricAuthenticationCallback(sendDataToProvider, biometricEntry, - onCancelFlowAndFinish) + onCancelFlowAndFinish, onIllegalStateAndFinish) val cancellationSignal = CancellationSignal() cancellationSignal.setOnCancelListener { @@ -211,7 +212,8 @@ private fun removeDeviceCredential(requestAllowedAuthenticators: Int): Int { private fun setupBiometricAuthenticationCallback( sendDataToProvider: (EntryInfo, BiometricPrompt.AuthenticationResult) -> Unit, selectedEntry: EntryInfo, - onCancelFlowAndFinish: (String) -> Unit + onCancelFlowAndFinish: () -> Unit, + onIllegalStateAndFinish: (String) -> Unit, ): BiometricPrompt.AuthenticationCallback { val callback: BiometricPrompt.AuthenticationCallback = object : BiometricPrompt.AuthenticationCallback() { @@ -224,14 +226,12 @@ private fun setupBiometricAuthenticationCallback( if (authResult != null) { sendDataToProvider(selectedEntry, authResult) } else { - onCancelFlowAndFinish("The biometric flow succeeded but unexpectedly " + + onIllegalStateAndFinish("The biometric flow succeeded but unexpectedly " + "returned a null value.") - // TODO(b/326243754) : Propagate to provider } } catch (e: Exception) { - onCancelFlowAndFinish("The biometric flow succeeded but failed on handling " + + onIllegalStateAndFinish("The biometric flow succeeded but failed on handling " + "the result. See: \n$e\n") - // TODO(b/326243754) : Propagate to provider } } @@ -245,6 +245,12 @@ private fun setupBiometricAuthenticationCallback( override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { super.onAuthenticationError(errorCode, errString) Log.d(TAG, "Authentication error-ed out: $errorCode and $errString") + if (errorCode == BiometricPrompt.BIOMETRIC_ERROR_USER_CANCELED) { + // Note that because the biometric prompt is imbued directly + // into the selector, parity applies to the selector's cancellation instead + // of the provider's biometric prompt cancellation. + onCancelFlowAndFinish() + } // TODO(b/326243754) : Propagate to provider } diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt index b59ccc264630..4d7272c7716e 100644 --- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt +++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt @@ -148,7 +148,8 @@ fun GetCredentialScreen( // activeEntry will always be what represents the single tap flow biometricEntry = getCredentialUiState.activeEntry, onMoreOptionSelected = viewModel::getFlowOnMoreOptionSelected, - onCancelFlowAndFinish = viewModel::onIllegalUiState, + onCancelFlowAndFinish = viewModel::onUserCancel, + onIllegalStateAndFinish = viewModel::onIllegalUiState, requestDisplayInfo = getCredentialUiState.requestDisplayInfo, providerInfoList = getCredentialUiState.providerInfoList, providerDisplayInfo = getCredentialUiState.providerDisplayInfo, @@ -212,7 +213,8 @@ fun GetCredentialScreen( @Composable internal fun BiometricSelectionPage( biometricEntry: EntryInfo?, - onCancelFlowAndFinish: (String) -> Unit, + onCancelFlowAndFinish: () -> Unit, + onIllegalStateAndFinish: (String) -> Unit, onMoreOptionSelected: () -> Unit, requestDisplayInfo: RequestDisplayInfo, providerInfoList: List<ProviderInfo>, @@ -230,6 +232,7 @@ internal fun BiometricSelectionPage( openMoreOptionsPage = onMoreOptionSelected, sendDataToProvider = onBiometricEntrySelected, onCancelFlowAndFinish = onCancelFlowAndFinish, + onIllegalStateAndFinish = onIllegalStateAndFinish, getRequestDisplayInfo = requestDisplayInfo, getProviderInfoList = providerInfoList, getProviderDisplayInfo = providerDisplayInfo, diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp index 66fad3689eac..d6cbf2a0f581 100644 --- a/packages/SettingsLib/Android.bp +++ b/packages/SettingsLib/Android.bp @@ -22,7 +22,7 @@ android_library { "guava", "WifiTrackerLibRes", - "iconloader", + "//frameworks/libs/systemui:iconloader", "setupdesign", "SettingsLibActionBarShadow", @@ -88,6 +88,7 @@ java_defaults { aconfig_declarations { name: "settingslib_media_flags", package: "com.android.settingslib.media.flags", + container: "system", srcs: [ "aconfig/settingslib_media_flag_declarations.aconfig", ], @@ -101,6 +102,7 @@ java_aconfig_library { aconfig_declarations { name: "settingslib_flags", package: "com.android.settingslib.flags", + container: "system", srcs: [ "aconfig/settingslib.aconfig", ], diff --git a/packages/SettingsLib/aconfig/settingslib.aconfig b/packages/SettingsLib/aconfig/settingslib.aconfig index 54c5a14702f6..e09ab0086451 100644 --- a/packages/SettingsLib/aconfig/settingslib.aconfig +++ b/packages/SettingsLib/aconfig/settingslib.aconfig @@ -1,4 +1,5 @@ package: "com.android.settingslib.flags" +container: "system" flag { name: "new_status_bar_icons" diff --git a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig index f3e537b33230..4d70aec9fa5c 100644 --- a/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig +++ b/packages/SettingsLib/aconfig/settingslib_media_flag_declarations.aconfig @@ -1,4 +1,5 @@ package: "com.android.settingslib.media.flags" +container: "system" flag { name: "use_media_router2_for_info_media_manager" diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java index 6b833cc68b93..0282f03e0b98 100644 --- a/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java +++ b/packages/SettingsLib/src/com/android/settingslib/applications/StorageStatsSource.java @@ -16,6 +16,7 @@ package com.android.settingslib.applications; +import android.annotation.NonNull; import android.app.usage.StorageStats; import android.app.usage.StorageStatsManager; import android.content.Context; @@ -25,6 +26,7 @@ import android.os.UserHandle; import androidx.annotation.VisibleForTesting; import java.io.IOException; +import java.util.UUID; /** * StorageStatsSource wraps the StorageStatsManager for testability purposes. @@ -59,6 +61,10 @@ public class StorageStatsSource { return mStorageStatsManager.getCacheQuotaBytes(volumeUuid, uid); } + public long getTotalBytes(@NonNull UUID storageUuid) throws IOException { + return mStorageStatsManager.getTotalBytes(storageUuid); + } + /** * Static class that provides methods for querying the amount of external storage available as * well as breaking it up into several media types. diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java index e34c50eb5ce6..4e6d3cbb60cf 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java @@ -174,6 +174,7 @@ public abstract class InfoMediaManager { public void startScan() { mMediaDevices.clear(); + registerRouter(); startScanOnRouter(); updateRouteListingPreference(); refreshDevices(); @@ -188,10 +189,19 @@ public abstract class InfoMediaManager { } } - public abstract void stopScan(); + public final void stopScan() { + stopScanOnRouter(); + unregisterRouter(); + } + + protected abstract void stopScanOnRouter(); protected abstract void startScanOnRouter(); + protected abstract void registerRouter(); + + protected abstract void unregisterRouter(); + protected abstract void transferToRoute(@NonNull MediaRoute2Info route); protected abstract void selectRoute( @@ -514,17 +524,20 @@ public abstract class InfoMediaManager { // MediaRoute2Info.getType was made public on API 34, but exists since API 30. @SuppressWarnings("NewApi") private synchronized void buildAvailableRoutes() { - for (MediaRoute2Info route : getAvailableRoutes()) { + RoutingSessionInfo activeSession = getActiveRoutingSession(); + + for (MediaRoute2Info route : getAvailableRoutes(activeSession)) { if (DEBUG) { Log.d(TAG, "buildAvailableRoutes() route : " + route.getName() + ", volume : " + route.getVolume() + ", type : " + route.getType()); } - addMediaDevice(route); + addMediaDevice(route, activeSession); } } - private synchronized List<MediaRoute2Info> getAvailableRoutes() { + + private synchronized List<MediaRoute2Info> getAvailableRoutes( + RoutingSessionInfo activeSession) { List<MediaRoute2Info> availableRoutes = new ArrayList<>(); - RoutingSessionInfo activeSession = getActiveRoutingSession(); List<MediaRoute2Info> selectedRoutes = getSelectedRoutes(activeSession); availableRoutes.addAll(selectedRoutes); @@ -562,7 +575,7 @@ public abstract class InfoMediaManager { // MediaRoute2Info.getType was made public on API 34, but exists since API 30. @SuppressWarnings("NewApi") @VisibleForTesting - void addMediaDevice(MediaRoute2Info route) { + void addMediaDevice(MediaRoute2Info route, RoutingSessionInfo activeSession) { final int deviceType = route.getType(); MediaDevice mediaDevice = null; switch (deviceType) { @@ -627,14 +640,13 @@ public abstract class InfoMediaManager { break; } - if (mediaDevice != null - && getActiveRoutingSession().getSelectedRoutes().contains(route.getId())) { - mediaDevice.setState(STATE_SELECTED); - if (mCurrentConnectedDevice == null) { - mCurrentConnectedDevice = mediaDevice; - } - } if (mediaDevice != null) { + if (activeSession.getSelectedRoutes().contains(route.getId())) { + mediaDevice.setState(STATE_SELECTED); + if (mCurrentConnectedDevice == null) { + mCurrentConnectedDevice = mediaDevice; + } + } mMediaDevices.add(mediaDevice); } } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java index c4fac358c01a..23063da747af 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java @@ -62,22 +62,30 @@ public class ManagerInfoMediaManager extends InfoMediaManager { @Override protected void startScanOnRouter() { if (!mIsScanning) { - mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); mRouterManager.registerScanRequest(); mIsScanning = true; } } @Override - public void stopScan() { + protected void registerRouter() { + mRouterManager.registerCallback(mExecutor, mMediaRouterCallback); + } + + @Override + protected void stopScanOnRouter() { if (mIsScanning) { - mRouterManager.unregisterCallback(mMediaRouterCallback); mRouterManager.unregisterScanRequest(); mIsScanning = false; } } @Override + protected void unregisterRouter() { + mRouterManager.unregisterCallback(mMediaRouterCallback); + } + + @Override protected void transferToRoute(@NonNull MediaRoute2Info route) { // TODO: b/279555229 - provide real user handle of a caller. mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle()); diff --git a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java index 2b8c2dd0d0e3..cf11c6da737f 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/NoOpInfoMediaManager.java @@ -63,12 +63,22 @@ import java.util.List; } @Override - public void stopScan() { + protected void startScanOnRouter() { // Do nothing. } @Override - protected void startScanOnRouter() { + protected void registerRouter() { + // Do nothing. + } + + @Override + protected void stopScanOnRouter() { + // Do nothing. + } + + @Override + protected void unregisterRouter() { // Do nothing. } diff --git a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java index 9c82cb1ef57d..0dceebab13f8 100644 --- a/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java +++ b/packages/SettingsLib/src/com/android/settingslib/media/RouterInfoMediaManager.java @@ -97,11 +97,6 @@ public final class RouterInfoMediaManager extends InfoMediaManager { @Override protected void startScanOnRouter() { - mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY); - mRouter.registerRouteListingPreferenceUpdatedCallback( - mExecutor, mRouteListingPreferenceCallback); - mRouter.registerTransferCallback(mExecutor, mTransferCallback); - mRouter.registerControllerCallback(mExecutor, mControllerCallback); if (Flags.enableScreenOffScanning()) { MediaRouter2.ScanRequest request = new MediaRouter2.ScanRequest.Builder().build(); mScanToken.compareAndSet(null, mRouter.requestScan(request)); @@ -111,7 +106,16 @@ public final class RouterInfoMediaManager extends InfoMediaManager { } @Override - public void stopScan() { + protected void registerRouter() { + mRouter.registerRouteCallback(mExecutor, mRouteCallback, RouteDiscoveryPreference.EMPTY); + mRouter.registerRouteListingPreferenceUpdatedCallback( + mExecutor, mRouteListingPreferenceCallback); + mRouter.registerTransferCallback(mExecutor, mTransferCallback); + mRouter.registerControllerCallback(mExecutor, mControllerCallback); + } + + @Override + protected void stopScanOnRouter() { if (Flags.enableScreenOffScanning()) { MediaRouter2.ScanToken token = mScanToken.getAndSet(null); if (token != null) { @@ -120,6 +124,10 @@ public final class RouterInfoMediaManager extends InfoMediaManager { } else { mRouter.stopScan(); } + } + + @Override + protected void unregisterRouter() { mRouter.unregisterControllerCallback(mControllerCallback); mRouter.unregisterTransferCallback(mTransferCallback); mRouter.unregisterRouteListingPreferenceUpdatedCallback(mRouteListingPreferenceCallback); diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java index d85d2534f856..f8dcec7315b2 100644 --- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java +++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/media/InfoMediaManagerTest.java @@ -86,6 +86,30 @@ public class InfoMediaManagerTest { private static final String TEST_DUPLICATED_ID_2 = "test_duplicated_id_2"; private static final String TEST_DUPLICATED_ID_3 = "test_duplicated_id_3"; + private static final String TEST_SYSTEM_ROUTE_ID = "TEST_SYSTEM_ROUTE_ID"; + private static final String TEST_BLUETOOTH_ROUTE_ID = "TEST_BT_ROUTE_ID"; + + private static final RoutingSessionInfo TEST_SYSTEM_ROUTING_SESSION = + new RoutingSessionInfo.Builder("FAKE_SYSTEM_ROUTING_SESSION_ID", TEST_PACKAGE_NAME) + .addSelectedRoute(TEST_SYSTEM_ROUTE_ID) + .addTransferableRoute(TEST_BLUETOOTH_ROUTE_ID) + .setSystemSession(true) + .build(); + + private static final MediaRoute2Info TEST_SELECTED_SYSTEM_ROUTE = + new MediaRoute2Info.Builder(TEST_SYSTEM_ROUTE_ID, "SELECTED_SYSTEM_ROUTE") + .setSystemRoute(true) + .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) + .build(); + + private static final MediaRoute2Info TEST_BLUETOOTH_ROUTE = + new MediaRoute2Info.Builder(TEST_BLUETOOTH_ROUTE_ID, "BLUETOOTH_ROUTE") + .setSystemRoute(true) + .addFeature(MediaRoute2Info.FEATURE_LIVE_AUDIO) + .setType(TYPE_BLUETOOTH_A2DP) + .setAddress("00:00:00:00:00:00") + .build(); + @Mock private MediaRouter2Manager mRouterManager; @Mock @@ -795,19 +819,19 @@ public class InfoMediaManagerTest { when(route2Info.getType()).thenReturn(TYPE_REMOTE_SPEAKER); when(route2Info.getId()).thenReturn(TEST_ID); - mInfoMediaManager.addMediaDevice(route2Info); + mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION); assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof InfoMediaDevice).isTrue(); when(route2Info.getType()).thenReturn(TYPE_USB_DEVICE); when(route2Info.getId()).thenReturn(TEST_ID); mInfoMediaManager.mMediaDevices.clear(); - mInfoMediaManager.addMediaDevice(route2Info); + mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION); assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue(); when(route2Info.getType()).thenReturn(TYPE_WIRED_HEADSET); when(route2Info.getId()).thenReturn(TEST_ID); mInfoMediaManager.mMediaDevices.clear(); - mInfoMediaManager.addMediaDevice(route2Info); + mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION); assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue(); when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP); @@ -818,12 +842,12 @@ public class InfoMediaManagerTest { when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))) .thenReturn(cachedDevice); mInfoMediaManager.mMediaDevices.clear(); - mInfoMediaManager.addMediaDevice(route2Info); + mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION); assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof BluetoothMediaDevice).isTrue(); when(route2Info.getType()).thenReturn(TYPE_BUILTIN_SPEAKER); mInfoMediaManager.mMediaDevices.clear(); - mInfoMediaManager.addMediaDevice(route2Info); + mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION); assertThat(mInfoMediaManager.mMediaDevices.get(0) instanceof PhoneMediaDevice).isTrue(); } @@ -841,26 +865,27 @@ public class InfoMediaManagerTest { .thenReturn(null); mInfoMediaManager.mMediaDevices.clear(); - mInfoMediaManager.addMediaDevice(route2Info); + mInfoMediaManager.addMediaDevice(route2Info, TEST_SYSTEM_ROUTING_SESSION); assertThat(mInfoMediaManager.mMediaDevices.size()).isEqualTo(0); } @Test public void addMediaDevice_deviceIncludedInSelectedDevices_shouldSetAsCurrentConnected() { - final MediaRoute2Info route2Info = mock(MediaRoute2Info.class); final CachedBluetoothDeviceManager cachedBluetoothDeviceManager = mock(CachedBluetoothDeviceManager.class); + final CachedBluetoothDevice cachedDevice = mock(CachedBluetoothDevice.class); - final List<RoutingSessionInfo> routingSessionInfos = new ArrayList<>(); - final RoutingSessionInfo sessionInfo = mock(RoutingSessionInfo.class); - routingSessionInfos.add(sessionInfo); + RoutingSessionInfo selectedBtSession = + new RoutingSessionInfo.Builder(TEST_SYSTEM_ROUTING_SESSION) + .clearSelectedRoutes() + .clearTransferableRoutes() + .addSelectedRoute(TEST_BLUETOOTH_ROUTE_ID) + .addTransferableRoute(TEST_SYSTEM_ROUTE_ID) + .build(); - when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)).thenReturn(routingSessionInfos); - when(sessionInfo.getSelectedRoutes()).thenReturn(ImmutableList.of(TEST_ID)); - when(route2Info.getType()).thenReturn(TYPE_BLUETOOTH_A2DP); - when(route2Info.getAddress()).thenReturn("00:00:00:00:00:00"); - when(route2Info.getId()).thenReturn(TEST_ID); + when(mRouterManager.getRoutingSessions(TEST_PACKAGE_NAME)) + .thenReturn(List.of(selectedBtSession)); when(mLocalBluetoothManager.getCachedDeviceManager()) .thenReturn(cachedBluetoothDeviceManager); when(cachedBluetoothDeviceManager.findDevice(any(BluetoothDevice.class))) @@ -868,7 +893,7 @@ public class InfoMediaManagerTest { mInfoMediaManager.mRouterManager = mRouterManager; mInfoMediaManager.mMediaDevices.clear(); - mInfoMediaManager.addMediaDevice(route2Info); + mInfoMediaManager.addMediaDevice(TEST_BLUETOOTH_ROUTE, selectedBtSession); MediaDevice device = mInfoMediaManager.mMediaDevices.get(0); diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp index bf4f60d84e4d..e9c267284e91 100644 --- a/packages/SettingsProvider/Android.bp +++ b/packages/SettingsProvider/Android.bp @@ -32,6 +32,7 @@ android_library { "unsupportedappusage", ], static_libs: [ + "aconfig_demo_flags_java_lib", "device_config_service_flags_java", "libaconfig_java_proto_lite", "SettingsLibDeviceStateRotationLock", @@ -87,6 +88,7 @@ android_test { aconfig_declarations { name: "device_config_service_flags", package: "com.android.providers.settings", + container: "system", srcs: [ "src/com/android/providers/settings/device_config_service.aconfig", ], diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java index 4e4c22fadcfd..a28cfeb4dc80 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java +++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsState.java @@ -60,16 +60,17 @@ import libcore.io.IoUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; -import java.io.FileWriter; import java.io.IOException; import java.io.PrintWriter; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFilePermission; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; @@ -165,8 +166,8 @@ final class SettingsState { private static final String STORAGE_MIGRATION_FLAG = "core_experiments_team_internal/com.android.providers.settings.storage_test_mission_1"; - private static final String STORAGE_MIGRATION_LOG = - "/metadata/aconfig/flags/storage_migration.log"; + private static final String STORAGE_MIGRATION_MARKER_FILE = + "/metadata/aconfig/storage_test_mission_1"; /** * This tag is applied to all aconfig default value-loaded flags. @@ -1467,16 +1468,29 @@ final class SettingsState { } } - if (name != null && name.equals(STORAGE_MIGRATION_FLAG) && value.equals("true")) { - File file = new File(STORAGE_MIGRATION_LOG); - if (!file.exists()) { - try (BufferedWriter writer = - new BufferedWriter(new FileWriter(STORAGE_MIGRATION_LOG))) { - final long timestamp = System.currentTimeMillis(); - String entry = String.format("%d | Log init", timestamp); - writer.write(entry); - } catch (IOException e) { - Slog.e(LOG_TAG, "failed to write storage migration file", e); + if (isConfigSettingsKey(mKey) && name != null + && name.equals(STORAGE_MIGRATION_FLAG)) { + if (value.equals("true")) { + Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE); + if (!Files.exists(path)) { + Files.createFile(path); + } + + Set<PosixFilePermission> perms = + Files.readAttributes(path, PosixFileAttributes.class).permissions(); + perms.add(PosixFilePermission.OWNER_WRITE); + perms.add(PosixFilePermission.OWNER_READ); + perms.add(PosixFilePermission.GROUP_READ); + perms.add(PosixFilePermission.OTHERS_READ); + try { + Files.setPosixFilePermissions(path, perms); + } catch (Exception e) { + Slog.e(LOG_TAG, "failed to set permissions on migration marker", e); + } + } else { + java.nio.file.Path path = Paths.get(STORAGE_MIGRATION_MARKER_FILE); + if (Files.exists(path)) { + Files.delete(path); } } } diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig index c572bdb57c6a..d20fbf591a25 100644 --- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig +++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig @@ -1,4 +1,5 @@ package: "com.android.providers.settings" +container: "system" flag { name: "support_overrides" diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 58040716db3e..b94e224850aa 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -896,7 +896,6 @@ <!-- Permissions required for CTS test - CtsVoiceInteractionTestCases --> <uses-permission android:name="android.permission.RESET_HOTWORD_TRAINING_DATA_EGRESS_COUNT" /> - <uses-permission android:name="android.permission.RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA" /> <uses-permission android:name="android.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO" /> <uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" /> diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp index 089782c5e82a..40db52eec81b 100644 --- a/packages/SystemUI/Android.bp +++ b/packages/SystemUI/Android.bp @@ -57,6 +57,7 @@ filegroup { "src-release/**/*.kt", "src-release/**/*.java", ], + visibility: ["//visibility:private"], } filegroup { @@ -65,6 +66,7 @@ filegroup { "src-debug/**/*.kt", "src-debug/**/*.java", ], + visibility: ["//visibility:private"], } //Create a library to expose SystemUI's resources to other modules. @@ -105,6 +107,7 @@ android_library { }, use_resource_processor: true, static_libs: [ + "//frameworks/libs/systemui:compilelib", "SystemUI-res", "WifiTrackerLib", "WindowManager-Shell", @@ -117,7 +120,7 @@ android_library { "SystemUI-statsd", "SettingsLib", "com_android_systemui_flags_lib", - "com_android_systemui_shared_flags_lib", + "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "androidx.core_core-ktx", "androidx.viewpager2_viewpager2", "androidx.legacy_legacy-support-v4", @@ -145,7 +148,7 @@ android_library { "device_state_flags_lib", "kotlinx_coroutines_android", "kotlinx_coroutines", - "iconloader_base", + "//frameworks/libs/systemui:iconloader_base", "SystemUI-tags", "SystemUI-proto", "monet", @@ -156,7 +159,7 @@ android_library { "lottie", "LowLightDreamLib", "TraceurCommon", - "motion_tool_lib", + "//frameworks/libs/systemui:motion_tool_lib", "notification_flags_lib", "PlatformComposeCore", "PlatformComposeSceneTransitionLayout", @@ -263,7 +266,7 @@ android_library { "SystemUI-statsd", "SettingsLib", "com_android_systemui_flags_lib", - "com_android_systemui_shared_flags_lib", + "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "flag-junit-base", "platform-parametric-runner-lib", "androidx.viewpager2_viewpager2", @@ -293,7 +296,7 @@ android_library { "kotlinx-coroutines-core", "kotlinx_coroutines_test", "kotlin-reflect", - "iconloader_base", + "//frameworks/libs/systemui:iconloader_base", "SystemUI-tags", "SystemUI-proto", "metrics-helper-lib", @@ -307,7 +310,7 @@ android_library { "jsr330", "WindowManager-Shell", "LowLightDreamLib", - "motion_tool_lib", + "//frameworks/libs/systemui:motion_tool_lib", "androidx.core_core-animation-testing", "androidx.compose.ui_ui", "flag-junit", @@ -344,6 +347,7 @@ android_library { "compose/facade/enabled/src/**/*.kt", ], static_libs: [ + "//frameworks/libs/systemui:compilelib", "SystemUI-tests-base", "androidx.test.uiautomator_uiautomator", "androidx.core_core-animation-testing", @@ -398,6 +402,7 @@ android_app { "compose/facade/enabled/src/**/*.kt", ], static_libs: [ + "//frameworks/libs/systemui:compilelib", "SystemUI-tests-base", "androidx.compose.runtime_runtime", ], diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index af89f63cb4e3..4bfc6296b8a3 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -25,6 +25,13 @@ flag { } flag { + name: "notification_heads_up_cycling" + namespace: "systemui" + description: "Heads-up notification cycling animation for the Notification Avalanche feature." + bug: "316404716" +} + +flag { name: "notification_minimalism_prototype" namespace: "systemui" description: "Prototype of notification minimalism; the new 'Intermediate' lockscreen customization proposal." diff --git a/packages/SystemUI/animation/Android.bp b/packages/SystemUI/animation/Android.bp index 1ce3be897259..dec664fa7a14 100644 --- a/packages/SystemUI/animation/Android.bp +++ b/packages/SystemUI/animation/Android.bp @@ -47,8 +47,8 @@ android_library { "com_android_systemui_flags_lib", "SystemUIShaderLib", "WindowManager-Shell-shared", - "animationlib", - "com_android_systemui_shared_flags_lib", + "//frameworks/libs/systemui:animationlib", + "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", ], manifest: "AndroidManifest.xml", diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt index 1c938a6c19a5..51a7e8ed8b40 100644 --- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt +++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/DefaultClockSection.kt @@ -34,7 +34,6 @@ import androidx.compose.ui.viewinterop.AndroidView import androidx.core.view.contains import com.android.compose.animation.scene.SceneScope import com.android.compose.modifiers.padding -import com.android.systemui.customization.R as customizationR import com.android.systemui.customization.R import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.largeClockElementKey import com.android.systemui.keyguard.ui.composable.blueprint.ClockElementKeys.smallClockElementKey @@ -65,32 +64,24 @@ constructor( return } val context = LocalContext.current - MovableElement(key = smallClockElementKey, modifier = modifier) { - content { - AndroidView( - factory = { context -> - FrameLayout(context).apply { - ensureClockViewExists(checkNotNull(currentClock).smallClock.view) - } - }, - update = { - it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view) - }, - modifier = - Modifier.height(dimensionResource(R.dimen.small_clock_height)) - .padding( - horizontal = - dimensionResource(customizationR.dimen.clock_padding_start) - ) - .padding(top = { viewModel.getSmallClockTopMargin(context) }) - .onTopPlacementChanged(onTopChanged) - .burnInAware( - viewModel = aodBurnInViewModel, - params = burnInParams, - ), - ) - } - } + AndroidView( + factory = { context -> + FrameLayout(context).apply { + ensureClockViewExists(checkNotNull(currentClock).smallClock.view) + } + }, + update = { it.ensureClockViewExists(checkNotNull(currentClock).smallClock.view) }, + modifier = + modifier + .height(dimensionResource(R.dimen.small_clock_height)) + .padding(top = { viewModel.getSmallClockTopMargin(context) }) + .onTopPlacementChanged(onTopChanged) + .burnInAware( + viewModel = aodBurnInViewModel, + params = burnInParams, + ) + .element(smallClockElementKey), + ) } @Composable diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt index 9dbeeda42986..1120914fec7c 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt @@ -47,15 +47,16 @@ import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants import com.android.systemui.classifier.FalsingA11yDelegate import com.android.systemui.classifier.FalsingCollector -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlags import com.android.systemui.flags.Flags import com.android.systemui.keyboard.data.repository.FakeKeyboardRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testScope import com.android.systemui.log.SessionTracker @@ -832,7 +833,9 @@ class KeyguardSecurityContainerControllerTest : SysuiTestCase() { // While listening, going from the bouncer scene to the gone scene, does dismiss the // keyguard. - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() fakeSceneDataSource.pause() sceneInteractor.changeScene(Scenes.Gone, "reason") diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt index 16ec9aa897fb..f9f7df820217 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt @@ -122,7 +122,6 @@ class BouncerMessageViewModelTest : SysuiTestCase() { repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { bouncerInteractor.authenticate(WRONG_PIN) } - assertThat(message?.isUpdateAnimated).isFalse() val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0 advanceTimeBy(lockoutEndMs - testScope.currentTime) @@ -133,6 +132,7 @@ class BouncerMessageViewModelTest : SysuiTestCase() { fun lockoutMessage() = testScope.runTest { val message by collectLastValue(underTest.message) + val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin) assertThat(kosmos.fakeAuthenticationRepository.lockoutEndTimestamp).isNull() runCurrent() @@ -140,14 +140,14 @@ class BouncerMessageViewModelTest : SysuiTestCase() { repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times -> bouncerInteractor.authenticate(WRONG_PIN) runCurrent() - if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) { + if (times == FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { + assertTryAgainMessage(message?.text, lockoutSeconds) + assertThat(message?.isUpdateAnimated).isFalse() + } else { assertThat(message?.text).isEqualTo("Wrong PIN. Try again.") assertThat(message?.isUpdateAnimated).isTrue() } } - val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - assertTryAgainMessage(message?.text, lockoutSeconds) - assertThat(message?.isUpdateAnimated).isFalse() repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS) { time -> advanceTimeBy(1.seconds) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt index 71c578545647..c28cf348b9fc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt @@ -27,7 +27,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.inputmethod.data.model.InputMethodModel import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor @@ -153,7 +152,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(Scenes.Bouncer) // No input entered. @@ -329,7 +327,6 @@ class PasswordBouncerViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(Scenes.Bouncer) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt index 51b73ee92df5..14d36343041d 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -373,7 +372,6 @@ class PatternBouncerViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pattern ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(Scenes.Bouncer) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt index 564795429fa6..a0db482cc360 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt @@ -28,7 +28,6 @@ import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository import com.android.systemui.bouncer.domain.interactor.bouncerInteractor import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -387,7 +386,6 @@ class PinBouncerViewModelTest : SysuiTestCase() { private fun TestScope.lockDeviceAndOpenPinBouncer() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) switchToScene(Scenes.Bouncer) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt index 6bff0dc7bd9e..5e120b5f9560 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalPrefsRepositoryImplTest.kt @@ -16,11 +16,15 @@ package com.android.systemui.communal.data.repository +import android.content.Context +import android.content.Intent import android.content.SharedPreferences import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.backup.BackupHelper +import com.android.systemui.broadcast.broadcastDispatcher import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME import com.android.systemui.coroutines.collectLastValue import com.android.systemui.kosmos.testDispatcher @@ -34,16 +38,22 @@ import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.FakeSharedPreferences import com.google.common.truth.Truth.assertThat import java.io.File +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock +import org.mockito.Mockito +import org.mockito.Mockito.atLeastOnce +import org.mockito.Mockito.clearInvocations +import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) -@android.platform.test.annotations.EnabledOnRavenwood class CommunalPrefsRepositoryImplTest : SysuiTestCase() { @Mock private lateinit var tableLogBuffer: TableLogBuffer @@ -69,20 +79,12 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() { USER_INFOS[1].id to FakeSharedPreferences() ) ) - underTest = - CommunalPrefsRepositoryImpl( - testScope.backgroundScope, - kosmos.testDispatcher, - userRepository, - userFileManager, - logcatLogBuffer("CommunalPrefsRepositoryImplTest"), - tableLogBuffer, - ) } @Test fun isCtaDismissedValue_byDefault_isFalse() = testScope.runTest { + underTest = createCommunalPrefsRepositoryImpl(userFileManager) val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) assertThat(isCtaDismissed).isFalse() } @@ -90,6 +92,7 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() { @Test fun isCtaDismissedValue_onSet_isTrue() = testScope.runTest { + underTest = createCommunalPrefsRepositoryImpl(userFileManager) val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) underTest.setCtaDismissedForCurrentUser() @@ -99,6 +102,7 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() { @Test fun isCtaDismissedValue_whenSwitchUser() = testScope.runTest { + underTest = createCommunalPrefsRepositoryImpl(userFileManager) val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) underTest.setCtaDismissedForCurrentUser() @@ -118,6 +122,44 @@ class CommunalPrefsRepositoryImplTest : SysuiTestCase() { assertThat(isCtaDismissed).isTrue() } + @Test + fun getSharedPreferences_whenFileRestored() = + testScope.runTest { + val userFileManagerSpy = Mockito.spy(userFileManager) + underTest = createCommunalPrefsRepositoryImpl(userFileManagerSpy) + + val isCtaDismissed by collectLastValue(underTest.isCtaDismissed) + userRepository.setSelectedUserInfo(USER_INFOS[0]) + assertThat(isCtaDismissed).isFalse() + clearInvocations(userFileManagerSpy) + + // Received restore finished event. + kosmos.broadcastDispatcher.sendIntentToMatchingReceiversOnly( + context, + Intent(BackupHelper.ACTION_RESTORE_FINISHED), + ) + runCurrent() + + // Get shared preferences from the restored file. + verify(userFileManagerSpy, atLeastOnce()) + .getSharedPreferences( + FILE_NAME, + Context.MODE_PRIVATE, + userRepository.getSelectedUserInfo().id + ) + } + + private fun createCommunalPrefsRepositoryImpl(userFileManager: UserFileManager) = + CommunalPrefsRepositoryImpl( + testScope.backgroundScope, + kosmos.testDispatcher, + userRepository, + userFileManager, + kosmos.broadcastDispatcher, + logcatLogBuffer("CommunalPrefsRepositoryImplTest"), + tableLogBuffer, + ) + private class FakeUserFileManager(private val sharedPrefs: Map<Int, SharedPreferences>) : UserFileManager { override fun getFile(fileName: String, userId: Int): File { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt index 9536084fdb23..73373d553560 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt @@ -5,15 +5,11 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.widget.LockPatternUtils import com.android.systemui.SysuiTestCase -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.kosmos.testDispatcher import com.android.systemui.kosmos.testScope import com.android.systemui.statusbar.phone.KeyguardBypassController -import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.FakeUserRepository -import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.whenever import com.android.systemui.util.mockito.withArgCaptor import com.google.common.truth.Truth.assertThat @@ -25,7 +21,6 @@ import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.Mock -import org.mockito.Mockito import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations @@ -37,12 +32,10 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { @Mock private lateinit var lockPatternUtils: LockPatternUtils @Mock private lateinit var keyguardBypassController: KeyguardBypassController - @Mock private lateinit var keyguardStateController: KeyguardStateController private val kosmos = testKosmos() private val testScope = kosmos.testScope private val userRepository = FakeUserRepository() - private val keyguardRepository = FakeKeyguardRepository() private lateinit var underTest: DeviceEntryRepository @@ -59,35 +52,9 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { userRepository = userRepository, lockPatternUtils = lockPatternUtils, keyguardBypassController = keyguardBypassController, - keyguardStateController = keyguardStateController, - keyguardRepository = keyguardRepository, ) testScope.runCurrent() } - - @Test - fun isUnlocked() = - testScope.runTest { - whenever(keyguardStateController.isUnlocked).thenReturn(false) - val isUnlocked by collectLastValue(underTest.isUnlocked) - - runCurrent() - assertThat(isUnlocked).isFalse() - - val captor = argumentCaptor<KeyguardStateController.Callback>() - verify(keyguardStateController, Mockito.atLeastOnce()).addCallback(captor.capture()) - - whenever(keyguardStateController.isUnlocked).thenReturn(true) - captor.value.onUnlockedChanged() - runCurrent() - assertThat(isUnlocked).isTrue() - - whenever(keyguardStateController.isUnlocked).thenReturn(false) - captor.value.onKeyguardShowingChanged() - runCurrent() - assertThat(isUnlocked).isFalse() - } - @Test fun isLockscreenEnabled() = testScope.runTest { @@ -102,17 +69,6 @@ class DeviceEntryRepositoryTest : SysuiTestCase() { } @Test - fun reportSuccessfulAuthentication_updatesIsUnlocked() = - testScope.runTest { - val isUnlocked by collectLastValue(underTest.isUnlocked) - assertThat(isUnlocked).isFalse() - - underTest.reportSuccessfulAuthentication() - - assertThat(isUnlocked).isTrue() - } - - @Test fun isBypassEnabled_disabledInController() = testScope.runTest { whenever(keyguardBypassController.isBypassEnabled).thenAnswer { false } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt index 70ceb2a75d7c..5caf35ba90d4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt @@ -41,8 +41,10 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso import com.android.systemui.flags.fakeSystemPropertiesHelper import com.android.systemui.keyguard.data.repository.fakeBiometricSettingsRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeTrustRepository import com.android.systemui.keyguard.shared.model.AuthenticationFlags +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags @@ -89,22 +91,8 @@ class DeviceEntryInteractorTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None ) - kosmos.fakeDeviceEntryRepository.apply { - setLockscreenEnabled(false) - - // Toggle isUnlocked, twice. - // - // This is done because the underTest.isUnlocked flow doesn't receive values from - // just changing the state above; the actual isUnlocked state needs to change to - // cause the logic under test to "pick up" the current state again. - // - // It is done twice to make sure that we don't actually change the isUnlocked state - // from what it originally was. - setUnlocked(!isUnlocked.value) - runCurrent() - setUnlocked(!isUnlocked.value) - runCurrent() - } + kosmos.fakeDeviceEntryRepository.apply { setLockscreenEnabled(false) } + runCurrent() val isUnlocked by collectLastValue(underTest.isUnlocked) assertThat(isUnlocked).isTrue() @@ -125,7 +113,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Sim ) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) val isUnlocked by collectLastValue(underTest.isUnlocked) assertThat(isUnlocked).isFalse() @@ -271,7 +261,6 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_lockedAndSecured_true() = testScope.runTest { - kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password @@ -283,7 +272,6 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_lockedAndNotSecured_false() = testScope.runTest { - kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None @@ -295,7 +283,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_unlockedAndSecured_false() = testScope.runTest { - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password @@ -307,7 +297,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { @Test fun isAuthenticationRequired_unlockedAndNotSecured_false() = testScope.runTest { - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None @@ -333,7 +325,9 @@ class DeviceEntryInteractorTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() underTest.attemptDeviceEntry() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt index 51db4513bb39..a7a7bea313fe 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorTest.kt @@ -16,6 +16,7 @@ package com.android.systemui.deviceentry.domain.interactor +import android.content.pm.UserInfo import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase @@ -24,16 +25,30 @@ import com.android.systemui.authentication.domain.interactor.authenticationInter import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.data.repository.fakeTrustRepository +import com.android.systemui.keyguard.domain.interactor.trustInteractor +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.testKosmos +import com.android.systemui.user.data.model.SelectionStatus +import com.android.systemui.user.data.repository.fakeUserRepository 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) -@android.platform.test.annotations.EnabledOnRavenwood class DeviceUnlockedInteractorTest : SysuiTestCase() { private val kosmos = testKosmos() @@ -46,71 +61,170 @@ class DeviceUnlockedInteractorTest : SysuiTestCase() { applicationScope = testScope.backgroundScope, authenticationInteractor = kosmos.authenticationInteractor, deviceEntryRepository = deviceEntryRepository, + trustInteractor = kosmos.trustInteractor, + faceAuthInteractor = kosmos.deviceEntryFaceAuthInteractor, + fingerprintAuthInteractor = kosmos.deviceEntryFingerprintAuthInteractor, + powerInteractor = kosmos.powerInteractor, ) + @Before + fun setup() { + kosmos.fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser)) + } + @Test - fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsNone_isTrue() = + fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsNone_isTrue() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isDeviceUnlocked) + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) - deviceEntryRepository.setUnlocked(true) authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - assertThat(isUnlocked).isTrue() + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + assertThat(deviceUnlockStatus?.deviceUnlockSource).isNull() } @Test - fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsPin_isTrue() = + fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsPin_isTrue() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isDeviceUnlocked) + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) - deviceEntryRepository.setUnlocked(true) authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - - assertThat(isUnlocked).isTrue() + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + assertThat(deviceUnlockStatus?.deviceUnlockSource) + .isEqualTo(DeviceUnlockSource.Fingerprint) } @Test - fun isDeviceUnlocked_whenUnlockedAndAuthMethodIsSim_isFalse() = + fun deviceUnlockStatus_whenUnlockedAndAuthMethodIsSim_isFalse() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isDeviceUnlocked) + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) - deviceEntryRepository.setUnlocked(true) authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() - assertThat(isUnlocked).isFalse() + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() } @Test - fun isDeviceUnlocked_whenLockedAndAuthMethodIsNone_isTrue() = + fun deviceUnlockStatus_whenLockedAndAuthMethodIsNone_isTrue() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isDeviceUnlocked) + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) - deviceEntryRepository.setUnlocked(false) authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None) - assertThat(isUnlocked).isTrue() + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() } @Test - fun isDeviceUnlocked_whenLockedAndAuthMethodIsPin_isFalse() = + fun deviceUnlockStatus_whenLockedAndAuthMethodIsPin_isFalse() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isDeviceUnlocked) + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) - deviceEntryRepository.setUnlocked(false) authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin) - assertThat(isUnlocked).isFalse() + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() } @Test - fun isDeviceUnlocked_whenLockedAndAuthMethodIsSim_isFalse() = + fun deviceUnlockStatus_whenLockedAndAuthMethodIsSim_isFalse() = testScope.runTest { - val isUnlocked by collectLastValue(underTest.isDeviceUnlocked) + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) - deviceEntryRepository.setUnlocked(false) authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Sim) - assertThat(isUnlocked).isFalse() + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() } + + @Test + fun deviceUnlockStatus_whenFaceIsAuthenticatedWhileAwakeWithBypass_isTrue() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + kosmos.powerInteractor.setAwakeForTest() + + kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true + kosmos.fakeDeviceEntryRepository.setBypassEnabled(true) + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + assertThat(deviceUnlockStatus?.deviceUnlockSource) + .isEqualTo(DeviceUnlockSource.FaceWithBypass) + } + + @Test + fun deviceUnlockStatus_whenFaceIsAuthenticatedWithoutBypass_providesThatInfo() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFaceAuthRepository.isAuthenticated.value = true + kosmos.fakeDeviceEntryRepository.setBypassEnabled(false) + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + assertThat(deviceUnlockStatus?.deviceUnlockSource) + .isEqualTo(DeviceUnlockSource.FaceWithoutBypass) + } + + @Test + fun deviceUnlockStatus_whenFingerprintIsAuthenticated_providesThatInfo() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + assertThat(deviceUnlockStatus?.deviceUnlockSource) + .isEqualTo(DeviceUnlockSource.Fingerprint) + } + + @Test + fun deviceUnlockStatus_whenUnlockedByTrustAgent_providesThatInfo() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + kosmos.fakeUserRepository.setSelectedUserInfo( + primaryUser, + SelectionStatus.SELECTION_COMPLETE + ) + + kosmos.fakeTrustRepository.setCurrentUserTrusted(true) + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + assertThat(deviceUnlockStatus?.deviceUnlockSource) + .isEqualTo(DeviceUnlockSource.TrustAgent) + } + + @Test + fun deviceUnlockStatus_isResetToFalse_whenDeviceGoesToSleep() = + testScope.runTest { + val deviceUnlockStatus by collectLastValue(underTest.deviceUnlockStatus) + + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + assertThat(deviceUnlockStatus?.isUnlocked).isTrue() + + kosmos.powerInteractor.setAsleepForTest() + runCurrent() + + assertThat(deviceUnlockStatus?.isUnlocked).isFalse() + } + + companion object { + private const val primaryUserId = 1 + private val primaryUser = UserInfo(primaryUserId, "test user", UserInfo.FLAG_PRIMARY) + + private val secondaryUser = UserInfo(2, "secondary user", 0) + } } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java index 6a86801cba90..0f8fc3824e3f 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java @@ -63,7 +63,6 @@ import org.mockito.MockitoAnnotations; import java.util.Collections; import java.util.Optional; - @SmallTest @RunWith(AndroidJUnit4.class) public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { @@ -119,6 +118,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { private static final float TOUCH_REGION = .3f; private static final int SCREEN_WIDTH_PX = 1024; private static final int SCREEN_HEIGHT_PX = 100; + private static final float MIN_BOUNCER_HEIGHT = .05f; private static final Rect SCREEN_BOUNDS = new Rect(0, 0, 1024, 100); private static final UserInfo CURRENT_USER_INFO = new UserInfo( @@ -142,6 +142,7 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { mFlingAnimationUtils, mFlingAnimationUtilsClosing, TOUCH_REGION, + MIN_BOUNCER_HEIGHT, mUiEventLogger); when(mScrimManager.getCurrentController()).thenReturn(mScrimController); @@ -160,9 +161,9 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { */ @Test public void testSessionStart() { - mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion); + mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, null); - verify(mRegion).op(mRectCaptor.capture(), eq(Region.Op.UNION)); + verify(mRegion).union(mRectCaptor.capture()); final Rect bounds = mRectCaptor.getValue(); final Rect expected = new Rect(); @@ -194,6 +195,85 @@ public class BouncerSwipeTouchHandlerTest extends SysuiTestCase { UP, } + @Test + public void testSwipeUp_whenBouncerInitiallyShowing_reduceHeightWithExclusionRects() { + mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, + new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX)); + verify(mRegion).union(mRectCaptor.capture()); + final Rect bounds = mRectCaptor.getValue(); + + final Rect expected = new Rect(); + final float minBouncerHeight = + SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT; + final int minAllowableBottom = SCREEN_HEIGHT_PX - Math.round(minBouncerHeight); + + expected.set(0, minAllowableBottom , SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX); + + assertThat(bounds).isEqualTo(expected); + + onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController); + } + + @Test + public void testSwipeUp_exclusionRectAtTop_doesNotIntersectGestureArea() { + mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, + new Rect(0, 0, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX / 4)); + verify(mRegion).union(mRectCaptor.capture()); + final Rect bounds = mRectCaptor.getValue(); + + final Rect expected = new Rect(); + final int gestureAreaTop = SCREEN_HEIGHT_PX - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION); + expected.set(0, gestureAreaTop, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX); + + assertThat(bounds).isEqualTo(expected); + onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController); + } + + @Test + public void testSwipeUp_exclusionRectBetweenNormalAndMinimumSwipeArea() { + final int normalSwipeAreaTop = SCREEN_HEIGHT_PX + - Math.round(SCREEN_HEIGHT_PX * TOUCH_REGION); + final int minimumSwipeAreaTop = SCREEN_HEIGHT_PX + - Math.round(SCREEN_HEIGHT_PX * MIN_BOUNCER_HEIGHT); + + Rect exclusionRect = new Rect(0, 0, SCREEN_WIDTH_PX, + (normalSwipeAreaTop + minimumSwipeAreaTop) / 2); + + mTouchHandler.getTouchInitiationRegion(SCREEN_BOUNDS, mRegion, exclusionRect); + + verify(mRegion).union(mRectCaptor.capture()); + + final Rect bounds = mRectCaptor.getValue(); + final Rect expected = new Rect(); + + final int expectedSwipeAreaBottom = exclusionRect.bottom; + expected.set(0, expectedSwipeAreaBottom, SCREEN_WIDTH_PX, SCREEN_HEIGHT_PX); + + assertThat(bounds).isEqualTo(expected); + + onSessionStartHelper(mTouchHandler, mTouchSession, mNotificationShadeWindowController); + } + + private static void onSessionStartHelper(BouncerSwipeTouchHandler touchHandler, + DreamTouchHandler.TouchSession touchSession, + NotificationShadeWindowController notificationShadeWindowController) { + touchHandler.onSessionStart(touchSession); + verify(notificationShadeWindowController).setForcePluginOpen(eq(true), any()); + ArgumentCaptor<InputChannelCompat.InputEventListener> eventListenerCaptor = + ArgumentCaptor.forClass(InputChannelCompat.InputEventListener.class); + ArgumentCaptor<GestureDetector.OnGestureListener> gestureListenerCaptor = + ArgumentCaptor.forClass(GestureDetector.OnGestureListener.class); + verify(touchSession).registerGestureListener(gestureListenerCaptor.capture()); + verify(touchSession).registerInputListener(eventListenerCaptor.capture()); + + // A touch within range at the bottom of the screen should trigger listening + assertThat(gestureListenerCaptor.getValue() + .onScroll(Mockito.mock(MotionEvent.class), + Mockito.mock(MotionEvent.class), + 1, + 2)).isTrue(); + } + /** * Makes sure swiping up when bouncer initially showing doesn't change the expansion amount. */ diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt index 056a401a2644..0f5e45814608 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractorTest.kt @@ -37,14 +37,17 @@ package com.android.systemui.keyguard.domain.interactor import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.coroutines.collectLastValue import com.android.systemui.coroutines.collectValues -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.data.repository.keyguardOcclusionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.kosmos.testScope import com.android.systemui.power.domain.interactor.PowerInteractor @@ -247,14 +250,20 @@ class KeyguardOcclusionInteractorTest : SysuiTestCase() { val occludingActivityWillDismissKeyguard by collectLastValue(underTest.occludingActivityWillDismissKeyguard) assertThat(occludingActivityWillDismissKeyguard).isFalse() + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + runCurrent() // Unlock device: - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() assertThat(occludingActivityWillDismissKeyguard).isTrue() // Re-lock device: - kosmos.fakeDeviceEntryRepository.setUnlocked(false) + kosmos.powerInteractor.setAsleepForTest() runCurrent() assertThat(occludingActivityWillDismissKeyguard).isFalse() } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt index 2fd2ef1f3240..66f7e015a133 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt @@ -121,7 +121,6 @@ class LockscreenSceneViewModelTest : SysuiTestCase() { AuthenticationMethodModel.Pin } ) - kosmos.fakeDeviceEntryRepository.setUnlocked(canSwipeToEnter) sceneInteractor.changeScene(Scenes.Lockscreen, "reason") kosmos.shadeRepository.setShadeMode( if (isSingleShade) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt new file mode 100644 index 000000000000..d0cd56fce778 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileDataInteractorTest.kt @@ -0,0 +1,102 @@ +/* + * 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.sensorprivacy.domain.interactor + +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE +import android.os.UserHandle +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.Kosmos +import com.android.systemui.kosmos.testScope +import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger +import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController +import com.android.systemui.util.mockito.argumentCaptor +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.toCollection +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.verify + +@OptIn(ExperimentalCoroutinesApi::class) +@SmallTest +@RunWith(AndroidJUnit4::class) +class SensorPrivacyToggleTileDataInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val testScope = kosmos.testScope + private val mockSensorPrivacyController = + mock<IndividualSensorPrivacyController> { + whenever(isSensorBlocked(eq(CAMERA))).thenReturn(false) // determines initial value + } + private val testUser = UserHandle.of(1) + private val underTest = + SensorPrivacyToggleTileDataInteractor( + testScope.testScheduler, + mockSensorPrivacyController, + CAMERA + ) + + @Test + fun availability_isTrue() = + testScope.runTest { + whenever(mockSensorPrivacyController.supportsSensorToggle(eq(CAMERA))).thenReturn(true) + + val availability = underTest.availability(testUser).toCollection(mutableListOf()) + runCurrent() + + assertThat(availability).hasSize(1) + assertThat(availability.last()).isTrue() + } + + @Test + fun tileData_matchesPrivacyControllerIsSensorBlocked() = + testScope.runTest { + val callbackCaptor = argumentCaptor<IndividualSensorPrivacyController.Callback>() + val data by + collectLastValue( + underTest.tileData(testUser, flowOf(DataUpdateTrigger.InitialRequest)) + ) + runCurrent() + verify(mockSensorPrivacyController).addCallback(callbackCaptor.capture()) + val callback = callbackCaptor.value + + runCurrent() + assertThat(data!!.isBlocked).isFalse() + + callback.onSensorBlockedChanged(CAMERA, true) + runCurrent() + assertThat(data!!.isBlocked).isTrue() + + callback.onSensorBlockedChanged(CAMERA, false) + runCurrent() + assertThat(data!!.isBlocked).isFalse() + + callback.onSensorBlockedChanged(MICROPHONE, true) + runCurrent() + assertThat(data!!.isBlocked).isFalse() // We're NOT listening for MIC sensor changes + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt new file mode 100644 index 000000000000..562e6ebcc029 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/interactor/SensorPrivacyToggleTileUserActionInteractorTest.kt @@ -0,0 +1,168 @@ +/* + * 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.sensorprivacy.domain.interactor + +import android.hardware.SensorPrivacyManager +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE +import android.provider.Settings +import android.safetycenter.SafetyCenterManager +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.filters.SmallTest +import com.android.systemui.SysuiTestCase +import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository +import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.plugins.activityStarter +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.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController +import com.android.systemui.util.mockito.any +import com.android.systemui.util.mockito.mock +import com.android.systemui.util.mockito.whenever +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.test.runTest +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mockito.never +import org.mockito.Mockito.verify + +@SmallTest +@RunWith(AndroidJUnit4::class) +class SensorPrivacyToggleTileUserActionInteractorTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val inputHandler = FakeQSTileIntentUserInputHandler() + private val keyguardInteractor = kosmos.keyguardInteractor + // The keyguard repository below is the same one kosmos used to create the interactor above + private val keyguardRepository = kosmos.fakeKeyguardRepository + private val mockActivityStarter = kosmos.activityStarter + private val mockSensorPrivacyController = mock<IndividualSensorPrivacyController>() + private val fakeSafetyCenterManager = mock<SafetyCenterManager>() + + private val underTest = + SensorPrivacyToggleTileUserActionInteractor( + inputHandler, + keyguardInteractor, + mockActivityStarter, + mockSensorPrivacyController, + fakeSafetyCenterManager, + CAMERA + ) + + @Test + fun handleClickWhenNotBlocked() = runTest { + val originalIsBlocked = false + + underTest.handleInput( + QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked)) + ) + + verify(mockSensorPrivacyController) + .setSensorBlocked( + eq(SensorPrivacyManager.Sources.QS_TILE), + eq(CAMERA), + eq(!originalIsBlocked) + ) + } + + @Test + fun handleClickWhenBlocked() = runTest { + val originalIsBlocked = true + + underTest.handleInput( + QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked)) + ) + + verify(mockSensorPrivacyController) + .setSensorBlocked( + eq(SensorPrivacyManager.Sources.QS_TILE), + eq(CAMERA), + eq(!originalIsBlocked) + ) + } + + @Test + fun handleClick_whenKeyguardIsDismissableAndShowing_whenControllerRequiresAuth() = runTest { + whenever(mockSensorPrivacyController.requiresAuthentication()).thenReturn(true) + keyguardRepository.setKeyguardDismissible(true) + keyguardRepository.setKeyguardShowing(true) + val originalIsBlocked = true + + underTest.handleInput( + QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(originalIsBlocked)) + ) + + verify(mockSensorPrivacyController, never()) + .setSensorBlocked( + eq(SensorPrivacyManager.Sources.QS_TILE), + eq(CAMERA), + eq(!originalIsBlocked) + ) + verify(mockActivityStarter).postQSRunnableDismissingKeyguard(any()) + } + + @Test + fun handleLongClick_whenSafetyManagerEnabled_privacyControlsIntent() = runTest { + whenever(fakeSafetyCenterManager.isSafetyCenterEnabled).thenReturn(true) + + underTest.handleInput(QSTileInputTestKtx.longClick(SensorPrivacyToggleTileModel(false))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_PRIVACY_CONTROLS) + } + } + + @Test + fun handleLongClick_whenSafetyManagerDisabled_privacySettingsIntent() = runTest { + whenever(fakeSafetyCenterManager.isSafetyCenterEnabled).thenReturn(false) + + underTest.handleInput(QSTileInputTestKtx.longClick(SensorPrivacyToggleTileModel(false))) + + QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput { + assertThat(it.intent.action).isEqualTo(Settings.ACTION_PRIVACY_SETTINGS) + } + } + + @Test + fun handleClick_microphone_flipsController() = runTest { + val micUserActionInteractor = + SensorPrivacyToggleTileUserActionInteractor( + inputHandler, + keyguardInteractor, + mockActivityStarter, + mockSensorPrivacyController, + fakeSafetyCenterManager, + MICROPHONE + ) + + micUserActionInteractor.handleInput( + QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(false)) + ) + verify(mockSensorPrivacyController) + .setSensorBlocked(eq(SensorPrivacyManager.Sources.QS_TILE), eq(MICROPHONE), eq(true)) + + micUserActionInteractor.handleInput( + QSTileInputTestKtx.click(SensorPrivacyToggleTileModel(true)) + ) + verify(mockSensorPrivacyController) + .setSensorBlocked(eq(SensorPrivacyManager.Sources.QS_TILE), eq(MICROPHONE), eq(false)) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt new file mode 100644 index 000000000000..5e7aadcda6db --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapperTest.kt @@ -0,0 +1,161 @@ +/* + * 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.sensorprivacy.ui + +import android.graphics.drawable.TestStubDrawable +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE +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.sensorprivacy.domain.model.SensorPrivacyToggleTileModel +import com.android.systemui.qs.tiles.impl.sensorprivacy.qsCameraSensorPrivacyToggleTileConfig +import com.android.systemui.qs.tiles.impl.sensorprivacy.qsMicrophoneSensorPrivacyToggleTileConfig +import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources.CameraPrivacyTileResources +import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources.MicrophonePrivacyTileResources +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 SensorPrivacyToggleTileMapperTest : SysuiTestCase() { + private val kosmos = Kosmos() + private val cameraConfig = kosmos.qsCameraSensorPrivacyToggleTileConfig + private val micConfig = kosmos.qsMicrophoneSensorPrivacyToggleTileConfig + + @Test + fun mapCamera_notBlocked() { + val mapper = createMapper(CameraPrivacyTileResources) + val inputModel = SensorPrivacyToggleTileModel(false) + + val outputState = mapper.map(cameraConfig, inputModel) + + val expectedState = + createSensorPrivacyToggleTileState( + QSTileState.ActivationState.ACTIVE, + context.getString(R.string.quick_settings_camera_mic_available), + R.drawable.qs_camera_access_icon_on, + null, + CAMERA + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun mapCamera_blocked() { + val mapper = createMapper(CameraPrivacyTileResources) + val inputModel = SensorPrivacyToggleTileModel(true) + + val outputState = mapper.map(cameraConfig, inputModel) + + val expectedState = + createSensorPrivacyToggleTileState( + QSTileState.ActivationState.INACTIVE, + context.getString(R.string.quick_settings_camera_mic_blocked), + R.drawable.qs_camera_access_icon_off, + null, + CAMERA + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun mapMic_notBlocked() { + val mapper = createMapper(MicrophonePrivacyTileResources) + val inputModel = SensorPrivacyToggleTileModel(false) + + val outputState = mapper.map(micConfig, inputModel) + + val expectedState = + createSensorPrivacyToggleTileState( + QSTileState.ActivationState.ACTIVE, + context.getString(R.string.quick_settings_camera_mic_available), + R.drawable.qs_mic_access_on, + null, + MICROPHONE + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + @Test + fun mapMic_blocked() { + val mapper = createMapper(MicrophonePrivacyTileResources) + val inputModel = SensorPrivacyToggleTileModel(true) + + val outputState = mapper.map(micConfig, inputModel) + + val expectedState = + createSensorPrivacyToggleTileState( + QSTileState.ActivationState.INACTIVE, + context.getString(R.string.quick_settings_camera_mic_blocked), + R.drawable.qs_mic_access_off, + null, + MICROPHONE + ) + QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState) + } + + private fun createMapper( + sensorResources: SensorPrivacyTileResources + ): SensorPrivacyToggleTileMapper { + val mapper = + SensorPrivacyToggleTileMapper( + context.orCreateTestableResources + .apply { + addOverride(R.drawable.qs_camera_access_icon_off, TestStubDrawable()) + addOverride(R.drawable.qs_camera_access_icon_on, TestStubDrawable()) + addOverride(R.drawable.qs_mic_access_off, TestStubDrawable()) + addOverride(R.drawable.qs_mic_access_on, TestStubDrawable()) + } + .resources, + context.theme, + sensorResources, + ) + return mapper + } + + private fun createSensorPrivacyToggleTileState( + activationState: QSTileState.ActivationState, + secondaryLabel: String, + iconRes: Int, + stateDescription: CharSequence?, + sensorId: Int, + ): QSTileState { + val label = + if (sensorId == CAMERA) context.getString(R.string.quick_settings_camera_label) + else context.getString(R.string.quick_settings_mic_label) + + return QSTileState( + { Icon.Loaded(context.getDrawable(iconRes)!!, null) }, + label, + activationState, + secondaryLabel, + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK), + label, + stateDescription, + QSTileState.SideViewIcon.None, + QSTileState.EnabledState.ENABLED, + Switch::class.qualifiedName + ) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt index fd685192fdab..98cbda2efd2b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt @@ -50,6 +50,7 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.domain.interactor.keyguardInteractor @@ -265,8 +266,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { sceneInteractor = sceneInteractor, ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) - val displayTracker = FakeDisplayTracker(context) val sysUiState = SysUiState(displayTracker, kosmos.sceneContainerPlugin) val startable = @@ -291,6 +290,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { headsUpInteractor = kosmos.headsUpNotificationInteractor, occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor, + deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor, ) startable.start() @@ -371,8 +371,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fun swipeUpOnShadeScene_withAuthMethodSwipe_lockscreenDismissed_goesToGone() = testScope.runTest { val destinationScenes by collectLastValue(shadeSceneViewModel.destinationScenes) + val canSwipeToEnter by collectLastValue(deviceEntryInteractor.canSwipeToEnter) + setAuthMethod(AuthenticationMethodModel.None, enableLockscreen = true) - assertThat(deviceEntryInteractor.canSwipeToEnter.value).isTrue() + + assertThat(canSwipeToEnter).isTrue() assertCurrentScene(Scenes.Lockscreen) // Emulate a user swipe to dismiss the lockscreen. @@ -437,17 +440,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { } @Test - fun deviceWakesUpWhileUnlocked_dismissesLockscreen() = - testScope.runTest { - unlockDevice() - assertCurrentScene(Scenes.Gone) - putDeviceToSleep(instantlyLockDevice = false) - assertCurrentScene(Scenes.Lockscreen) - wakeUpDevice() - assertCurrentScene(Scenes.Gone) - } - - @Test fun swipeUpOnLockscreenWhileUnlocked_dismissesLockscreen() = testScope.runTest { unlockDevice() @@ -540,7 +532,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { fakeSceneDataSource.pause() introduceLockedSim() emulatePendingTransitionProgress(expectedVisible = true) - enterSimPin(authMethodAfterSimUnlock = AuthenticationMethodModel.None) + enterSimPin( + authMethodAfterSimUnlock = AuthenticationMethodModel.None, + enableLockscreen = false + ) + assertCurrentScene(Scenes.Gone) } @@ -708,7 +704,6 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { .that(authMethod.isSecure) .isTrue() - kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() } @@ -721,9 +716,7 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { emulateUserDrivenTransition(Scenes.Bouncer) fakeSceneDataSource.pause() enterPin() - // This repository state is not changed by the AuthInteractor, it relies on - // KeyguardStateController. - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + emulatePendingTransitionProgress( expectedVisible = false, ) @@ -763,7 +756,8 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { * Does not assert that the device is locked or unlocked. */ private fun TestScope.enterSimPin( - authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None + authMethodAfterSimUnlock: AuthenticationMethodModel = AuthenticationMethodModel.None, + enableLockscreen: Boolean = true, ) { assertWithMessage("Cannot enter PIN when not on the Bouncer scene!") .that(getCurrentSceneInUi()) @@ -778,9 +772,11 @@ class SceneFrameworkIntegrationTest : SysuiTestCase() { pinBouncerViewModel.onPinButtonClicked(digit) } pinBouncerViewModel.onAuthenticateButtonClicked() - setAuthMethod(authMethodAfterSimUnlock) kosmos.fakeMobileConnectionsRepository.isAnySimSecure.value = false runCurrent() + + setAuthMethod(authMethodAfterSimUnlock, enableLockscreen) + runCurrent() } /** Changes device wakefulness state from asleep to awake, going through intermediary states. */ diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt index f645f1cc4369..143c0f277209 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/interactor/SceneInteractorTest.kt @@ -23,7 +23,8 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.data.repository.sceneContainerRepository import com.android.systemui.scene.sceneContainerConfig @@ -79,7 +80,9 @@ class SceneInteractorTest : SysuiTestCase() { val currentScene by collectLastValue(underTest.currentScene) assertThat(currentScene).isEqualTo(Scenes.Lockscreen) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() underTest.changeScene(Scenes.Gone, "reason") @@ -88,11 +91,7 @@ class SceneInteractorTest : SysuiTestCase() { @Test(expected = IllegalStateException::class) fun changeScene_toGoneWhenStillLocked_throws() = - testScope.runTest { - kosmos.fakeDeviceEntryRepository.setUnlocked(false) - - underTest.changeScene(Scenes.Gone, "reason") - } + testScope.runTest { underTest.changeScene(Scenes.Gone, "reason") } @Test fun sceneChanged_inDataSource() = diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index e330a359add5..3fd5306abb71 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -39,9 +39,13 @@ import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.keyguard.data.repository.deviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFaceAuthRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.domain.interactor.keyguardInteractor +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.model.sysUiState import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest @@ -135,6 +139,7 @@ class SceneContainerStartableTest : SysuiTestCase() { headsUpInteractor = kosmos.headsUpNotificationInteractor, occlusionInteractor = kosmos.sceneContainerOcclusionInteractor, faceUnlockInteractor = kosmos.deviceEntryFaceAuthInteractor, + deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor, ) } @@ -145,6 +150,7 @@ class SceneContainerStartableTest : SysuiTestCase() { val isVisible by collectLastValue(sceneInteractor.isVisible) val transitionStateFlow = prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = true, initialSceneKey = Scenes.Gone, ) @@ -198,6 +204,7 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val isVisible by collectLastValue(sceneInteractor.isVisible) prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = true, initialSceneKey = Scenes.Lockscreen, isDeviceProvisioned = false, @@ -249,14 +256,14 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = true, initialSceneKey = Scenes.Gone, + startsAwake = false, ) assertThat(currentSceneKey).isEqualTo(Scenes.Gone) underTest.start() - kosmos.fakeDeviceEntryRepository.setUnlocked(false) - assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @@ -265,13 +272,16 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = false, initialSceneKey = Scenes.Bouncer, ) assertThat(currentSceneKey).isEqualTo(Scenes.Bouncer) underTest.start() - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @@ -281,13 +291,16 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, isBypassEnabled = true, initialSceneKey = Scenes.Lockscreen, ) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @@ -305,7 +318,6 @@ class SceneContainerStartableTest : SysuiTestCase() { // Authenticate using a passive auth method like face auth while bypass is disabled. faceAuthRepository.isAuthenticated.value = true - kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) } @@ -327,7 +339,9 @@ class SceneContainerStartableTest : SysuiTestCase() { transitionStateFlowValue.value = ObservableTransitionState.Idle(Scenes.Shade) assertThat(currentSceneKey).isEqualTo(Scenes.Shade) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() assertThat(currentSceneKey).isEqualTo(Scenes.Shade) @@ -346,7 +360,6 @@ class SceneContainerStartableTest : SysuiTestCase() { // Authenticate using a passive auth method like face auth while bypass is disabled. faceAuthRepository.isAuthenticated.value = true - kosmos.fakeDeviceEntryRepository.setUnlocked(true) assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @@ -383,7 +396,9 @@ class SceneContainerStartableTest : SysuiTestCase() { ) .forEachIndexed { index, sceneKey -> if (sceneKey == Scenes.Gone) { - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() } fakeSceneDataSource.pause() @@ -453,6 +468,7 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() powerInteractor.setAwakeForTest() + runCurrent() assertThat(currentSceneKey).isEqualTo(Scenes.Gone) } @@ -529,7 +545,9 @@ class SceneContainerStartableTest : SysuiTestCase() { assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) underTest.start() - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() powerInteractor.setAwakeForTest() runCurrent() @@ -564,7 +582,9 @@ class SceneContainerStartableTest : SysuiTestCase() { } // Changing to the Gone scene should report a successful unlock. - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() sceneInteractor.changeScene(Scenes.Gone, "reason") runCurrent() @@ -759,7 +779,9 @@ class SceneContainerStartableTest : SysuiTestCase() { runCurrent() verify(falsingCollector).onBouncerShown() - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() sceneInteractor.changeScene(Scenes.Gone, "reason") runCurrent() @@ -830,6 +852,7 @@ class SceneContainerStartableTest : SysuiTestCase() { val currentDesiredSceneKey by collectLastValue(sceneInteractor.currentScene) val transitionStateFlow = prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = true, initialSceneKey = Scenes.Gone, ) @@ -985,6 +1008,7 @@ class SceneContainerStartableTest : SysuiTestCase() { testScope.runTest { val transitionStateFlow = prepareState( + authenticationMethod = AuthenticationMethodModel.Pin, isDeviceUnlocked = true, initialSceneKey = Scenes.Gone, ) @@ -1147,6 +1171,11 @@ class SceneContainerStartableTest : SysuiTestCase() { assert(isLockscreenEnabled) { "Lockscreen cannot be disabled while having a secure authentication method" } + if (isDeviceUnlocked) { + kosmos.deviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + } } check(initialSceneKey != Scenes.Gone || isDeviceUnlocked) { @@ -1154,8 +1183,13 @@ class SceneContainerStartableTest : SysuiTestCase() { } sceneContainerFlags.enabled = true - kosmos.fakeDeviceEntryRepository.setUnlocked(isDeviceUnlocked) kosmos.fakeDeviceEntryRepository.setBypassEnabled(isBypassEnabled) + authenticationMethod?.let { + kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod) + kosmos.fakeDeviceEntryRepository.setLockscreenEnabled( + isLockscreenEnabled = isLockscreenEnabled + ) + } runCurrent() val transitionStateFlow = MutableStateFlow<ObservableTransitionState>( @@ -1166,12 +1200,6 @@ class SceneContainerStartableTest : SysuiTestCase() { transitionStateFlow.value = ObservableTransitionState.Idle(it) sceneInteractor.changeScene(it, "reason") } - authenticationMethod?.let { - kosmos.fakeAuthenticationRepository.setAuthenticationMethod(authenticationMethod) - kosmos.fakeDeviceEntryRepository.setLockscreenEnabled( - isLockscreenEnabled = isLockscreenEnabled - ) - } if (startsAwake) { powerInteractor.setAwakeForTest() } else { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt index cd79ed1a8965..cbbcce96873b 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ShadeControllerSceneImplTest.kt @@ -21,10 +21,11 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.testCase import com.android.systemui.kosmos.testScope @@ -38,6 +39,7 @@ import com.android.systemui.util.mockito.mock import com.google.common.truth.Truth.assertThat import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Before @@ -53,7 +55,7 @@ import org.mockito.Mockito.verify class ShadeControllerSceneImplTest : SysuiTestCase() { private val kosmos = Kosmos() private val testScope = kosmos.testScope - private val sceneInteractor = kosmos.sceneInteractor + private val sceneInteractor by lazy { kosmos.sceneInteractor } private val deviceEntryInteractor by lazy { kosmos.deviceEntryInteractor } private lateinit var shadeInteractor: ShadeInteractor @@ -68,7 +70,9 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { set(Flags.NSSL_DEBUG_LINES, false) set(Flags.FULL_SCREEN_USER_SWITCHER, false) } - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) testScope.runCurrent() shadeInteractor = kosmos.shadeInteractor underTest = kosmos.shadeControllerSceneImpl @@ -161,6 +165,10 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { testScope.runTest { // GIVEN shade is collapsed and a post-collapse action is enqueued val testRunnable = mock<Runnable>() + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() setCollapsed() underTest.postOnShadeExpanded(testRunnable) @@ -179,7 +187,14 @@ class ShadeControllerSceneImplTest : SysuiTestCase() { testScope.runCurrent() } - private fun setDeviceEntered(isEntered: Boolean) { + private fun TestScope.setDeviceEntered(isEntered: Boolean) { + if (isEntered) { + // Unlock the device marking the device has entered. + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + } setScene( if (isEntered) { Scenes.Gone diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt index ad40f8eab4f6..6485c475eeb7 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/PanelExpansionInteractorImplTest.kt @@ -24,9 +24,12 @@ import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor +import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource +import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -49,7 +52,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { private val kosmos = testKosmos() private val testScope = kosmos.testScope - private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository private val deviceUnlockedInteractor = kosmos.deviceUnlockedInteractor private val sceneInteractor = kosmos.sceneInteractor private val shadeAnimationInteractor = kosmos.shadeAnimationInteractor @@ -71,7 +73,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { fun legacyPanelExpansion_whenIdle_whenLocked() = testScope.runTest { underTest = kosmos.panelExpansionInteractorImpl - setUnlocked(false) val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) changeScene(Scenes.Lockscreen) { assertThat(panelExpansion).isEqualTo(1f) } @@ -95,7 +96,15 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { fun legacyPanelExpansion_whenIdle_whenUnlocked() = testScope.runTest { underTest = kosmos.panelExpansionInteractorImpl - setUnlocked(true) + val unlockStatus by collectLastValue(deviceUnlockedInteractor.deviceUnlockStatus) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() + + assertThat(unlockStatus) + .isEqualTo(DeviceUnlockStatus(true, DeviceUnlockSource.Fingerprint)) + val panelExpansion by collectLastValue(underTest.legacyPanelExpansion) changeScene(Scenes.Gone) { assertThat(panelExpansion).isEqualTo(0f) } @@ -147,14 +156,6 @@ class PanelExpansionInteractorImplTest : SysuiTestCase() { assertThat(underTest.shouldHideStatusBarIconsWhenExpanded()).isFalse() } - private fun TestScope.setUnlocked(isUnlocked: Boolean) { - val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked) - deviceEntryRepository.setUnlocked(isUnlocked) - runCurrent() - - assertThat(isDeviceUnlocked).isEqualTo(isUnlocked) - } - private fun TestScope.changeScene( toScene: SceneKey, assertDuringProgress: ((progress: Float) -> Unit) = {}, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt index d309c6bcfd58..e759b504d5c3 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/interactor/ShadeBackActionInteractorImplTest.kt @@ -22,7 +22,6 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.kosmos.testScope import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.flag.fakeSceneContainerFlags @@ -48,7 +47,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() { val kosmos = testKosmos().apply { fakeSceneContainerFlags.enabled = true } val testScope = kosmos.testScope val sceneInteractor = kosmos.sceneInteractor - val deviceEntryRepository = kosmos.fakeDeviceEntryRepository val underTest = kosmos.shadeBackActionInteractor @Before @@ -78,7 +76,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() { @Test fun animateCollapseQs_fullyCollapse_locked() = testScope.runTest { - deviceEntryRepository.setUnlocked(false) setScene(Scenes.QuickSettings) underTest.animateCollapseQs(true) runCurrent() @@ -95,7 +92,6 @@ class ShadeBackActionInteractorImplTest : SysuiTestCase() { } private fun enterDevice() { - deviceEntryRepository.setUnlocked(true) testScope.runCurrent() setScene(Scenes.Gone) } diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt index 52caa787bb2f..2ab934c2386e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/domain/startable/ShadeStartableTest.kt @@ -21,11 +21,15 @@ import androidx.test.filters.SmallTest import com.android.compose.animation.scene.ObservableTransitionState import com.android.compose.animation.scene.SceneKey import com.android.systemui.SysuiTestCase +import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository +import com.android.systemui.authentication.shared.model.AuthenticationMethodModel import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository import com.android.systemui.coroutines.collectLastValue import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.EnableSceneContainer +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -40,6 +44,7 @@ import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.mock import com.android.systemui.util.mockito.whenever import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.TestScope @@ -48,6 +53,7 @@ import kotlinx.coroutines.test.runTest import org.junit.Test import org.junit.runner.RunWith +@OptIn(ExperimentalCoroutinesApi::class) @SmallTest @RunWith(AndroidJUnit4::class) class ShadeStartableTest : SysuiTestCase() { @@ -95,7 +101,14 @@ class ShadeStartableTest : SysuiTestCase() { underTest.start() - setUnlocked(true) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( + AuthenticationMethodModel.Pin + ) + runCurrent() + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) + runCurrent() val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(Scenes.Gone) @@ -120,14 +133,6 @@ class ShadeStartableTest : SysuiTestCase() { } } - private fun TestScope.setUnlocked(isUnlocked: Boolean) { - val isDeviceUnlocked by collectLastValue(deviceUnlockedInteractor.isDeviceUnlocked) - deviceEntryRepository.setUnlocked(isUnlocked) - runCurrent() - - assertThat(isDeviceUnlocked).isEqualTo(isUnlocked) - } - private fun TestScope.changeScene( toScene: SceneKey, transitionState: MutableStateFlow<ObservableTransitionState>, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt index 1d6b22309579..77109d65fadc 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneViewModelTest.kt @@ -29,6 +29,8 @@ import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepositor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor import com.android.systemui.flags.FakeFeatureFlagsClassic import com.android.systemui.flags.Flags +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.media.controls.domain.pipeline.MediaDataManager import com.android.systemui.qs.footerActionsController @@ -140,7 +142,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) .isEqualTo(Scenes.Lockscreen) @@ -153,7 +154,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) assertThat(destinationScenes?.get(Swipe(SwipeDirection.Up))?.toScene) .isEqualTo(Scenes.Gone) @@ -178,7 +181,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() { testScope.runTest { val destinationScenes by collectLastValue(underTest.destinationScenes) kosmos.fakeDeviceEntryRepository.setLockscreenEnabled(true) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.None ) @@ -196,7 +198,9 @@ class ShadeSceneViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() assertThat(isClickable).isFalse() @@ -209,7 +213,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() assertThat(isClickable).isTrue() @@ -222,7 +225,6 @@ class ShadeSceneViewModelTest : SysuiTestCase() { kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Pin ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() underTest.onContentClicked() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt index 30564bb6eb84..29f286fd31fa 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/AvalancheControllerTest.kt @@ -22,6 +22,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.logging.testing.UiEventLoggerFake import com.android.systemui.SysuiTestCase +import com.android.systemui.dump.DumpManager import com.android.systemui.log.logcatLogBuffer import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun @@ -47,12 +48,14 @@ import org.mockito.junit.MockitoRule @EnableFlags(NotificationThrottleHun.FLAG_NAME) class AvalancheControllerTest : SysuiTestCase() { - private val mAvalancheController = AvalancheController() - // For creating mocks @get:Rule var rule: MockitoRule = MockitoJUnit.rule() @Mock private val runnableMock: Runnable? = null + // For creating AvalancheController + @Mock private lateinit var dumpManager: DumpManager + private lateinit var mAvalancheController: AvalancheController + // For creating TestableHeadsUpManager @Mock private val mAccessibilityMgr: AccessibilityManagerWrapper? = null private val mUiEventLoggerFake = UiEventLoggerFake() @@ -73,7 +76,10 @@ class AvalancheControllerTest : SysuiTestCase() { ) .then { i: InvocationOnMock -> i.getArgument(0) } - // Initialize TestableHeadsUpManager here instead of at declaration, when mocks will be null + // Initialize AvalancheController and TestableHeadsUpManager during setUp instead of + // declaration, where mocks are null + mAvalancheController = AvalancheController(dumpManager) + testableHeadsUpManager = TestableHeadsUpManager( mContext, diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java index 3dc449514699..7c130be7849e 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java @@ -45,6 +45,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.logging.testing.UiEventLoggerFake; import com.android.systemui.SysuiTestCase; +import com.android.systemui.dump.DumpManager; import com.android.systemui.res.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder; @@ -73,7 +74,9 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { private UiEventLoggerFake mUiEventLoggerFake = new UiEventLoggerFake(); private final HeadsUpManagerLogger mLogger = spy(new HeadsUpManagerLogger(logcatLogBuffer())); - private AvalancheController mAvalancheController = new AvalancheController(); + + @Mock private DumpManager dumpManager; + private AvalancheController mAvalancheController; @Mock private AccessibilityManagerWrapper mAccessibilityMgr; @@ -130,6 +133,7 @@ public class BaseHeadsUpManagerTest extends SysuiTestCase { public void SysuiSetup() throws Exception { super.SysuiSetup(); mSetFlagsRule.disableFlags(NotificationThrottleHun.FLAG_NAME); + mAvalancheController = new AvalancheController(dumpManager); } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java index 61a79d897b0b..a8a75c052000 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.java @@ -32,6 +32,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.logging.UiEventLogger; +import com.android.systemui.dump.DumpManager; import com.android.systemui.plugins.statusbar.StatusBarStateController; import com.android.systemui.res.R; import com.android.systemui.shade.domain.interactor.ShadeInteractor; @@ -76,7 +77,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { @Mock private UiEventLogger mUiEventLogger; @Mock private JavaAdapter mJavaAdapter; @Mock private ShadeInteractor mShadeInteractor; - private AvalancheController mAvalancheController = new AvalancheController(); + @Mock private DumpManager dumpManager; + private AvalancheController mAvalancheController; private static final class TestableHeadsUpManagerPhone extends HeadsUpManagerPhone { TestableHeadsUpManagerPhone( @@ -154,6 +156,8 @@ public class HeadsUpManagerPhoneTest extends BaseHeadsUpManagerTest { mDependency.injectMockDependency(NotificationShadeWindowController.class); mContext.getOrCreateTestableResources().addOverride( R.integer.ambient_notification_extension_time, 500); + + mAvalancheController = new AvalancheController(dumpManager); } @Test diff --git a/packages/SystemUI/res/anim/slide_in_up.xml b/packages/SystemUI/res/anim/slide_in_up.xml new file mode 100644 index 000000000000..6089a2809de3 --- /dev/null +++ b/packages/SystemUI/res/anim/slide_in_up.xml @@ -0,0 +1,21 @@ +<?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. +--> + +<translate + xmlns:android="http://schemas.android.com/apk/res/android" + android:fromYDelta="100%p" + android:toYDelta="0" + android:duration="@android:integer/config_shortAnimTime" /> diff --git a/packages/SystemUI/res/anim/slide_out_down.xml b/packages/SystemUI/res/anim/slide_out_down.xml new file mode 100644 index 000000000000..5a7b591fce9e --- /dev/null +++ b/packages/SystemUI/res/anim/slide_out_down.xml @@ -0,0 +1,21 @@ +<?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. +--> + +<translate + xmlns:android="http://schemas.android.com/apk/res/android" + android:fromYDelta="0" + android:toYDelta="100%p" + android:duration="@android:integer/config_shortAnimTime" /> diff --git a/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml b/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml index 1b12e74141a7..0406f0e4304e 100644 --- a/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml +++ b/packages/SystemUI/res/drawable/ic_shortcutlist_search.xml @@ -14,12 +14,15 @@ limitations under the License --> <vector xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:width="24dp" android:height="24dp" android:viewportWidth="48" android:viewportHeight="48" - android:tint="?android:attr/textColorSecondary"> + android:tint="?androidprv:attr/materialColorOnSurfaceVariant"> <path android:fillColor="@android:color/white" + android:strokeColor="@android:color/white" + android:strokeWidth="2" android:pathData="M39.8,41.95 L26.65,28.8Q25.15,30.1 23.15,30.825Q21.15,31.55 18.9,31.55Q13.5,31.55 9.75,27.8Q6,24.05 6,18.75Q6,13.45 9.75,9.7Q13.5,5.95 18.85,5.95Q24.15,5.95 27.875,9.7Q31.6,13.45 31.6,18.75Q31.6,20.9 30.9,22.9Q30.2,24.9 28.8,26.65L42,39.75ZM18.85,28.55Q22.9,28.55 25.75,25.675Q28.6,22.8 28.6,18.75Q28.6,14.7 25.75,11.825Q22.9,8.95 18.85,8.95Q14.75,8.95 11.875,11.825Q9,14.7 9,18.75Q9,22.8 11.875,25.675Q14.75,28.55 18.85,28.55Z"/> -</vector>
\ No newline at end of file +</vector> diff --git a/packages/SystemUI/res/drawable/shortcut_button_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_colored.xml index bf908532ac17..2e2d9b9a3e35 100644 --- a/packages/SystemUI/res/drawable/shortcut_button_colored.xml +++ b/packages/SystemUI/res/drawable/shortcut_button_colored.xml @@ -21,7 +21,7 @@ android:color="?android:attr/colorControlHighlight"> <item> <shape android:shape="rectangle"> - <corners android:radius="16dp"/> + <corners android:radius="@dimen/ksh_button_corner_radius"/> <solid android:color="?androidprv:attr/materialColorSurfaceBright"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml index f692ed975319..5b88bb922a9e 100644 --- a/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml +++ b/packages/SystemUI/res/drawable/shortcut_button_focus_colored.xml @@ -21,7 +21,7 @@ android:color="?android:attr/colorControlHighlight"> <item> <shape android:shape="rectangle"> - <corners android:radius="16dp"/> + <corners android:radius="@dimen/ksh_button_corner_radius"/> <solid android:color="?androidprv:attr/materialColorPrimary"/> </shape> </item> diff --git a/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml b/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml index 6ce3eaecc147..aa0b2689f5bf 100644 --- a/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml +++ b/packages/SystemUI/res/drawable/shortcut_dialog_bg.xml @@ -17,8 +17,8 @@ <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="?android:attr/colorBackground"/> - <corners android:topLeftRadius="16dp" - android:topRightRadius="16dp" + <corners android:topLeftRadius="@dimen/ksh_dialog_top_corner_radius" + android:topRightRadius="@dimen/ksh_dialog_top_corner_radius" android:bottomLeftRadius="0dp" android:bottomRightRadius="0dp"/> -</shape>
\ No newline at end of file +</shape> diff --git a/packages/SystemUI/res/drawable/shortcut_search_background.xml b/packages/SystemUI/res/drawable/shortcut_search_background.xml index 66fc191cb736..d6847f0abb8d 100644 --- a/packages/SystemUI/res/drawable/shortcut_search_background.xml +++ b/packages/SystemUI/res/drawable/shortcut_search_background.xml @@ -19,8 +19,8 @@ xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"> <item> <shape android:shape="rectangle"> - <solid android:color="?androidprv:attr/colorSurface" /> - <corners android:radius="24dp" /> + <solid android:color="?androidprv:attr/materialColorSurfaceBright" /> + <corners android:radius="@dimen/ksh_search_box_corner_radius" /> </shape> </item> -</layer-list>
\ No newline at end of file +</layer-list> diff --git a/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml b/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml index 6c4d4fbcc1fe..2675906580f1 100644 --- a/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml +++ b/packages/SystemUI/res/drawable/shortcut_search_cancel_button.xml @@ -20,7 +20,7 @@ <shape android:shape="oval"> <size android:width="24dp" android:height="24dp" /> - <solid android:color="?androidprv:attr/colorSurface"/> + <solid android:color="?androidprv:attr/materialColorSurfaceBright"/> </shape> </item> </ripple> diff --git a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml index ae243130e537..14b3b55df0a4 100644 --- a/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml +++ b/packages/SystemUI/res/layout/clipboard_edit_text_activity.xml @@ -3,6 +3,7 @@ xmlns:android="http://schemas.android.com/apk/res/android" xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" + android:id="@+id/editor_root" android:layout_width="match_parent" android:layout_height="match_parent"> diff --git a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml index a0051008ddd6..5ab23271922c 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcut_app_item.xml @@ -15,13 +15,14 @@ ~ limitations under the License --> <com.android.systemui.statusbar.KeyboardShortcutAppItemLayout + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:background="@drawable/list_item_background" android:focusable="true" android:layout_width="match_parent" android:layout_height="wrap_content" - android:minHeight="48dp" + android:minHeight="@dimen/ksh_app_item_minimum_height" android:paddingBottom="8dp"> <ImageView android:id="@+id/keyboard_shortcuts_icon" @@ -39,7 +40,8 @@ android:layout_height="wrap_content" android:paddingEnd="12dp" android:paddingBottom="4dp" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurface" + android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="16sp" android:maxLines="5" android:singleLine="false" diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml index 530e46eb1c40..76e5b12bdcd6 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_short_separator.xml @@ -15,6 +15,6 @@ limitations under the License --> <View xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_marginTop="8dp" - android:layout_marginBottom="0dp" + android:layout_marginTop="@dimen/ksh_category_separator_margin" + android:layout_marginBottom="@dimen/ksh_category_separator_margin" style="@style/ShortcutHorizontalDivider" /> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml index 4f100f6b94d1..6e7fde68ca04 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_category_title.xml @@ -16,10 +16,12 @@ ~ limitations under the License --> <TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:layout_width="match_parent" android:layout_height="match_parent" + android:textAppearance="?android:attr/textAppearanceMedium" android:textSize="14sp" - android:fontFamily="sans-serif-medium" + android:textColor="?androidprv:attr/materialColorPrimary" android:importantForAccessibility="yes" android:paddingTop="20dp" android:paddingBottom="10dp"/> diff --git a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml index f6042e467987..2cfd644e9d62 100644 --- a/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml +++ b/packages/SystemUI/res/layout/keyboard_shortcuts_search_view.xml @@ -16,15 +16,21 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:androidprv="http://schemas.android.com/apk/prv/res/android" android:background="@drawable/shortcut_dialog_bg" android:layout_width="@dimen/ksh_layout_width" android:layout_height="wrap_content" android:orientation="vertical"> + + <com.google.android.material.bottomsheet.BottomSheetDragHandleView + android:id="@+id/drag_handle" + android:layout_width="match_parent" + android:layout_height="wrap_content"/> + <TextView android:id="@+id/shortcut_title" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginTop="40dp" android:layout_gravity="center_horizontal" android:textAppearance="?android:attr/textAppearanceLarge" android:textColor="?android:attr/textColorPrimary" @@ -39,44 +45,47 @@ android:layout_height="wrap_content" android:layout_marginTop="24dp" android:layout_marginBottom="24dp" - android:layout_marginStart="49dp" - android:layout_marginEnd="49dp" + android:layout_marginStart="@dimen/ksh_container_horizontal_margin" + android:layout_marginEnd="@dimen/ksh_container_horizontal_margin" android:padding="16dp" android:background="@drawable/shortcut_search_background" android:drawableStart="@drawable/ic_shortcutlist_search" android:drawablePadding="15dp" android:singleLine="true" - android:textColor="?android:attr/textColorPrimary" + android:textColor="?androidprv:attr/materialColorOnSurfaceVariant" android:inputType="text" android:textDirection="locale" android:textAlignment="viewStart" android:hint="@string/keyboard_shortcut_search_list_hint" - android:textColorHint="?android:attr/textColorTertiary" /> + android:textAppearance="@android:style/TextAppearance.Material" + android:textSize="16sp" + android:textColorHint="?androidprv:attr/materialColorOutline" /> <ImageButton android:id="@+id/keyboard_shortcuts_search_cancel" android:layout_gravity="center_vertical|end" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginEnd="49dp" + android:layout_marginEnd="@dimen/ksh_container_horizontal_margin" android:padding="16dp" android:contentDescription="@string/keyboard_shortcut_clear_text" android:src="@drawable/ic_shortcutlist_search_button_cancel" android:background="@drawable/shortcut_search_cancel_button" style="@android:style/Widget.Material.Button.Borderless.Small" - android:pointerIcon="arrow" /> + android:pointerIcon="arrow" + android:visibility="gone" /> </FrameLayout> <HorizontalScrollView android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginStart="49dp" + android:layout_marginStart="@dimen/ksh_container_horizontal_margin" android:layout_marginEnd="0dp" android:scrollbars="none"> <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" - android:gravity="center_vertical" + android:layout_gravity="center_vertical" android:orientation="horizontal"> <Button android:id="@+id/shortcut_system" @@ -113,29 +122,29 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="50dp" - android:layout_marginStart="49dp" - android:layout_marginEnd="49dp" + android:layout_marginStart="@dimen/ksh_container_horizontal_margin" + android:layout_marginEnd="@dimen/ksh_container_horizontal_margin" android:layout_gravity="center_horizontal" android:textAppearance="?android:attr/textAppearanceMedium" android:textColor="?android:attr/textColorPrimary" android:text="@string/keyboard_shortcut_search_list_no_result"/> - <ScrollView + <androidx.core.widget.NestedScrollView android:id="@+id/keyboard_shortcuts_scroll_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" - android:layout_marginStart="49dp" - android:layout_marginEnd="49dp" + android:layout_marginStart="@dimen/ksh_container_horizontal_margin" + android:layout_marginEnd="@dimen/ksh_container_horizontal_margin" android:overScrollMode="never" - android:layout_marginBottom="16dp" + android:clipToPadding="false" android:scrollbars="none"> <LinearLayout android:id="@+id/keyboard_shortcuts_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical"/> - </ScrollView> + </androidx.core.widget.NestedScrollView> <!-- Required for stretching to full available height when the items in the scroll view occupy less space then the full height --> <View diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index 55606aa0bc82..56ebc0668097 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -94,4 +94,7 @@ <dimen name="keyguard_indication_margin_bottom">8dp</dimen> <dimen name="lock_icon_margin_bottom">24dp</dimen> + + <!-- Keyboard shortcuts helper --> + <dimen name="ksh_container_horizontal_margin">48dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index fe8f2fff550a..b95ee56e9e48 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -978,6 +978,7 @@ <dimen name="assist_disclosure_shadow_thickness">1.5dp</dimen> <!-- Keyboard shortcuts helper --> + <dimen name="ksh_container_horizontal_margin">32dp</dimen> <dimen name="ksh_layout_width">@dimen/match_parent</dimen> <dimen name="ksh_item_text_size">14sp</dimen> <dimen name="ksh_item_padding">0dp</dimen> @@ -985,6 +986,11 @@ <dimen name="ksh_icon_scaled_size">18dp</dimen> <dimen name="ksh_key_view_padding_vertical">4dp</dimen> <dimen name="ksh_key_view_padding_horizontal">12dp</dimen> + <dimen name="ksh_button_corner_radius">12dp</dimen> + <dimen name="ksh_dialog_top_corner_radius">28dp</dimen> + <dimen name="ksh_search_box_corner_radius">100dp</dimen> + <dimen name="ksh_app_item_minimum_height">64dp</dimen> + <dimen name="ksh_category_separator_margin">16dp</dimen> <!-- The size of corner radius of the arrow in the onboarding toast. --> <dimen name="recents_onboarding_toast_arrow_corner_radius">2dp</dimen> @@ -1867,6 +1873,10 @@ .2 </item> + <item name="dream_overlay_bouncer_min_region_screen_percentage" format="float" type="dimen"> + .05 + </item> + <!-- The padding applied to the dream overlay container --> <dimen name="dream_overlay_container_padding_start">0dp</dimen> <dimen name="dream_overlay_container_padding_end">0dp</dimen> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 71353b6774af..244603908c65 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2023,12 +2023,12 @@ <!-- User visible title for the keyboard shortcut that pulls up Notes app for quick memo. [CHAR LIMIT=70] --> <string name="group_system_quick_memo">Take a note</string> - <!-- User visible title for the system multitasking keyboard shortcuts list. [CHAR LIMIT=70] --> - <string name="keyboard_shortcut_group_system_multitasking">System multitasking</string> - <!-- User visible title for the keyboard shortcut that enters split screen with current app to RHS [CHAR LIMIT=70] --> - <string name="system_multitasking_rhs">Enter split screen with current app to RHS</string> - <!-- User visible title for the keyboard shortcut that enters split screen with current app to LHS [CHAR LIMIT=70] --> - <string name="system_multitasking_lhs">Enter split screen with current app to LHS</string> + <!-- User visible title for the multitasking keyboard shortcuts list. [CHAR LIMIT=70] --> + <string name="keyboard_shortcut_group_system_multitasking">Multitasking</string> + <!-- User visible title for the keyboard shortcut that enters split screen with current app on the right [CHAR LIMIT=70] --> + <string name="system_multitasking_rhs">Use split screen with current app on the right</string> + <!-- User visible title for the keyboard shortcut that enters split screen with current app on the left [CHAR LIMIT=70] --> + <string name="system_multitasking_lhs">Use split screen with current app on the left</string> <!-- User visible title for the keyboard shortcut that switches from split screen to full screen [CHAR LIMIT=70] --> <string name="system_multitasking_full_screen">Switch from split screen to full screen</string> <!-- User visible title for the keyboard shortcut that switches to app on right or below while using split screen [CHAR LIMIT=70] --> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 6462d02de481..9da4f79e33df 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -224,6 +224,7 @@ <style name="TextAppearance.AuthCredential.ContentViewListItem" parent="TextAppearance.Material3.BodySmall"> <item name="android:textColor">?androidprv:attr/materialColorOnSurfaceVariant</item> <item name="android:paddingTop">@dimen/biometric_prompt_content_list_item_padding_top</item> + <item name="android:breakStrategy">high_quality</item> </style> <style name="TextAppearance.AuthCredential.Indicator" parent="TextAppearance.Material3.BodyMedium"> @@ -365,6 +366,21 @@ <item name="android:layout_height">wrap_content</item> </style> + <style name="KeyboardShortcutHelper" parent="@android:style/Theme.DeviceDefault.Settings"> + <!-- Needed to be able to use BottomSheetDragHandleView --> + <item name="android:windowActionBar">false</item> + <item name="bottomSheetDragHandleStyle">@style/KeyboardShortcutHelper.BottomSheet.DragHandle</item> + </style> + + <style name="KeyboardShortcutHelper.BottomSheet.DragHandle" parent="Widget.Material3.BottomSheet.DragHandle"> + <item name="tint">?androidprv:attr/materialColorOutlineVariant</item> + </style> + + <style name="KeyboardShortcutHelper.BottomSheetDialogAnimation"> + <item name="android:windowEnterAnimation">@anim/slide_in_up</item> + <item name="android:windowExitAnimation">@anim/slide_out_down</item> + </style> + <style name="BrightnessDialogContainer" parent="@style/BaseBrightnessDialogContainer" /> <style name="Animation" /> @@ -1597,14 +1613,15 @@ <item name="android:layout_marginEnd">12dp</item> <item name="android:paddingLeft">24dp</item> <item name="android:paddingRight">24dp</item> - <item name="android:minHeight">40dp</item> + <item name="android:minHeight">36dp</item> + <item name="android:minWidth">120dp</item> <item name="android:stateListAnimator">@*android:anim/flat_button_state_list_anim_material</item> <item name="android:pointerIcon">arrow</item> </style> <style name="ShortcutHorizontalDivider"> - <item name="android:layout_width">120dp</item> - <item name="android:layout_height">1dp</item> + <item name="android:layout_width">132dp</item> + <item name="android:layout_height">2dp</item> <item name="android:layout_gravity">center_horizontal</item> <item name="android:background">?android:attr/dividerHorizontal</item> </style> diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp index 42ba05cff906..fbe139930e11 100644 --- a/packages/SystemUI/shared/Android.bp +++ b/packages/SystemUI/shared/Android.bp @@ -58,7 +58,7 @@ android_library { "SystemUIUnfoldLib", "SystemUISharedLib-Keyguard", "WindowManager-Shell-shared", - "tracinglib-platform", + "//frameworks/libs/systemui:tracinglib-platform", "androidx.dynamicanimation_dynamicanimation", "androidx.concurrent_concurrent-futures", "androidx.lifecycle_lifecycle-runtime-ktx", @@ -68,7 +68,7 @@ android_library { "kotlinx_coroutines", "dagger2", "jsr330", - "com_android_systemui_shared_flags_lib", + "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", ], resource_dirs: [ "res", diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl index 46329148a659..dcc14409f046 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl @@ -155,5 +155,10 @@ interface ISystemUiProxy { */ oneway void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) = 54; - // Next id = 55 + /** + * Set the override value for home button long press duration in ms and slop multiplier. + */ + oneway void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) = 55; + + // Next id = 56 } diff --git a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt index c4d282e24a92..a667de2351d7 100644 --- a/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt +++ b/packages/SystemUI/src/com/android/systemui/backup/BackupHelper.kt @@ -28,10 +28,14 @@ import android.os.ParcelFileDescriptor import android.os.UserHandle import android.util.Log import com.android.app.tracing.traceSection +import com.android.systemui.Flags.communalHub +import com.android.systemui.backup.BackupHelper.Companion.ACTION_RESTORE_FINISHED +import com.android.systemui.communal.domain.backup.CommunalPrefsBackupHelper import com.android.systemui.controls.controller.AuxiliaryPersistenceWrapper import com.android.systemui.controls.controller.ControlsFavoritePersistenceWrapper import com.android.systemui.keyguard.domain.backup.KeyguardQuickAffordanceBackupHelper import com.android.systemui.people.widget.PeopleBackupHelper +import com.android.systemui.res.R import com.android.systemui.settings.UserFileManagerImpl /** @@ -53,6 +57,8 @@ open class BackupHelper : BackupAgentHelper() { private const val PEOPLE_TILES_BACKUP_KEY = "systemui.people.shared_preferences" private const val KEYGUARD_QUICK_AFFORDANCES_BACKUP_KEY = "systemui.keyguard.quickaffordance.shared_preferences" + private const val COMMUNAL_PREFS_BACKUP_KEY = + "systemui.communal.shared_preferences" val controlsDataLock = Any() const val ACTION_RESTORE_FINISHED = "com.android.systemui.backup.RESTORE_FINISHED" const val PERMISSION_SELF = "com.android.systemui.permission.SELF" @@ -75,6 +81,15 @@ open class BackupHelper : BackupAgentHelper() { userId = userHandle.identifier, ), ) + if (communalEnabled()) { + addHelper( + COMMUNAL_PREFS_BACKUP_KEY, + CommunalPrefsBackupHelper( + context = this, + userId = userHandle.identifier, + ) + ) + } } override fun onRestoreFinished() { @@ -100,6 +115,10 @@ open class BackupHelper : BackupAgentHelper() { } } + private fun communalEnabled(): Boolean { + return resources.getBoolean(R.bool.config_communalServiceEnabled) && communalHub() + } + /** * Helper class for restoring files ONLY if they are not present. * diff --git a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt index fd7e98f1865c..9f594feb03ab 100644 --- a/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt +++ b/packages/SystemUI/src/com/android/systemui/battery/unified/BatteryDrawableState.kt @@ -30,7 +30,7 @@ enum class ColorProfile { Active, // Yellow for e.g., battery saver Warning, - // Red for e.t., low battery + // Red for e.g., low battery Error, } @@ -108,17 +108,17 @@ sealed interface BatteryColors { // 22% alpha white override val bg: Int = Color.valueOf(1f, 1f, 1f, 0.22f).toArgb() + // GM Gray 500 + override val fill = Color.parseColor("#9AA0A6") // GM Gray 600 - override val fill = Color.parseColor("#80868B") - // GM Gray 700 - override val fillOnly = Color.parseColor("#5F6368") + override val fillOnly = Color.parseColor("#80868B") - // GM Green 700 - override val activeFill = Color.parseColor("#188038") + // GM Green 500 + override val activeFill = Color.parseColor("#34A853") // GM Yellow 500 override val warnFill = Color.parseColor("#FBBC04") - // GM Red 600 - override val errorFill = Color.parseColor("#D93025") + // GM Red 500 + override val errorFill = Color.parseColor("#EA4335") } /** Color scheme appropriate for dark mode (light icons) */ @@ -132,12 +132,12 @@ sealed interface BatteryColors { // GM Gray 400 override val fillOnly = Color.parseColor("#BDC1C6") - // GM Green 500 - override val activeFill = Color.parseColor("#34A853") - // GM Yellow - override val warnFill = Color.parseColor("#FBBC04") - // GM Red 600 - override val errorFill = Color.parseColor("#D93025") + // GM Green 700 + override val activeFill = Color.parseColor("#188038") + // GM Yellow 700 + override val warnFill = Color.parseColor("#F29900") + // GM Red 700 + override val errorFill = Color.parseColor("#C5221F") } companion object { diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt index 88aef5675240..7ccac03bcac6 100644 --- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricCustomizedViewBinder.kt @@ -31,18 +31,19 @@ import android.text.style.BulletSpan import android.view.LayoutInflater import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT +import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewTreeObserver import android.widget.Button import android.widget.LinearLayout import android.widget.Space import android.widget.TextView -import androidx.lifecycle.lifecycleScope import com.android.settingslib.Utils import com.android.systemui.biometrics.ui.BiometricPromptLayout import com.android.systemui.lifecycle.repeatWhenAttached import com.android.systemui.res.R import kotlin.math.ceil -import kotlinx.coroutines.launch + +private const val TAG = "BiometricCustomizedViewBinder" /** Sub-binder for [BiometricPromptLayout.customized_view_container]. */ object BiometricCustomizedViewBinder { @@ -52,21 +53,20 @@ object BiometricCustomizedViewBinder { legacyCallback: Spaghetti.Callback ) { customizedViewContainer.repeatWhenAttached { containerView -> - lifecycleScope.launch { - if (contentView == null) { - containerView.visibility = View.GONE - return@launch - } + if (contentView == null) { + containerView.visibility = View.GONE + return@repeatWhenAttached + } - containerView.width { containerWidth -> - if (containerWidth == 0) { - return@width - } - (containerView as LinearLayout).addView( - contentView.toView(containerView.context, containerWidth, legacyCallback) - ) - containerView.visibility = View.VISIBLE + containerView.width { containerWidth -> + if (containerWidth == 0) { + return@width } + (containerView as LinearLayout).addView( + contentView.toView(containerView.context, containerWidth, legacyCallback), + LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT) + ) + containerView.visibility = View.VISIBLE } } } @@ -118,51 +118,42 @@ private fun PromptVerticalListContentView.initLayout( containerViewWidth: Int ): View { val inflater = LayoutInflater.from(context) - val resources = context.resources + context.resources val contentView = inflater.inflateContentView( R.layout.biometric_prompt_vertical_list_content_layout, description ) + val listItemsToShow = ArrayList<PromptContentItem>(listItems) // Show two column by default, once there is an item exceeding max lines, show single // item instead. val showTwoColumn = - listItems.all { !it.doesExceedMaxLinesIfTwoColumn(context, containerViewWidth) } - var currRowView = createNewRowLayout(inflater) - for (item in listItems) { + listItemsToShow.all { !it.doesExceedMaxLinesIfTwoColumn(context, containerViewWidth) } + // If should show two columns and there are more than one items, make listItems always have odd + // number items. + if (showTwoColumn && listItemsToShow.size > 1 && listItemsToShow.size % 2 == 1) { + listItemsToShow.add(dummyItem()) + } + var currRow = createNewRowLayout(inflater) + for (i in 0 until listItemsToShow.size) { + val item = listItemsToShow[i] val itemView = item.toView(context, inflater) - // If this item will be in the first row (contentView only has description view) and - // description is empty, remove top padding of this item. - if (contentView.childCount == 1 && description.isNullOrEmpty()) { - itemView.setPadding( - itemView.paddingLeft, - 0, - itemView.paddingRight, - itemView.paddingBottom - ) - } - currRowView.addView(itemView) + contentView.removeTopPaddingForFirstRow(description, itemView) - // If this is the first item in the current row, add space behind it. - if (currRowView.childCount == 1 && showTwoColumn) { - currRowView.addSpaceView( - resources.getDimensionPixelSize( - R.dimen.biometric_prompt_content_space_width_between_items - ), - MATCH_PARENT - ) + // If there should be two column, and there is already one item in the current row, add + // space between two items. + if (showTwoColumn && currRow.childCount == 1) { + currRow.addSpaceViewBetweenListItem() } + currRow.addView(itemView) - // If there are already two items (plus the space view) in the current row, or it - // should be one column, start a new row - if (currRowView.childCount == 3 || !showTwoColumn) { - contentView.addView(currRowView) - currRowView = createNewRowLayout(inflater) + // If there should be one column, or there are already two items (plus the space view) in + // the current row, or it's already the last item, start a new row + if (!showTwoColumn || currRow.childCount == 3 || i == listItemsToShow.size - 1) { + contentView.addView(currRow) + currRow = createNewRowLayout(inflater) } } - if (currRowView.childCount > 0) { - contentView.addView(currRowView) - } return contentView } @@ -170,10 +161,6 @@ private fun createNewRowLayout(inflater: LayoutInflater): LinearLayout { return inflater.inflate(R.layout.biometric_prompt_content_row_layout, null) as LinearLayout } -private fun LinearLayout.addSpaceView(width: Int, height: Int) { - addView(Space(context), LinearLayout.LayoutParams(width, height)) -} - private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn( context: Context, containerViewWidth: Int, @@ -194,7 +181,10 @@ private fun PromptContentItem.doesExceedMaxLinesIfTwoColumn( val contentViewPadding = resources.getDimensionPixelSize(R.dimen.biometric_prompt_content_padding_horizontal) val listItemPadding = getListItemPadding(resources) - val maxWidth = containerViewWidth / 2 - contentViewPadding - listItemPadding + var maxWidth = containerViewWidth / 2 - contentViewPadding - listItemPadding + // Reduce maxWidth a bit since paint#measureText is not accurate. See b/330909104 for + // more context. + maxWidth -= contentViewPadding / 2 val paint = TextPaint() val attributes = @@ -224,6 +214,7 @@ private fun PromptContentItem.toView( inflater: LayoutInflater, ): TextView { val resources = context.resources + // Somehow xml layout params settings doesn't work, set it again here. val textView = inflater.inflate(R.layout.biometric_prompt_content_row_item_text_view, null) as TextView val lp = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.MATCH_PARENT, 1f) @@ -251,6 +242,29 @@ private fun PromptContentItem.toView( return textView } +/* [contentView] function */ +private fun LinearLayout.addSpaceViewBetweenListItem() = + addView( + Space(context), + LinearLayout.LayoutParams( + resources.getDimensionPixelSize( + R.dimen.biometric_prompt_content_space_width_between_items + ), + MATCH_PARENT + ) + ) + +/* [contentView] function*/ +private fun LinearLayout.removeTopPaddingForFirstRow(description: String?, itemView: TextView) { + // If this item will be in the first row (contentView only has description view and + // description is empty), remove top padding of this item. + if (description.isNullOrEmpty() && childCount == 1) { + itemView.setPadding(itemView.paddingLeft, 0, itemView.paddingRight, itemView.paddingBottom) + } +} + +private fun dummyItem(): PromptContentItem = PromptContentItemPlainText("") + private fun PromptContentItem.getListItemPadding(resources: Resources): Int { var listItemPadding = resources.getDimensionPixelSize( diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt index f1a0e5e3539c..78811a96a026 100644 --- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt @@ -12,14 +12,15 @@ import com.android.systemui.bouncer.ui.BouncerDialogFactory import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel import com.android.systemui.dagger.SysUISingleton -import com.android.systemui.flags.Flags.COMPOSE_BOUNCER_ENABLED import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel import com.android.systemui.log.BouncerLogger import com.android.systemui.user.domain.interactor.SelectedUserInteractor import dagger.Lazy import javax.inject.Inject +import kotlinx.coroutines.ExperimentalCoroutinesApi /** Helper data class that allows to lazy load all the dependencies of the legacy bouncer. */ +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton data class LegacyBouncerDependencies @Inject @@ -59,7 +60,7 @@ constructor( private val composeBouncerDependencies: Lazy<ComposeBouncerDependencies>, ) { fun bind(view: ViewGroup) { - if (COMPOSE_BOUNCER_ENABLED && composeBouncerFlags.isOnlyComposeBouncerEnabled()) { + if (composeBouncerFlags.isOnlyComposeBouncerEnabled()) { val deps = composeBouncerDependencies.get() ComposeBouncerViewBinder.bind( view, diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java index bb201b624a71..a43447f7fcf4 100644 --- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java +++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/EditTextActivity.java @@ -23,14 +23,20 @@ import android.content.ClipData; import android.content.ClipDescription; import android.content.ClipboardManager; import android.content.pm.PackageManager; +import android.graphics.Insets; import android.os.Bundle; import android.os.PersistableBundle; import android.text.Editable; import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowInsets; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.TextView; +import androidx.annotation.NonNull; + import com.android.systemui.res.R; /** @@ -53,6 +59,24 @@ public class EditTextActivity extends Activity mEditText = findViewById(R.id.edit_text); mAttribution = findViewById(R.id.attribution); mClipboardManager = requireNonNull(getSystemService(ClipboardManager.class)); + + findViewById(R.id.editor_root).setOnApplyWindowInsetsListener( + new View.OnApplyWindowInsetsListener() { + @NonNull + @Override + public WindowInsets onApplyWindowInsets(@NonNull View view, + @NonNull WindowInsets windowInsets) { + Insets insets = windowInsets.getInsets(WindowInsets.Type.systemBars()); + ViewGroup.MarginLayoutParams layoutParams = + (ViewGroup.MarginLayoutParams) view.getLayoutParams(); + layoutParams.leftMargin = insets.left; + layoutParams.bottomMargin = insets.bottom; + layoutParams.rightMargin = insets.right; + layoutParams.topMargin = insets.top; + view.setLayoutParams(layoutParams); + return WindowInsets.CONSUMED; + } + }); } @Override diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt index 0e9b32ffd12a..40d744015498 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalPrefsRepository.kt @@ -17,8 +17,11 @@ package com.android.systemui.communal.data.repository import android.content.Context +import android.content.IntentFilter import android.content.SharedPreferences import android.content.pm.UserInfo +import com.android.systemui.backup.BackupHelper +import com.android.systemui.broadcast.BroadcastDispatcher import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.LogBuffer @@ -30,15 +33,18 @@ import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.settings.UserFileManager import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.SharedPreferencesExt.observe +import com.android.systemui.util.kotlin.emitOnStart import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext @@ -65,14 +71,36 @@ constructor( @Background private val bgDispatcher: CoroutineDispatcher, private val userRepository: UserRepository, private val userFileManager: UserFileManager, + broadcastDispatcher: BroadcastDispatcher, @CommunalLog logBuffer: LogBuffer, @CommunalTableLog tableLogBuffer: TableLogBuffer, ) : CommunalPrefsRepository { private val logger = Logger(logBuffer, "CommunalPrefsRepositoryImpl") + /** + * Emits an event each time a Backup & Restore restoration job is completed. Does not emit an + * initial value. + */ + private val backupRestorationEvents: Flow<Unit> = + broadcastDispatcher.broadcastFlow( + filter = IntentFilter(BackupHelper.ACTION_RESTORE_FINISHED), + flags = Context.RECEIVER_NOT_EXPORTED, + permission = BackupHelper.PERMISSION_SELF, + ) + override val isCtaDismissed: Flow<Boolean> = - userRepository.selectedUserInfo + combine( + userRepository.selectedUserInfo, + // Make sure combine can emit even if we never get a Backup & Restore event, + // which is the most common case as restoration only happens on initial device + // setup. + backupRestorationEvents.emitOnStart().onEach { + logger.i("Restored state for communal preferences.") + }, + ) { user, _ -> + user + } .flatMapLatest(::observeCtaDismissState) .logDiffsForTable( tableLogBuffer = tableLogBuffer, diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt new file mode 100644 index 000000000000..55c6ec88bd11 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/backup/CommunalPrefsBackupHelper.kt @@ -0,0 +1,37 @@ +/* + * 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.communal.domain.backup + +import android.app.backup.SharedPreferencesBackupHelper +import android.content.Context +import com.android.systemui.communal.data.repository.CommunalPrefsRepositoryImpl.Companion.FILE_NAME +import com.android.systemui.settings.UserFileManagerImpl + +/** Helper to backup & restore the shared preferences in glanceable hub for the current user. */ +class CommunalPrefsBackupHelper( + context: Context, + userId: Int, +) : + SharedPreferencesBackupHelper( + context, + UserFileManagerImpl.createFile( + userId = userId, + fileName = FILE_NAME, + ) + .path + ) diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt index 82834387f7b8..f779ac8fb0bb 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepository.kt @@ -1,15 +1,11 @@ package com.android.systemui.deviceentry.data.repository -import android.util.Log import com.android.internal.widget.LockPatternUtils -import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background -import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.statusbar.phone.KeyguardBypassController -import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.user.data.repository.UserRepository import dagger.Binds import dagger.Module @@ -17,38 +13,20 @@ import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.withContext /** Interface for classes that can access device-entry-related application state. */ interface DeviceEntryRepository { /** - * Whether the device is unlocked. - * - * A device that is not yet unlocked requires unlocking by completing an authentication - * challenge according to the current authentication method, unless in cases when the current - * authentication method is not "secure" (for example, None); in such cases, the value of this - * flow will always be `true`, even if the lockscreen is showing and still needs to be dismissed - * by the user to proceed. - */ - val isUnlocked: StateFlow<Boolean> - - /** * Whether the lockscreen is enabled for the current user. This is `true` whenever the user has * chosen any secure authentication method and even if they set the lockscreen to be dismissed * when the user swipes on it. */ suspend fun isLockscreenEnabled(): Boolean - /** Report successful authentication for device entry. */ - fun reportSuccessfulAuthentication() - /** * Whether lockscreen bypass is enabled. When enabled, the lockscreen will be automatically * dismissed once the authentication challenge is completed. @@ -73,53 +51,8 @@ constructor( private val userRepository: UserRepository, private val lockPatternUtils: LockPatternUtils, private val keyguardBypassController: KeyguardBypassController, - keyguardStateController: KeyguardStateController, - keyguardRepository: KeyguardRepository, ) : DeviceEntryRepository { - private val _isUnlocked = MutableStateFlow(false) - - private val isUnlockedReportedByLegacyKeyguard = - conflatedCallbackFlow { - val callback = - object : KeyguardStateController.Callback { - override fun onUnlockedChanged() { - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "updated isUnlocked due to onUnlockedChanged" - ) - } - - override fun onKeyguardShowingChanged() { - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "updated isUnlocked due to onKeyguardShowingChanged" - ) - } - } - - keyguardStateController.addCallback(callback) - // Adding the callback does not send an initial update. - trySendWithFailureLogging( - keyguardStateController.isUnlocked, - TAG, - "initial isKeyguardUnlocked" - ) - - awaitClose { keyguardStateController.removeCallback(callback) } - } - .distinctUntilChanged() - .onEach { _isUnlocked.value = it } - .stateIn( - applicationScope, - SharingStarted.Eagerly, - initialValue = false, - ) - - override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() - override suspend fun isLockscreenEnabled(): Boolean { return withContext(backgroundDispatcher) { val selectedUserId = userRepository.getSelectedUserInfo().id @@ -127,11 +60,6 @@ constructor( } } - override fun reportSuccessfulAuthentication() { - Log.d(TAG, "Successful authentication reported.") - _isUnlocked.value = true - } - override val isBypassEnabled: StateFlow<Boolean> = conflatedCallbackFlow { val listener = diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt index c4e0ef7d082d..ec574d2d031d 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt @@ -24,6 +24,7 @@ import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthentication import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import javax.inject.Inject import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow @@ -58,6 +59,9 @@ constructor( val fingerprintHelp: Flow<HelpFingerprintAuthenticationStatus> = repository.authenticationStatus.filterIsInstance<HelpFingerprintAuthenticationStatus>() + val fingerprintSuccess: Flow<SuccessFingerprintAuthenticationStatus> = + repository.authenticationStatus.filterIsInstance<SuccessFingerprintAuthenticationStatus>() + /** * Whether fingerprint authentication is currently allowed for the user. This is true if the * user has fingerprint auth enabled, enrolled, it is not disabled by any security timeouts by diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index fa2421a3516d..5c1ca646529e 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -26,7 +26,6 @@ import com.android.systemui.deviceentry.shared.model.DeviceEntryRestrictionReaso import com.android.systemui.flags.SystemPropertiesHelper import com.android.systemui.keyguard.domain.interactor.TrustInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor -import com.android.systemui.scene.shared.flag.SceneContainerFlags import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.util.kotlin.Quad import javax.inject.Inject @@ -35,14 +34,11 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.merge -import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch @@ -65,8 +61,7 @@ constructor( private val fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, private val biometricSettingsInteractor: DeviceEntryBiometricSettingsInteractor, private val trustInteractor: TrustInteractor, - flags: SceneContainerFlags, - deviceUnlockedInteractor: DeviceUnlockedInteractor, + private val deviceUnlockedInteractor: DeviceUnlockedInteractor, private val systemPropertiesHelper: SystemPropertiesHelper, ) { /** @@ -78,7 +73,14 @@ constructor( * of this flow will always be `true`, even if the lockscreen is showing and still needs to be * dismissed by the user to proceed. */ - val isUnlocked: StateFlow<Boolean> = deviceUnlockedInteractor.isDeviceUnlocked + val isUnlocked: StateFlow<Boolean> = + deviceUnlockedInteractor.deviceUnlockStatus + .map { it.isUnlocked } + .stateIn( + scope = applicationScope, + started = SharingStarted.WhileSubscribed(), + initialValue = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked, + ) /** * Whether the device has been entered (i.e. the lockscreen has been dismissed, by any method). @@ -100,17 +102,6 @@ constructor( ) /** - * Whether the user is currently authenticated by a TrustAgent like trusted device, location, - * etc., or by face auth. - */ - private val isPassivelyAuthenticated = - merge( - trustInteractor.isTrusted, - faceAuthInteractor.authenticated, - ) - .onStart { emit(false) } - - /** * Whether it's currently possible to swipe up to enter the device without requiring * authentication or when the device is already authenticated using a passive authentication * mechanism like face or trust manager. This returns `false` whenever the lockscreen has been @@ -129,10 +120,13 @@ constructor( authenticationInteractor.authenticationMethod.map { it == AuthenticationMethodModel.None && repository.isLockscreenEnabled() }, - isPassivelyAuthenticated, + deviceUnlockedInteractor.deviceUnlockStatus, isDeviceEntered - ) { isSwipeAuthMethod, isPassivelyAuthenticated, isDeviceEntered -> - (isSwipeAuthMethod || isPassivelyAuthenticated) && !isDeviceEntered + ) { isSwipeAuthMethod, deviceUnlockStatus, isDeviceEntered -> + (isSwipeAuthMethod || + (deviceUnlockStatus.isUnlocked && + deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) && + !isDeviceEntered } .stateIn( scope = applicationScope, @@ -235,7 +229,8 @@ constructor( * `false` if the device can be entered without authenticating first. */ suspend fun isAuthenticationRequired(): Boolean { - return !isUnlocked.value && authenticationInteractor.getAuthenticationMethod().isSecure + return !deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked && + authenticationInteractor.getAuthenticationMethod().isSecure } /** @@ -246,18 +241,6 @@ constructor( */ val isBypassEnabled: StateFlow<Boolean> = repository.isBypassEnabled - init { - if (flags.isEnabled()) { - applicationScope.launch { - authenticationInteractor.onAuthenticationResult.collectLatest { isSuccessful -> - if (isSuccessful) { - repository.reportSuccessfulAuthentication() - } - } - } - } - } - private val wasRebootedForMainlineUpdate get() = systemPropertiesHelper.get(SYS_BOOT_REASON_PROP) == REBOOT_MAINLINE_UPDATE diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index b0495fb8e819..098ede30d618 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -21,13 +21,23 @@ import com.android.systemui.authentication.shared.model.AuthenticationMethodMode import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository +import com.android.systemui.deviceentry.shared.model.DeviceUnlockSource +import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus +import com.android.systemui.keyguard.domain.interactor.TrustInteractor +import com.android.systemui.power.domain.interactor.PowerInteractor import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.stateIn +@OptIn(ExperimentalCoroutinesApi::class) @SysUISingleton class DeviceUnlockedInteractor @Inject @@ -35,28 +45,63 @@ constructor( @Application private val applicationScope: CoroutineScope, authenticationInteractor: AuthenticationInteractor, deviceEntryRepository: DeviceEntryRepository, + trustInteractor: TrustInteractor, + faceAuthInteractor: DeviceEntryFaceAuthInteractor, + fingerprintAuthInteractor: DeviceEntryFingerprintAuthInteractor, + private val powerInteractor: PowerInteractor, ) { + private val deviceUnlockSource = + merge( + fingerprintAuthInteractor.fingerprintSuccess.map { DeviceUnlockSource.Fingerprint }, + faceAuthInteractor.authenticated + .filter { it } + .map { + if (deviceEntryRepository.isBypassEnabled.value) { + DeviceUnlockSource.FaceWithBypass + } else { + DeviceUnlockSource.FaceWithoutBypass + } + }, + trustInteractor.isTrusted.filter { it }.map { DeviceUnlockSource.TrustAgent }, + authenticationInteractor.onAuthenticationResult + .filter { it } + .map { DeviceUnlockSource.BouncerInput } + ) + /** - * Whether the device is unlocked. + * Whether the device is unlocked or not, along with the information about the authentication + * method that was used to unlock the device. * * A device that is not yet unlocked requires unlocking by completing an authentication * challenge according to the current authentication method, unless in cases when the current * authentication method is not "secure" (for example, None and Swipe); in such cases, the value - * of this flow will always be `true`, even if the lockscreen is showing and still needs to be - * dismissed by the user to proceed. + * of this flow will always be an instance of [DeviceUnlockStatus] with + * [DeviceUnlockStatus.deviceUnlockSource] as null and [DeviceUnlockStatus.isUnlocked] set to + * true, even if the lockscreen is showing and still needs to be dismissed by the user to + * proceed. */ - val isDeviceUnlocked: StateFlow<Boolean> = - combine( - deviceEntryRepository.isUnlocked, - authenticationInteractor.authenticationMethod, - ) { isUnlocked, authenticationMethod -> - (!authenticationMethod.isSecure || isUnlocked) && - authenticationMethod != AuthenticationMethodModel.Sim + val deviceUnlockStatus: StateFlow<DeviceUnlockStatus> = + authenticationInteractor.authenticationMethod + .flatMapLatest { authMethod -> + if (!authMethod.isSecure) { + flowOf(DeviceUnlockStatus(true, null)) + } else if (authMethod == AuthenticationMethodModel.Sim) { + // Device is locked if SIM is locked. + flowOf(DeviceUnlockStatus(false, null)) + } else { + powerInteractor.isAsleep.flatMapLatest { isAsleep -> + if (isAsleep) { + flowOf(DeviceUnlockStatus(false, null)) + } else { + deviceUnlockSource.map { DeviceUnlockStatus(true, it) } + } + } + } } .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, - initialValue = false, + initialValue = DeviceUnlockStatus(false, null), ) } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt new file mode 100644 index 000000000000..619c2400ec72 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockSource.kt @@ -0,0 +1,32 @@ +/* + * 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.deviceentry.shared.model + +/** + * Source of the device unlock. + * + * @property dismissesLockscreen whether unlock with this authentication method dismisses the + * lockscreen and enters the device. + */ +sealed class DeviceUnlockSource(val dismissesLockscreen: Boolean) { + + data object Fingerprint : DeviceUnlockSource(true) + data object FaceWithBypass : DeviceUnlockSource(dismissesLockscreen = true) + data object FaceWithoutBypass : DeviceUnlockSource(dismissesLockscreen = false) + data object TrustAgent : DeviceUnlockSource(dismissesLockscreen = false) + data object BouncerInput : DeviceUnlockSource(dismissesLockscreen = true) +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt new file mode 100644 index 000000000000..f694c331bd7a --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/shared/model/DeviceUnlockStatus.kt @@ -0,0 +1,36 @@ +/* + * 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.deviceentry.shared.model + +/** + * Wrapper class that combines whether device is unlocked or not, along with the authentication + * method used to unlock the device. + * + * @property isUnlocked whether device is unlocked or not. + * @property deviceUnlockSource source that unlocked the device, null if lockscreen is not secure or + * if [isUnlocked] is false. + */ +data class DeviceUnlockStatus( + val isUnlocked: Boolean, + val deviceUnlockSource: DeviceUnlockSource? +) { + init { + assert(isUnlocked || deviceUnlockSource == null) { + "deviceUnlockSource must be null when device is locked." + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java index 926f7f1aee48..75c50fd5f586 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java @@ -19,6 +19,7 @@ package com.android.systemui.dreams.touch; import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING; import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_OPENING; import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.SWIPE_TO_BOUNCER_START_REGION; +import static com.android.systemui.dreams.touch.dagger.BouncerSwipeModule.MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; @@ -81,6 +82,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { private final LockPatternUtils mLockPatternUtils; private final UserTracker mUserTracker; private final float mBouncerZoneScreenPercentage; + private final float mMinBouncerZoneScreenPercentage; private final ScrimManager mScrimManager; private ScrimController mCurrentScrimController; @@ -222,6 +224,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { @Named(SWIPE_TO_BOUNCER_FLING_ANIMATION_UTILS_CLOSING) FlingAnimationUtils flingAnimationUtilsClosing, @Named(SWIPE_TO_BOUNCER_START_REGION) float swipeRegionPercentage, + @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) float minRegionPercentage, UiEventLogger uiEventLogger) { mCentralSurfaces = centralSurfaces; mScrimManager = scrimManager; @@ -229,6 +232,7 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { mLockPatternUtils = lockPatternUtils; mUserTracker = userTracker; mBouncerZoneScreenPercentage = swipeRegionPercentage; + mMinBouncerZoneScreenPercentage = minRegionPercentage; mFlingAnimationUtils = flingAnimationUtils; mFlingAnimationUtilsClosing = flingAnimationUtilsClosing; mValueAnimatorCreator = valueAnimatorCreator; @@ -237,24 +241,27 @@ public class BouncerSwipeTouchHandler implements DreamTouchHandler { } @Override - public void getTouchInitiationRegion(Rect bounds, Region region) { + public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) { final int width = bounds.width(); final int height = bounds.height(); - - if (mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false)) { - region.op(new Rect(0, 0, width, - Math.round( - height * mBouncerZoneScreenPercentage)), - Region.Op.UNION); - } else { - region.op(new Rect(0, - Math.round(height * (1 - mBouncerZoneScreenPercentage)), - width, - height), - Region.Op.UNION); + final float minBouncerHeight = height * mMinBouncerZoneScreenPercentage; + final int minAllowableBottom = Math.round(height * (1 - mMinBouncerZoneScreenPercentage)); + + final boolean isBouncerShowing = + mCentralSurfaces.map(CentralSurfaces::isBouncerShowing).orElse(false); + final Rect normalRegion = isBouncerShowing + ? new Rect(0, 0, width, Math.round(height * mBouncerZoneScreenPercentage)) + : new Rect(0, Math.round(height * (1 - mBouncerZoneScreenPercentage)), + width, height); + + if (!isBouncerShowing && exclusionRect != null) { + int lowestBottom = Math.min(Math.max(0, exclusionRect.bottom), minAllowableBottom); + normalRegion.top = Math.max(normalRegion.top, lowestBottom); } + region.union(normalRegion); } + @Override public void onSessionStart(TouchSession session) { mVelocityTracker = mVelocityTrackerFactory.obtain(); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java index e5c705f608b9..13588c2d45fe 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/CommunalTouchHandler.java @@ -87,7 +87,7 @@ public class CommunalTouchHandler implements DreamTouchHandler { } @Override - public void getTouchInitiationRegion(Rect bounds, Region region) { + public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) { final Rect outBounds = new Rect(bounds); outBounds.inset(outBounds.width() - mInitiationWidth, 0, 0, 0); region.op(outBounds, Region.Op.UNION); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java index 55a9c0c4de99..3b22b31de121 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitor.java @@ -18,9 +18,15 @@ package com.android.systemui.dreams.touch; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; +import static com.android.systemui.shared.Flags.bouncerAreaExclusion; + import android.graphics.Rect; import android.graphics.Region; +import android.os.RemoteException; +import android.util.Log; import android.view.GestureDetector; +import android.view.ISystemGestureExclusionListener; +import android.view.IWindowManager; import android.view.InputEvent; import android.view.MotionEvent; @@ -31,6 +37,8 @@ import androidx.lifecycle.Lifecycle; import androidx.lifecycle.LifecycleObserver; import androidx.lifecycle.LifecycleOwner; +import com.android.systemui.dagger.qualifiers.Background; +import com.android.systemui.dagger.qualifiers.DisplayId; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.touch.dagger.InputSessionComponent; import com.android.systemui.shared.system.InputChannelCompat; @@ -58,8 +66,23 @@ import javax.inject.Inject; public class DreamOverlayTouchMonitor { // This executor is used to protect {@code mActiveTouchSessions} from being modified // concurrently. Any operation that adds or removes values should use this executor. - private final Executor mExecutor; + public String TAG = "DreamOverlayTouchMonitor"; + private final Executor mMainExecutor; + private final Executor mBackgroundExecutor; private final Lifecycle mLifecycle; + private Rect mExclusionRect = null; + + private ISystemGestureExclusionListener mGestureExclusionListener = + new ISystemGestureExclusionListener.Stub() { + @Override + public void onSystemGestureExclusionChanged(int displayId, + Region systemGestureExclusion, + Region systemGestureExclusionUnrestricted) { + mExclusionRect = systemGestureExclusion.getBounds(); + } + }; + + /** * Adds a new {@link TouchSessionImpl} to participate in receiving future touches and gestures. @@ -67,7 +90,7 @@ public class DreamOverlayTouchMonitor { private ListenableFuture<DreamTouchHandler.TouchSession> push( TouchSessionImpl touchSessionImpl) { return CallbackToFutureAdapter.getFuture(completer -> { - mExecutor.execute(() -> { + mMainExecutor.execute(() -> { if (!mActiveTouchSessions.remove(touchSessionImpl)) { completer.set(null); return; @@ -90,7 +113,7 @@ public class DreamOverlayTouchMonitor { private ListenableFuture<DreamTouchHandler.TouchSession> pop( TouchSessionImpl touchSessionImpl) { return CallbackToFutureAdapter.getFuture(completer -> { - mExecutor.execute(() -> { + mMainExecutor.execute(() -> { if (mActiveTouchSessions.remove(touchSessionImpl)) { touchSessionImpl.onRemoved(); @@ -240,6 +263,17 @@ public class DreamOverlayTouchMonitor { */ private void startMonitoring() { stopMonitoring(true); + if (bouncerAreaExclusion()) { + mBackgroundExecutor.execute(() -> { + try { + mWindowManagerService.registerSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + } catch (RemoteException e) { + // Handle the exception + Log.e(TAG, "Failed to register gesture exclusion listener", e); + } + }); + } mCurrentInputSession = mInputSessionFactory.create( "dreamOverlay", mInputEventListener, @@ -252,6 +286,18 @@ public class DreamOverlayTouchMonitor { * Destroys any active {@link InputSession}. */ private void stopMonitoring(boolean force) { + mExclusionRect = null; + if (bouncerAreaExclusion()) { + mBackgroundExecutor.execute(() -> { + try { + mWindowManagerService.unregisterSystemGestureExclusionListener( + mGestureExclusionListener, mDisplayId); + } catch (RemoteException e) { + // Handle the exception + Log.e(TAG, "unregisterSystemGestureExclusionListener: failed", e); + } + }); + } if (mCurrentInputSession == null) { return; } @@ -263,7 +309,7 @@ public class DreamOverlayTouchMonitor { // When we stop monitoring touches, we must ensure that all active touch sessions and // descendants informed of the removal so any cleanup for active tracking can proceed. - mExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> { + mMainExecutor.execute(() -> mActiveTouchSessions.forEach(touchSession -> { while (touchSession != null) { touchSession.onRemoved(); touchSession = touchSession.getPredecessor(); @@ -295,11 +341,15 @@ public class DreamOverlayTouchMonitor { if (!handler.isEnabled()) { continue; } - final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(), - TYPE_APPLICATION_OVERLAY); - - final Region initiationRegion = Region.obtain(); - handler.getTouchInitiationRegion(maxBounds, initiationRegion); + final Rect maxBounds = mDisplayHelper.getMaxBounds(ev.getDisplayId(), + TYPE_APPLICATION_OVERLAY); + final Region initiationRegion = Region.obtain(); + Rect exclusionRect = null; + if (bouncerAreaExclusion()) { + exclusionRect = getCurrentExclusionRect(); + } + handler.getTouchInitiationRegion( + maxBounds, initiationRegion, exclusionRect); if (!initiationRegion.isEmpty()) { // Initiation regions require a motion event to determine pointer location @@ -335,6 +385,9 @@ public class DreamOverlayTouchMonitor { .flatMap(Collection::stream) .forEach(inputEventListener -> inputEventListener.onInputEvent(ev)); } + private Rect getCurrentExclusionRect() { + return mExclusionRect; + } }; /** @@ -416,6 +469,9 @@ public class DreamOverlayTouchMonitor { private InputSessionComponent.Factory mInputSessionFactory; private InputSession mCurrentInputSession; + private final int mDisplayId; + private final IWindowManager mWindowManagerService; + /** * Designated constructor for {@link DreamOverlayTouchMonitor} @@ -432,15 +488,21 @@ public class DreamOverlayTouchMonitor { @Inject public DreamOverlayTouchMonitor( @Main Executor executor, + @Background Executor backgroundExecutor, Lifecycle lifecycle, InputSessionComponent.Factory inputSessionFactory, DisplayHelper displayHelper, - Set<DreamTouchHandler> handlers) { + Set<DreamTouchHandler> handlers, + IWindowManager windowManagerService, + @DisplayId int displayId) { + mDisplayId = displayId; mHandlers = handlers; mInputSessionFactory = inputSessionFactory; - mExecutor = executor; + mMainExecutor = executor; + mBackgroundExecutor = backgroundExecutor; mLifecycle = lifecycle; mDisplayHelper = displayHelper; + mWindowManagerService = windowManagerService; } /** diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java index 72ad45d1055c..1ec000835ad2 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/DreamTouchHandler.java @@ -104,7 +104,7 @@ public interface DreamTouchHandler { * indicating the entire screen should be considered. * @param region A {@link Region} that is passed in to the target entry touch region. */ - default void getTouchInitiationRegion(Rect bounds, Region region) { + default void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) { } /** diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java index 6f05e83b22ba..e0bf52e81875 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/ShadeTouchHandler.java @@ -82,7 +82,7 @@ public class ShadeTouchHandler implements DreamTouchHandler { } @Override - public void getTouchInitiationRegion(Rect bounds, Region region) { + public void getTouchInitiationRegion(Rect bounds, Region region, Rect exclusionRect) { final Rect outBounds = new Rect(bounds); outBounds.inset(0, 0, 0, outBounds.height() - mInitiationHeight); region.op(outBounds, Region.Op.UNION); diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java index 8cf11a9817b7..a5db2ff81f99 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/dagger/BouncerSwipeModule.java @@ -21,10 +21,10 @@ import android.content.res.Resources; import android.util.TypedValue; import android.view.VelocityTracker; -import com.android.systemui.res.R; import com.android.systemui.dagger.qualifiers.Main; import com.android.systemui.dreams.touch.BouncerSwipeTouchHandler; import com.android.systemui.dreams.touch.DreamTouchHandler; +import com.android.systemui.res.R; import com.android.systemui.shade.ShadeViewController; import com.android.wm.shell.animation.FlingAnimationUtils; @@ -46,6 +46,9 @@ public class BouncerSwipeModule { */ public static final String SWIPE_TO_BOUNCER_START_REGION = "swipe_to_bouncer_start_region"; + public static final String MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE = + "min_bouncer_zone_screen_percentage"; + /** * The {@link android.view.animation.AnimationUtils} for animating the bouncer closing. */ @@ -110,6 +113,18 @@ public class BouncerSwipeModule { } /** + * Provides the minimum region to start wipe gestures from. + */ + @Provides + @Named(MIN_BOUNCER_ZONE_SCREEN_PERCENTAGE) + public static float providesMinBouncerZoneScreenPercentage(@Main Resources resources) { + TypedValue typedValue = new TypedValue(); + resources.getValue(R.dimen.dream_overlay_bouncer_min_region_screen_percentage, + typedValue, true); + return typedValue.getFloat(); + } + + /** * Provides the default {@link BouncerSwipeTouchHandler.ValueAnimatorCreator}, which is simply * a wrapper around {@link ValueAnimator}. */ diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt index 73878b6780d9..640534cc9d34 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt @@ -410,13 +410,6 @@ object Flags { val CLIPBOARD_SHARED_TRANSITIONS = unreleasedFlag("clipboard_shared_transitions", teamfood = true) - /** - * Whether the compose bouncer is enabled. This ensures ProGuard can - * remove unused code from our APK at compile time. - */ - // TODO(b/280877228): Tracking Bug - @JvmField val COMPOSE_BOUNCER_ENABLED = false - // 1900 @JvmField val NOTE_TASKS = releasedFlag("keycode_flag") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index f128a80191df..2182fe378d2f 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -193,7 +193,7 @@ constructor( val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing /** Whether the keyguard is dismissible or not. */ - val isKeyguardDismissible: Flow<Boolean> = repository.isKeyguardDismissible + val isKeyguardDismissible: StateFlow<Boolean> = repository.isKeyguardDismissible /** Whether the keyguard is occluded (covered by an activity). */ @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED") diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt index 03ed5675a77a..4abd6c6a6453 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardOcclusionInteractor.kt @@ -107,7 +107,7 @@ constructor( */ val occludingActivityWillDismissKeyguard: StateFlow<Boolean> = if (SceneContainerFlag.isEnabled) { - deviceUnlockedInteractor.get().isDeviceUnlocked + deviceUnlockedInteractor.get().deviceUnlockStatus.map { it.isUnlocked } } else { keyguardInteractor.isKeyguardDismissible } 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 e861ddf69aa6..da9e00ddb6c2 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java @@ -16,11 +16,8 @@ package com.android.systemui.mediaprojection.permission; -import static android.Manifest.permission.LOG_COMPAT_CHANGE; -import static android.Manifest.permission.READ_COMPAT_CHANGE_CONFIG; import static android.media.projection.IMediaProjectionManager.EXTRA_PACKAGE_REUSING_GRANTED_CONSENT; import static android.media.projection.IMediaProjectionManager.EXTRA_USER_REVIEW_GRANTED_CONSENT; -import static android.media.projection.MediaProjectionManager.OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION; import static android.media.projection.ReviewGrantedConsentResult.RECORD_CANCEL; import static android.media.projection.ReviewGrantedConsentResult.RECORD_CONTENT_DISPLAY; import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS; @@ -29,13 +26,11 @@ import static com.android.systemui.mediaprojection.permission.ScreenShareOptionK import static com.android.systemui.mediaprojection.permission.ScreenShareOptionKt.SINGLE_APP; import android.annotation.Nullable; -import android.annotation.RequiresPermission; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions.LaunchCookie; import android.app.AlertDialog; import android.app.StatusBarManager; -import android.app.compat.CompatChanges; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -113,7 +108,6 @@ public class MediaProjectionPermissionActivity extends Activity } @Override - @RequiresPermission(allOf = {READ_COMPAT_CHANGE_CONFIG, LOG_COMPAT_CHANGE}) public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -241,10 +235,6 @@ public class MediaProjectionPermissionActivity extends Activity // the correct screen width when in split screen. Context dialogContext = getApplicationContext(); if (isPartialScreenSharingEnabled()) { - final boolean overrideDisableSingleAppOption = - CompatChanges.isChangeEnabled( - OVERRIDE_DISABLE_MEDIA_PROJECTION_SINGLE_APP_OPTION, - mPackageName, getHostUserHandle()); MediaProjectionPermissionDialogDelegate delegate = new MediaProjectionPermissionDialogDelegate( dialogContext, @@ -256,7 +246,6 @@ public class MediaProjectionPermissionActivity extends Activity }, () -> finish(RECORD_CANCEL, /* projection= */ null), appName, - overrideDisableSingleAppOption, mUid, mMediaProjectionMetricsLogger); mDialog = diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt index 8858041ae529..0f54e934f3cf 100644 --- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt +++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegate.kt @@ -30,12 +30,11 @@ class MediaProjectionPermissionDialogDelegate( private val onStartRecordingClicked: Consumer<MediaProjectionPermissionDialogDelegate>, private val onCancelClicked: Runnable, private val appName: String?, - private val forceShowPartialScreenshare: Boolean, hostUid: Int, mediaProjectionMetricsLogger: MediaProjectionMetricsLogger, ) : BaseMediaProjectionPermissionDialogDelegate<AlertDialog>( - createOptionList(context, appName, mediaProjectionConfig, forceShowPartialScreenshare), + createOptionList(context, appName, mediaProjectionConfig), appName, hostUid, mediaProjectionMetricsLogger @@ -66,8 +65,7 @@ class MediaProjectionPermissionDialogDelegate( private fun createOptionList( context: Context, appName: String?, - mediaProjectionConfig: MediaProjectionConfig?, - overrideDisableSingleAppOption: Boolean = false, + mediaProjectionConfig: MediaProjectionConfig? ): List<ScreenShareOption> { val singleAppWarningText = if (appName == null) { @@ -82,13 +80,8 @@ class MediaProjectionPermissionDialogDelegate( R.string.media_projection_entry_app_permission_dialog_warning_entire_screen } - // The single app option should only be disabled if there is an app name provided, - // the client has setup a MediaProjection with - // MediaProjectionConfig#createConfigForDefaultDisplay, AND it hasn't been overridden by - // the OVERRIDE_DISABLE_SINGLE_APP_OPTION per-app override. val singleAppOptionDisabled = appName != null && - !overrideDisableSingleAppOption && mediaProjectionConfig?.regionToCapture == MediaProjectionConfig.CAPTURE_REGION_FIXED_DISPLAY diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java index 4fe3a11078db..ade56c435421 100644 --- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java +++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java @@ -85,6 +85,7 @@ import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewTreeObserver; import android.view.ViewTreeObserver.InternalInsetsInfo; import android.view.ViewTreeObserver.OnComputeInternalInsetsListener; @@ -186,6 +187,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements /** Allow some time inbetween the long press for back and recents. */ private static final int LOCK_TO_APP_GESTURE_TOLERANCE = 200; private static final long AUTODIM_TIMEOUT_MS = 2250; + private static final float QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON = 3f; private final Context mContext; private final Bundle mSavedState; @@ -223,6 +225,7 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private final int mNavColorSampleMargin; private EdgeBackGestureHandler mEdgeBackGestureHandler; private NavigationBarFrame mFrame; + private MotionEvent mCurrentDownEvent; private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING; @@ -238,6 +241,8 @@ public class NavigationBar extends ViewController<NavigationBarView> implements private int mLayoutDirection; private Optional<Long> mHomeButtonLongPressDurationMs; + private Optional<Long> mOverrideHomeButtonLongPressDurationMs = Optional.empty(); + private Optional<Float> mOverrideHomeButtonLongPressSlopMultiplier = Optional.empty(); /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */ private @Appearance int mAppearance; @@ -405,6 +410,25 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } @Override + public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) { + mOverrideHomeButtonLongPressDurationMs = Optional.of(duration) + .filter(value -> value > 0); + mOverrideHomeButtonLongPressSlopMultiplier = Optional.of(slopMultiplier) + .filter(value -> value > 0); + if (mOverrideHomeButtonLongPressDurationMs.isPresent()) { + Log.d(TAG, "Receive duration override: " + + mOverrideHomeButtonLongPressDurationMs.get()); + } + if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) { + Log.d(TAG, "Receive slop multiplier override: " + + mOverrideHomeButtonLongPressSlopMultiplier.get()); + } + if (mView != null) { + reconfigureHomeLongClick(); + } + } + + @Override public void onHomeRotationEnabled(boolean enabled) { mView.getRotationButtonController().setHomeRotationEnabled(enabled); } @@ -1016,7 +1040,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements if (mView.getHomeButton().getCurrentView() == null) { return; } - if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) { + if (mHomeButtonLongPressDurationMs.isPresent() + || mOverrideHomeButtonLongPressDurationMs.isPresent() + || mOverrideHomeButtonLongPressSlopMultiplier.isPresent() + || !mLongPressHomeEnabled) { mView.getHomeButton().getCurrentView().setLongClickable(false); mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false); mView.getHomeButton().setOnLongClickListener(null); @@ -1038,6 +1065,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements pw.println(" mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation); pw.println(" mCurrentRotation=" + mCurrentRotation); pw.println(" mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs); + pw.println(" mOverrideHomeButtonLongPressDurationMs=" + + mOverrideHomeButtonLongPressDurationMs); + pw.println(" mOverrideHomeButtonLongPressSlopMultiplier=" + + mOverrideHomeButtonLongPressSlopMultiplier); pw.println(" mLongPressHomeEnabled=" + mLongPressHomeEnabled); pw.println(" mNavigationBarWindowState=" + windowStateToString(mNavigationBarWindowState)); @@ -1331,6 +1362,10 @@ public class NavigationBar extends ViewController<NavigationBarView> implements final Optional<CentralSurfaces> centralSurfacesOptional = mCentralSurfacesOptionalLazy.get(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: + if (mCurrentDownEvent != null) { + mCurrentDownEvent.recycle(); + } + mCurrentDownEvent = MotionEvent.obtain(event); mHomeBlockedThisTouch = false; if (mTelecomManagerOptional.isPresent() && mTelecomManagerOptional.get().isRinging()) { @@ -1342,9 +1377,45 @@ public class NavigationBar extends ViewController<NavigationBarView> implements } } if (mLongPressHomeEnabled) { - mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> { - mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration); - }); + if (mOverrideHomeButtonLongPressDurationMs.isPresent()) { + Log.d(TAG, "ACTION_DOWN Launcher override duration: " + + mOverrideHomeButtonLongPressDurationMs.get()); + mHandler.postDelayed(mOnVariableDurationHomeLongClick, + mOverrideHomeButtonLongPressDurationMs.get()); + } else if (mOverrideHomeButtonLongPressSlopMultiplier.isPresent()) { + // If override timeout doesn't exist but override touch slop exists, we use + // system default long press duration + Log.d(TAG, "ACTION_DOWN default duration: " + + ViewConfiguration.getLongPressTimeout()); + mHandler.postDelayed(mOnVariableDurationHomeLongClick, + ViewConfiguration.getLongPressTimeout()); + } else { + mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> { + Log.d(TAG, "ACTION_DOWN original duration: " + longPressDuration); + mHandler.postDelayed(mOnVariableDurationHomeLongClick, + longPressDuration); + }); + } + } + break; + case MotionEvent.ACTION_MOVE: + if (!mHandler.hasCallbacks(mOnVariableDurationHomeLongClick)) { + Log.w(TAG, "No callback. Don't handle touch slop."); + break; + } + float customSlopMultiplier = mOverrideHomeButtonLongPressSlopMultiplier.orElse(1f); + float touchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop(); + float calculatedTouchSlop = + customSlopMultiplier * QUICKSTEP_TOUCH_SLOP_RATIO_TWO_BUTTON * touchSlop; + float touchSlopSquared = calculatedTouchSlop * calculatedTouchSlop; + + float dx = event.getX() - mCurrentDownEvent.getX(); + float dy = event.getY() - mCurrentDownEvent.getY(); + double distanceSquared = (dx * dx) + (dy * dy); + if (distanceSquared > touchSlopSquared) { + Log.i(TAG, "Touch slop passed. Abort."); + mView.abortCurrentGesture(); + mHandler.removeCallbacks(mOnVariableDurationHomeLongClick); } break; case MotionEvent.ACTION_UP: diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java index dc42b5c35223..b27b974dc972 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/CastTile.java @@ -63,6 +63,7 @@ import com.android.systemui.statusbar.policy.CastController; import com.android.systemui.statusbar.policy.CastController.CastDevice; import com.android.systemui.statusbar.policy.HotspotController; import com.android.systemui.statusbar.policy.KeyguardStateController; +import com.android.systemui.util.DialogKt; import java.util.ArrayList; import java.util.List; @@ -245,6 +246,10 @@ public class CastTile extends QSTileImpl<BooleanState> { new DialogCuj(InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN, INTERACTION_JANK_TAG)); } else { + if (dialog.getWindow() != null) { + DialogKt.registerAnimationOnBackInvoked(dialog, + dialog.getWindow().getDecorView()); + } dialog.show(); } }); @@ -272,7 +277,7 @@ public class CastTile extends QSTileImpl<BooleanState> { state.secondaryLabel = getDeviceName(device); state.stateDescription = state.stateDescription + "," + mContext.getString( - R.string.accessibility_cast_name, state.label); + R.string.accessibility_cast_name, state.label); connecting = false; break; } else if (device.state == CastDevice.STATE_CONNECTING) { @@ -342,14 +347,14 @@ public class CastTile extends QSTileImpl<BooleanState> { }; private final SignalCallback mSignalCallback = new SignalCallback() { - @Override - public void setWifiIndicators(@NonNull WifiIndicators indicators) { - // statusIcon.visible has the connected status information - boolean enabledAndConnected = indicators.enabled - && (indicators.qsIcon != null && indicators.qsIcon.visible); - setCastTransportAllowed(enabledAndConnected); - } - }; + @Override + public void setWifiIndicators(@NonNull WifiIndicators indicators) { + // statusIcon.visible has the connected status information + boolean enabledAndConnected = indicators.enabled + && (indicators.qsIcon != null && indicators.qsIcon.visible); + setCastTransportAllowed(enabledAndConnected); + } + }; private final HotspotController.Callback mHotspotCallback = new HotspotController.Callback() { diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt new file mode 100644 index 000000000000..7117629622e6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyToggleTileDataInteractor.kt @@ -0,0 +1,114 @@ +/* + * 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.sensorprivacy + +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE +import android.hardware.SensorPrivacyManager.Sensors.Sensor +import android.os.UserHandle +import android.provider.DeviceConfig +import android.util.Log +import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow +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.sensorprivacy.domain.model.SensorPrivacyToggleTileModel +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import kotlin.coroutines.CoroutineContext +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.onStart +import kotlinx.coroutines.withContext + +/** Observes SensorPrivacyToggle mode state changes providing the [SensorPrivacyToggleTileModel]. */ +class SensorPrivacyToggleTileDataInteractor +@AssistedInject +constructor( + @Background private val bgCoroutineContext: CoroutineContext, + private val privacyController: IndividualSensorPrivacyController, + @Assisted @Sensor private val sensorId: Int, +) : QSTileDataInteractor<SensorPrivacyToggleTileModel> { + @AssistedFactory + interface Factory { + fun create(@Sensor id: Int): SensorPrivacyToggleTileDataInteractor + } + + override fun tileData( + user: UserHandle, + triggers: Flow<DataUpdateTrigger> + ): Flow<SensorPrivacyToggleTileModel> = + conflatedCallbackFlow { + val callback = + IndividualSensorPrivacyController.Callback { sensor, blocked -> + if (sensor == sensorId) trySend(SensorPrivacyToggleTileModel(blocked)) + } + privacyController.addCallback(callback) // does not emit an initial state + awaitClose { privacyController.removeCallback(callback) } + } + .onStart { + emit(SensorPrivacyToggleTileModel(privacyController.isSensorBlocked(sensorId))) + } + .distinctUntilChanged() + .flowOn(bgCoroutineContext) + + override fun availability(user: UserHandle) = + flow { emit(isAvailable()) }.flowOn(bgCoroutineContext) + + private suspend fun isAvailable(): Boolean { + return privacyController.supportsSensorToggle(sensorId) && isSensorDeviceConfigSet() + } + + private suspend fun isSensorDeviceConfigSet(): Boolean = + withContext(bgCoroutineContext) { + try { + val deviceConfigName = getDeviceConfigName(sensorId) + return@withContext DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_PRIVACY, + deviceConfigName, + true + ) + } catch (exception: IllegalArgumentException) { + Log.w( + TAG, + "isDeviceConfigSet for sensorId $sensorId: " + + "Defaulting to true due to exception. ", + exception + ) + return@withContext true + } + } + + private fun getDeviceConfigName(sensorId: Int): String { + if (sensorId == MICROPHONE) { + return "mic_toggle_enabled" + } else if (sensorId == CAMERA) { + return "camera_toggle_enabled" + } else { + throw IllegalArgumentException("getDeviceConfigName: unexpected sensorId: $sensorId") + } + } + + private companion object { + const val TAG = "SensorPrivacyToggleTileException" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt new file mode 100644 index 000000000000..9711cb81e2c0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/SensorPrivacyToggleTileUserActionInteractor.kt @@ -0,0 +1,87 @@ +/* + * 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.sensorprivacy.domain + +import android.content.Intent +import android.hardware.SensorPrivacyManager.Sensors.Sensor +import android.hardware.SensorPrivacyManager.Sources.QS_TILE +import android.provider.Settings +import android.safetycenter.SafetyCenterManager +import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor +import com.android.systemui.plugins.ActivityStarter +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.sensorprivacy.domain.model.SensorPrivacyToggleTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction +import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** Handles sensor privacy toggle tile clicks and long clicks. */ +class SensorPrivacyToggleTileUserActionInteractor +@AssistedInject +constructor( + private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler, + private val keyguardInteractor: KeyguardInteractor, + private val activityStarter: ActivityStarter, + private val sensorPrivacyController: IndividualSensorPrivacyController, + private val safetyCenterManager: SafetyCenterManager, + @Assisted @Sensor private val sensorId: Int, +) : QSTileUserActionInteractor<SensorPrivacyToggleTileModel> { + @AssistedFactory + interface Factory { + fun create(@Sensor id: Int): SensorPrivacyToggleTileUserActionInteractor + } + + // should only be initialized in code known to run in background thread + private lateinit var longClickIntent: Intent + + override suspend fun handleInput(input: QSTileInput<SensorPrivacyToggleTileModel>) = + with(input) { + when (action) { + is QSTileUserAction.Click -> { + val blocked = input.data.isBlocked + if ( + sensorPrivacyController.requiresAuthentication() && + keyguardInteractor.isKeyguardDismissible.value && + keyguardInteractor.isKeyguardShowing() + ) { + activityStarter.postQSRunnableDismissingKeyguard { + sensorPrivacyController.setSensorBlocked(QS_TILE, sensorId, !blocked) + } + return + } + sensorPrivacyController.setSensorBlocked(QS_TILE, sensorId, !blocked) + } + is QSTileUserAction.LongClick -> { + if (!::longClickIntent.isInitialized) { + longClickIntent = + Intent( + if (safetyCenterManager.isSafetyCenterEnabled) { + Settings.ACTION_PRIVACY_CONTROLS + } else { + Settings.ACTION_PRIVACY_SETTINGS + } + ) + } + qsTileIntentUserActionHandler.handle(action.view, longClickIntent) + } + } + } +} diff --git a/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/model/SensorPrivacyToggleTileModel.kt index dc804ca61dcf..04719afafa85 100644 --- a/packages/SystemUI/src-debug/com/android/systemui/util/Compile.java +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/domain/model/SensorPrivacyToggleTileModel.kt @@ -1,5 +1,5 @@ /* - * 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,10 +14,11 @@ * limitations under the License. */ -package com.android.systemui.util; +package com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model -/** Constants that vary by compilation configuration. */ -public class Compile { - /** Whether SystemUI was compiled in debug mode, and supports debug features */ - public static final boolean IS_DEBUG = true; -} +/** + * Sensor privacy toggle tile model. + * + * @param isBlocked is true when the sensor is blocked + */ +@JvmInline value class SensorPrivacyToggleTileModel(val isBlocked: Boolean) diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt new file mode 100644 index 000000000000..2a9fd07a67cb --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyTileResources.kt @@ -0,0 +1,52 @@ +/* + * 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.sensorprivacy.ui + +import com.android.systemui.res.R + +sealed interface SensorPrivacyTileResources { + fun getIconRes(isBlocked: Boolean): Int + fun getTileLabelRes(): Int + + data object CameraPrivacyTileResources : SensorPrivacyTileResources { + override fun getIconRes(isBlocked: Boolean): Int { + return if (isBlocked) { + R.drawable.qs_camera_access_icon_off + } else { + R.drawable.qs_camera_access_icon_on + } + } + + override fun getTileLabelRes(): Int { + return R.string.quick_settings_camera_label + } + } + + data object MicrophonePrivacyTileResources : SensorPrivacyTileResources { + override fun getIconRes(isBlocked: Boolean): Int { + return if (isBlocked) { + R.drawable.qs_mic_access_off + } else { + R.drawable.qs_mic_access_on + } + } + + override fun getTileLabelRes(): Int { + return R.string.quick_settings_mic_label + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.kt new file mode 100644 index 000000000000..52622d26348d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/sensorprivacy/ui/SensorPrivacyToggleTileMapper.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.sensorprivacy.ui + +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.sensorprivacy.domain.model.SensorPrivacyToggleTileModel +import com.android.systemui.qs.tiles.viewmodel.QSTileConfig +import com.android.systemui.qs.tiles.viewmodel.QSTileState +import com.android.systemui.res.R +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +/** Maps [SensorPrivacyToggleTileModel] to [QSTileState]. */ +class SensorPrivacyToggleTileMapper +@AssistedInject +constructor( + @Main private val resources: Resources, + private val theme: Resources.Theme, + @Assisted private val sensorPrivacyTileResources: SensorPrivacyTileResources, +) : QSTileDataToStateMapper<SensorPrivacyToggleTileModel> { + + @AssistedFactory + interface Factory { + fun create( + sensorPrivacyTileResources: SensorPrivacyTileResources + ): SensorPrivacyToggleTileMapper + } + + override fun map(config: QSTileConfig, data: SensorPrivacyToggleTileModel): QSTileState = + QSTileState.build(resources, theme, config.uiConfig) { + label = resources.getString(sensorPrivacyTileResources.getTileLabelRes()) + contentDescription = label + supportedActions = + setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK) + icon = { + Icon.Loaded( + resources.getDrawable( + sensorPrivacyTileResources.getIconRes(data.isBlocked), + theme + ), + null + ) + } + + sideViewIcon = QSTileState.SideViewIcon.None + + if (data.isBlocked) { + activationState = QSTileState.ActivationState.INACTIVE + secondaryLabel = resources.getString(R.string.quick_settings_camera_mic_blocked) + } else { + activationState = QSTileState.ActivationState.ACTIVE + secondaryLabel = resources.getString(R.string.quick_settings_camera_mic_available) + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java index 7c1a2c032bea..f621f11fdaf2 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java +++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java @@ -259,6 +259,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } @Override + public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) { + verifyCallerAndClearCallingIdentityPostMain("setOverrideHomeButtonLongPress", + () -> notifySetOverrideHomeButtonLongPress(duration, slopMultiplier)); + } + + @Override public void onBackPressed() { verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> { sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK); @@ -947,6 +953,12 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis } } + private void notifySetOverrideHomeButtonLongPress(long duration, float slopMultiplier) { + for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) { + mConnectionCallbacks.get(i).setOverrideHomeButtonLongPress(duration, slopMultiplier); + } + } + public void notifyAssistantVisibilityChanged(float visibility) { try { if (mOverviewProxy != null) { @@ -1104,6 +1116,8 @@ public class OverviewProxyService implements CallbackController<OverviewProxyLis default void startAssistant(Bundle bundle) {} default void setAssistantOverridesRequested(int[] invocationTypes) {} default void animateNavBarLongPress(boolean isTouchDown, boolean shrink, long durationMs) {} + /** Set override of home button long press duration and touch slop multiplier. */ + default void setOverrideHomeButtonLongPress(long override, float slopMultiplier) {} } /** diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 75bf131afdf9..2ccd3b9e9f8a 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -162,7 +162,9 @@ constructor( loggingReason: String, transitionKey: TransitionKey? = null, ) { - check(toScene != Scenes.Gone || deviceUnlockedInteractor.isDeviceUnlocked.value) { + check( + toScene != Scenes.Gone || deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked + ) { "Cannot change to the Gone scene while the device is locked. Logging reason for scene" + " change was: $loggingReason" } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index f4292207f3a9..32d72e0bac22 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -34,6 +34,7 @@ import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.DisplayId import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFaceAuthInteractor import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.model.SceneContainerPlugin import com.android.systemui.model.SysUiState @@ -51,6 +52,7 @@ import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNoti import com.android.systemui.statusbar.phone.CentralSurfaces import com.android.systemui.statusbar.policy.domain.interactor.DeviceProvisioningInteractor import com.android.systemui.util.asIndenting +import com.android.systemui.util.kotlin.sample import com.android.systemui.util.printSection import com.android.systemui.util.println import dagger.Lazy @@ -83,6 +85,7 @@ constructor( @Application private val applicationScope: CoroutineScope, private val sceneInteractor: SceneInteractor, private val deviceEntryInteractor: DeviceEntryInteractor, + private val deviceUnlockedInteractor: DeviceUnlockedInteractor, private val bouncerInteractor: BouncerInteractor, private val keyguardInteractor: KeyguardInteractor, private val flags: SceneContainerFlags, @@ -194,39 +197,41 @@ constructor( } } applicationScope.launch { - simBouncerInteractor.get().isAnySimSecure.collect { isAnySimLocked -> - val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value - val isUnlocked = deviceEntryInteractor.isUnlocked.value - - when { - isAnySimLocked -> { - switchToScene( - targetSceneKey = Scenes.Bouncer, - loggingReason = "Need to authenticate locked SIM card." - ) - } - isUnlocked && canSwipeToEnter == false -> { - switchToScene( - targetSceneKey = Scenes.Gone, - loggingReason = - "All SIM cards unlocked and device already" + - " unlocked and lockscreen doesn't require a swipe to dismiss." - ) - } - else -> { - switchToScene( - targetSceneKey = Scenes.Lockscreen, - loggingReason = - "All SIM cards unlocked and device still locked" + - " or lockscreen still requires a swipe to dismiss." - ) + simBouncerInteractor + .get() + .isAnySimSecure + .sample(deviceUnlockedInteractor.deviceUnlockStatus, ::Pair) + .collect { (isAnySimLocked, unlockStatus) -> + when { + isAnySimLocked -> { + switchToScene( + targetSceneKey = Scenes.Bouncer, + loggingReason = "Need to authenticate locked SIM card." + ) + } + unlockStatus.isUnlocked && + deviceEntryInteractor.canSwipeToEnter.value == false -> { + switchToScene( + targetSceneKey = Scenes.Gone, + loggingReason = + "All SIM cards unlocked and device already unlocked and " + + "lockscreen doesn't require a swipe to dismiss." + ) + } + else -> { + switchToScene( + targetSceneKey = Scenes.Lockscreen, + loggingReason = + "All SIM cards unlocked and device still locked" + + " or lockscreen still requires a swipe to dismiss." + ) + } } } - } } applicationScope.launch { - deviceEntryInteractor.isUnlocked - .mapNotNull { isUnlocked -> + deviceUnlockedInteractor.deviceUnlockStatus + .mapNotNull { deviceUnlockStatus -> val renderedScenes = when (val transitionState = sceneInteractor.transitionState.value) { is ObservableTransitionState.Idle -> setOf(transitionState.scene) @@ -238,7 +243,7 @@ constructor( } val isOnLockscreen = renderedScenes.contains(Scenes.Lockscreen) val isOnBouncer = renderedScenes.contains(Scenes.Bouncer) - if (!isUnlocked) { + if (!deviceUnlockStatus.isUnlocked) { return@mapNotNull if (isOnLockscreen || isOnBouncer) { // Already on lockscreen or bouncer, no need to change scenes. null @@ -250,8 +255,6 @@ constructor( } } - val isBypassEnabled = deviceEntryInteractor.isBypassEnabled.value - val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value when { isOnBouncer -> // When the device becomes unlocked in Bouncer, go to Gone. @@ -266,14 +269,12 @@ constructor( // when the unlock state changes indicates this is an active // authentication attempt. when { - isBypassEnabled -> - Scenes.Gone to - "device has been unlocked on lockscreen with bypass" + - " enabled" - canSwipeToEnter == false -> + deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == + true -> Scenes.Gone to - "device has been unlocked on lockscreen using an active" + - " authentication mechanism" + "device has been unlocked on lockscreen with bypass " + + "enabled or using an active authentication " + + "mechanism: ${deviceUnlockStatus.deviceUnlockSource}" else -> null } // Not on lockscreen or bouncer, so remain in the current scene. @@ -297,7 +298,7 @@ constructor( ) } else { val canSwipeToEnter = deviceEntryInteractor.canSwipeToEnter.value - val isUnlocked = deviceEntryInteractor.isUnlocked.value + val isUnlocked = deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked if (isUnlocked && canSwipeToEnter == false) { val isTransitioningToLockscreen = sceneInteractor.transitioningTo.value == Scenes.Lockscreen @@ -429,8 +430,8 @@ constructor( /** Keeps the interaction state of [CentralSurfaces] up-to-date. */ private fun hydrateInteractionState() { applicationScope.launch { - deviceEntryInteractor.isUnlocked - .map { !it } + deviceUnlockedInteractor.deviceUnlockStatus + .map { !it.isUnlocked } .flatMapLatest { isDeviceLocked -> if (isDeviceLocked) { sceneInteractor.transitionState diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt index 9e27dad0ea73..5664d59c7c48 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt @@ -28,6 +28,7 @@ import com.android.systemui.keyguard.KeyguardWmStateRefactor import com.android.systemui.keyguard.MigrateClocksToBlueprint import com.android.systemui.keyguard.shared.ComposeLockscreen import com.android.systemui.media.controls.util.MediaInSceneContainerFlag +import com.android.systemui.statusbar.phone.PredictiveBackSysUiFlag import dagger.Module import dagger.Provides @@ -44,7 +45,8 @@ object SceneContainerFlag { MigrateClocksToBlueprint.isEnabled && ComposeLockscreen.isEnabled && MediaInSceneContainerFlag.isEnabled && - KeyguardWmStateRefactor.isEnabled + KeyguardWmStateRefactor.isEnabled && + PredictiveBackSysUiFlag.isEnabled // NOTE: Changes should also be made in getSecondaryFlags and @EnableSceneContainer /** The main aconfig flag. */ diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt index a1481f6d6d2b..4cf18fb482d8 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/LegacyScreenshotViewProxy.kt @@ -34,9 +34,9 @@ import androidx.appcompat.content.res.AppCompatResources import com.android.internal.logging.UiEventLogger import com.android.systemui.flags.FeatureFlags import com.android.systemui.res.R -import com.android.systemui.screenshot.scroll.ScrollCaptureController import com.android.systemui.screenshot.LogConfig.DEBUG_DISMISS import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_DISMISSED_OTHER +import com.android.systemui.screenshot.scroll.ScrollCaptureController import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject @@ -113,7 +113,7 @@ constructor( override fun setChipIntents(imageData: ScreenshotController.SavedImageData) = view.setChipIntents(imageData) - override fun requestDismissal(event: ScreenshotEvent) { + override fun requestDismissal(event: ScreenshotEvent?) { if (DEBUG_DISMISS) { Log.d(TAG, "screenshot dismissal requested") } @@ -124,7 +124,7 @@ constructor( } return } - logger.log(event, 0, packageName) + event?.let { logger.log(event, 0, packageName) } view.animateDismissal() } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java index bbf7ed529220..49144091cb62 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SaveImageInBackgroundTask.java @@ -165,6 +165,7 @@ class SaveImageInBackgroundTask extends AsyncTask<Void, Void, Void> { mQuickShareData.quickShareAction, mScreenshotId, uri, mImageTime, image, mParams.owner); mImageData.subject = getSubjectString(mImageTime); + mImageData.imageTime = mImageTime; mParams.mActionsReadyListener.onActionsReady(mImageData); if (DEBUG_CALLBACK) { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt index 5019a6fcaf8b..ca0a539d5ee0 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotActionsProvider.kt @@ -17,24 +17,37 @@ package com.android.systemui.screenshot import android.app.ActivityOptions +import android.app.BroadcastOptions import android.app.ExitTransitionCoordinator +import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.os.Process +import android.os.UserHandle +import android.provider.DeviceConfig import android.util.Log import android.util.Pair import androidx.appcompat.content.res.AppCompatResources import com.android.app.tracing.coroutines.launch +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.internal.logging.UiEventLogger import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.log.DebugLogger.debugLog import com.android.systemui.res.R import com.android.systemui.screenshot.ActionIntentCreator.createEdit import com.android.systemui.screenshot.ActionIntentCreator.createShareWithSubject import com.android.systemui.screenshot.ScreenshotController.SavedImageData +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_EDIT_TAPPED +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_PREVIEW_TAPPED +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SHARE_TAPPED +import com.android.systemui.screenshot.ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED import com.android.systemui.screenshot.ui.viewmodel.ActionButtonViewModel import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import java.text.DateFormat +import java.util.Date import kotlinx.coroutines.CoroutineScope /** @@ -48,7 +61,9 @@ interface ScreenshotActionsProvider { interface Factory { fun create( request: ScreenshotData, + requestId: String, windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>, + requestDismissal: () -> Unit, ): ScreenshotActionsProvider } } @@ -59,9 +74,13 @@ constructor( private val context: Context, private val viewModel: ScreenshotViewModel, private val actionExecutor: ActionIntentExecutor, + private val smartActionsProvider: SmartActionsProvider, + private val uiEventLogger: UiEventLogger, @Application private val applicationScope: CoroutineScope, @Assisted val request: ScreenshotData, + @Assisted val requestId: String, @Assisted val windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>, + @Assisted val requestDismissal: () -> Unit, ) : ScreenshotActionsProvider { private var pendingAction: ((SavedImageData) -> Unit)? = null private var result: SavedImageData? = null @@ -70,6 +89,7 @@ constructor( init { viewModel.setPreviewAction { debugLog(LogConfig.DEBUG_ACTIONS) { "Preview tapped" } + uiEventLogger.log(SCREENSHOT_PREVIEW_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createEdit(result.uri, context), true) } @@ -81,6 +101,7 @@ constructor( context.resources.getString(R.string.screenshot_edit_description), ) { debugLog(LogConfig.DEBUG_ACTIONS) { "Edit tapped" } + uiEventLogger.log(SCREENSHOT_EDIT_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createEdit(result.uri, context), true) } @@ -93,11 +114,46 @@ constructor( context.resources.getString(R.string.screenshot_share_description), ) { debugLog(LogConfig.DEBUG_ACTIONS) { "Share tapped" } + uiEventLogger.log(SCREENSHOT_SHARE_TAPPED, 0, request.packageNameString) onDeferrableActionTapped { result -> startSharedTransition(createShareWithSubject(result.uri, result.subject), false) } } ) + if (smartActionsEnabled(request.userHandle ?: Process.myUserHandle())) { + smartActionsProvider.requestQuickShare(request, requestId) { quickShare -> + if (!quickShare.actionIntent.isImmutable) { + viewModel.addAction( + ActionButtonViewModel( + quickShare.getIcon().loadDrawable(context), + quickShare.title, + quickShare.title + ) { + debugLog(LogConfig.DEBUG_ACTIONS) { "Quickshare tapped" } + onDeferrableActionTapped { result -> + uiEventLogger.log( + SCREENSHOT_SMART_ACTION_TAPPED, + 0, + request.packageNameString + ) + sendPendingIntent( + smartActionsProvider + .wrapIntent( + quickShare, + result.uri, + result.subject, + requestId + ) + .actionIntent + ) + } + } + ) + } else { + Log.w(TAG, "Received immutable quick share pending intent; ignoring") + } + } + } } override fun setCompletedScreenshot(result: SavedImageData) { @@ -105,12 +161,30 @@ constructor( Log.e(TAG, "Got a second completed screenshot for existing request!") return } - if (result.uri == null || result.owner == null || result.subject == null) { + if (result.uri == null || result.owner == null || result.imageTime == null) { Log.e(TAG, "Invalid result provided!") return } + if (result.subject == null) { + result.subject = getSubjectString(result.imageTime) + } this.result = result pendingAction?.invoke(result) + if (smartActionsEnabled(result.owner)) { + smartActionsProvider.requestSmartActions(request, requestId, result) { smartActions -> + viewModel.addActions( + smartActions.map { + ActionButtonViewModel( + it.getIcon().loadDrawable(context), + it.title, + it.title + ) { + sendPendingIntent(it.actionIntent) + } + } + ) + } + } } override fun isPendingSharedTransition(): Boolean { @@ -134,15 +208,47 @@ constructor( } } + private fun sendPendingIntent(pendingIntent: PendingIntent) { + try { + val options = BroadcastOptions.makeBasic() + options.setInteractive(true) + options.setPendingIntentBackgroundActivityStartMode( + ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED + ) + pendingIntent.send(options.toBundle()) + requestDismissal.invoke() + } catch (e: PendingIntent.CanceledException) { + Log.e(TAG, "Intent cancelled", e) + } + } + + private fun smartActionsEnabled(user: UserHandle): Boolean { + val savingToOtherUser = user != Process.myUserHandle() + return !savingToOtherUser && + DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, + true + ) + } + + private fun getSubjectString(imageTime: Long): String { + val subjectDate = DateFormat.getDateTimeInstance().format(Date(imageTime)) + return String.format(SCREENSHOT_SHARE_SUBJECT_TEMPLATE, subjectDate) + } + @AssistedFactory interface Factory : ScreenshotActionsProvider.Factory { override fun create( request: ScreenshotData, + requestId: String, windowTransition: () -> Pair<ActivityOptions, ExitTransitionCoordinator>, + requestDismissal: () -> Unit, ): DefaultScreenshotActionsProvider } companion object { private const val TAG = "ScreenshotActionsProvider" + private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" } } diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java index b43137fa2a74..70d1129a2b40 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java @@ -96,7 +96,10 @@ import dagger.assisted.Assisted; import dagger.assisted.AssistedFactory; import dagger.assisted.AssistedInject; +import kotlin.Unit; + import java.util.List; +import java.util.UUID; import java.util.concurrent.CancellationException; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -167,6 +170,7 @@ public class ScreenshotController { public Notification.Action quickShareAction; public UserHandle owner; public String subject; // Title for sharing + public Long imageTime; // Time at which screenshot was saved /** * Used to reset the return data on error @@ -176,6 +180,7 @@ public class ScreenshotController { smartActions = null; quickShareAction = null; subject = null; + imageTime = null; } } @@ -261,11 +266,9 @@ public class ScreenshotController { private SaveImageInBackgroundTask mSaveInBgTask; private boolean mScreenshotTakenInPortrait; private boolean mBlockAttach; - - private ScreenshotActionsProvider mActionsProvider; - private Animator mScreenshotAnimation; private RequestCallback mCurrentRequestCallback; + private ScreenshotActionsProvider mActionsProvider; private String mPackageName = ""; private final BroadcastReceiver mCopyBroadcastReceiver; @@ -317,6 +320,7 @@ public class ScreenshotController { @Assisted boolean showUIOnExternalDisplay ) { mScreenshotSmartActions = screenshotSmartActions; + mActionsProviderFactory = actionsProviderFactory; mNotificationsController = screenshotNotificationsControllerFactory.create(displayId); mScrollCaptureClient = scrollCaptureClient; mUiEventLogger = uiEventLogger; @@ -347,7 +351,6 @@ public class ScreenshotController { mAssistContentRequester = assistContentRequester; mViewProxy = viewProxyFactory.getProxy(mContext, mDisplayId); - mActionsProviderFactory = actionsProviderFactory; mScreenshotHandler.setOnTimeoutRunnable(() -> { if (DEBUG_UI) { @@ -441,8 +444,19 @@ public class ScreenshotController { return; } - saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, - this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); + if (screenshotShelfUi()) { + final UUID requestId = UUID.randomUUID(); + final String screenshotId = String.format("Screenshot_%s", requestId); + mActionsProvider = mActionsProviderFactory.create(screenshot, screenshotId, + this::createWindowTransition, () -> { + mViewProxy.requestDismissal(null); + return Unit.INSTANCE; + }); + saveScreenshotInBackground(screenshot, requestId, finisher); + } else { + saveScreenshotInWorkerThread(screenshot.getUserHandle(), finisher, + this::showUiOnActionsReady, this::showUiOnQuickShareActionReady); + } // The window is focusable by default setWindowFocusable(true); @@ -477,7 +491,9 @@ public class ScreenshotController { // ignore system bar insets for the purpose of window layout mWindow.getDecorView().setOnApplyWindowInsetsListener( (v, insets) -> WindowInsets.CONSUMED); - mScreenshotHandler.cancelTimeout(); // restarted after animation + if (!screenshotShelfUi()) { + mScreenshotHandler.cancelTimeout(); // restarted after animation + } } private boolean shouldShowUi() { @@ -497,11 +513,6 @@ public class ScreenshotController { mViewProxy.reset(); - if (screenshotShelfUi()) { - mActionsProvider = - mActionsProviderFactory.create(screenshot, this::createWindowTransition); - } - if (mViewProxy.isAttachedToWindow()) { // if we didn't already dismiss for another reason if (!mViewProxy.isDismissing()) { @@ -921,6 +932,39 @@ public class ScreenshotController { mScreenshotHandler.cancelTimeout(); } + private void saveScreenshotInBackground( + ScreenshotData screenshot, UUID requestId, Consumer<Uri> finisher) { + ListenableFuture<ImageExporter.Result> future = mImageExporter.export(mBgExecutor, + requestId, screenshot.getBitmap(), screenshot.getUserHandle(), mDisplayId); + future.addListener(() -> { + try { + ImageExporter.Result result = future.get(); + Log.d(TAG, "Saved screenshot: " + result); + logScreenshotResultStatus(result.uri, screenshot.getUserHandle()); + mScreenshotHandler.resetTimeout(); + if (result.uri != null) { + final SavedImageData savedImageData = new SavedImageData(); + savedImageData.uri = result.uri; + savedImageData.owner = screenshot.getUserHandle(); + savedImageData.imageTime = result.timestamp; + mActionsProvider.setCompletedScreenshot(savedImageData); + mViewProxy.setChipIntents(savedImageData); + } + if (DEBUG_CALLBACK) { + Log.d(TAG, "finished background processing, Calling (Consumer<Uri>) " + + "finisher.accept(\"" + result.uri + "\""); + } + finisher.accept(result.uri); + } catch (Exception e) { + Log.d(TAG, "Failed to store screenshot", e); + if (DEBUG_CALLBACK) { + Log.d(TAG, "Calling (Consumer<Uri>) finisher.accept(null)"); + } + finisher.accept(null); + } + }, mMainExecutor); + } + /** * Creates a new worker thread and saves the screenshot to the media store. */ @@ -958,11 +1002,6 @@ public class ScreenshotController { logSuccessOnActionsReady(imageData); mScreenshotHandler.resetTimeout(); - if (screenshotShelfUi()) { - mActionsProvider.setCompletedScreenshot(imageData); - return; - } - if (imageData.uri != null) { if (DEBUG_UI) { Log.d(TAG, "Showing UI actions"); @@ -1014,20 +1053,27 @@ public class ScreenshotController { /** * Logs success/failure of the screenshot saving task, and shows an error if it failed. */ - private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) { - if (imageData.uri == null) { + private void logScreenshotResultStatus(Uri uri, UserHandle owner) { + if (uri == null) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_NOT_SAVED, 0, mPackageName); mNotificationsController.notifyScreenshotError( R.string.screenshot_failed_to_save_text); } else { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED, 0, mPackageName); - if (mUserManager.isManagedProfile(imageData.owner.getIdentifier())) { + if (mUserManager.isManagedProfile(owner.getIdentifier())) { mUiEventLogger.log(ScreenshotEvent.SCREENSHOT_SAVED_TO_WORK_PROFILE, 0, mPackageName); } } } + /** + * Logs success/failure of the screenshot saving task, and shows an error if it failed. + */ + private void logSuccessOnActionsReady(ScreenshotController.SavedImageData imageData) { + logScreenshotResultStatus(imageData.uri, imageData.owner); + } + private boolean isUserSetupComplete(UserHandle owner) { return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0) .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1; diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt index defddc30586e..6b9332b39816 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotShelfViewProxy.kt @@ -98,7 +98,7 @@ constructor( override fun setChipIntents(imageData: SavedImageData) {} - override fun requestDismissal(event: ScreenshotEvent) { + override fun requestDismissal(event: ScreenshotEvent?) { debugLog(DEBUG_DISMISS) { "screenshot dismissal requested: $event" } // If we're already animating out, don't restart the animation @@ -106,7 +106,7 @@ constructor( debugLog(DEBUG_DISMISS) { "Already dismissing, ignoring duplicate command $event" } return } - logger.log(event, 0, packageName) + event?.let { logger.log(it, 0, packageName) } val animator = animationController.getExitAnimation() animator.addListener( object : AnimatorListenerAdapter() { diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt index 6be32a97f4e2..a4069d11f8fb 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotViewProxy.kt @@ -46,7 +46,7 @@ interface ScreenshotViewProxy { fun createScreenshotDropInAnimation(screenRect: Rect, showFlash: Boolean): Animator fun addQuickShareChip(quickShareAction: Notification.Action) fun setChipIntents(imageData: ScreenshotController.SavedImageData) - fun requestDismissal(event: ScreenshotEvent) + fun requestDismissal(event: ScreenshotEvent?) fun showScrollChip(packageName: String, onClick: Runnable) fun hideScrollChip() diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt new file mode 100644 index 000000000000..2eaff8654a9e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/screenshot/SmartActionsProvider.kt @@ -0,0 +1,285 @@ +/* + * 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.screenshot + +import android.app.Notification +import android.app.PendingIntent +import android.content.ClipData +import android.content.ClipDescription +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.graphics.Bitmap +import android.net.Uri +import android.os.Bundle +import android.os.Process +import android.os.SystemClock +import android.os.UserHandle +import android.provider.DeviceConfig +import android.util.Log +import com.android.internal.config.sysui.SystemUiDeviceConfigFlags +import com.android.systemui.log.DebugLogger.debugLog +import com.android.systemui.screenshot.LogConfig.DEBUG_ACTIONS +import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.QUICK_SHARE_ACTION +import com.android.systemui.screenshot.ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType.REGULAR_SMART_ACTIONS +import java.util.concurrent.CompletableFuture +import java.util.concurrent.TimeUnit +import java.util.concurrent.TimeoutException +import javax.inject.Inject +import kotlin.random.Random + +/** + * Handle requesting smart/quickshare actions from the provider and executing an action when the + * action futures complete. + */ +class SmartActionsProvider +@Inject +constructor( + private val context: Context, + private val smartActions: ScreenshotNotificationSmartActionsProvider, +) { + /** + * Requests quick share action for a given screenshot. + * + * @param data the ScreenshotData request + * @param id the request id for the screenshot + * @param onAction callback to run when quick share action is returned + */ + fun requestQuickShare( + data: ScreenshotData, + id: String, + onAction: (Notification.Action) -> Unit + ) { + val bitmap = data.bitmap ?: return + val user = data.userHandle ?: return + val component = data.topComponent ?: ComponentName("", "") + requestQuickShareAction(id, bitmap, component, user) { quickShareAction -> + onAction(quickShareAction) + } + } + + /** + * Requests smart actions for a given screenshot. + * + * @param data the ScreenshotData request + * @param id the request id for the screenshot + * @param result the data for the saved image + * @param onActions callback to run when actions are returned + */ + fun requestSmartActions( + data: ScreenshotData, + id: String, + result: ScreenshotController.SavedImageData, + onActions: (List<Notification.Action>) -> Unit + ) { + val bitmap = data.bitmap ?: return + val user = data.userHandle ?: return + val uri = result.uri ?: return + val component = data.topComponent ?: ComponentName("", "") + requestSmartActions(id, bitmap, component, user, uri, REGULAR_SMART_ACTIONS) { actions -> + onActions(actions) + } + } + + /** + * Wraps the given quick share action in a broadcast intent. + * + * @param quickShare the quick share action to wrap + * @param uri the URI of the saved screenshot + * @param subject the subject/title for the screenshot + * @param id the request ID of the screenshot + * @return the wrapped action + */ + fun wrapIntent( + quickShare: Notification.Action, + uri: Uri, + subject: String, + id: String + ): Notification.Action { + val wrappedIntent: Intent = + Intent(context, SmartActionsReceiver::class.java) + .putExtra(ScreenshotController.EXTRA_ACTION_INTENT, quickShare.actionIntent) + .putExtra( + ScreenshotController.EXTRA_ACTION_INTENT_FILLIN, + createFillInIntent(uri, subject) + ) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + val extras: Bundle = quickShare.extras + val actionType = + extras.getString( + ScreenshotNotificationSmartActionsProvider.ACTION_TYPE, + ScreenshotNotificationSmartActionsProvider.DEFAULT_ACTION_TYPE + ) + // We only query for quick share actions when smart actions are enabled, so we can assert + // that it's true here. + wrappedIntent + .putExtra(ScreenshotController.EXTRA_ACTION_TYPE, actionType) + .putExtra(ScreenshotController.EXTRA_ID, id) + .putExtra(ScreenshotController.EXTRA_SMART_ACTIONS_ENABLED, true) + val broadcastIntent = + PendingIntent.getBroadcast( + context, + Random.nextInt(), + wrappedIntent, + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + return Notification.Action.Builder(quickShare.getIcon(), quickShare.title, broadcastIntent) + .setContextual(true) + .addExtras(extras) + .build() + } + + private fun createFillInIntent(uri: Uri, subject: String): Intent { + val fillIn = Intent() + fillIn.setType("image/png") + fillIn.putExtra(Intent.EXTRA_STREAM, uri) + fillIn.putExtra(Intent.EXTRA_SUBJECT, subject) + // Include URI in ClipData also, so that grantPermission picks it up. + // We don't use setData here because some apps interpret this as "to:". + val clipData = + ClipData(ClipDescription("content", arrayOf("image/png")), ClipData.Item(uri)) + fillIn.clipData = clipData + fillIn.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + return fillIn + } + + private fun requestQuickShareAction( + id: String, + image: Bitmap, + component: ComponentName, + user: UserHandle, + timeoutMs: Long = 500, + onAction: (Notification.Action) -> Unit + ) { + requestSmartActions(id, image, component, user, null, QUICK_SHARE_ACTION, timeoutMs) { + it.firstOrNull()?.let { action -> onAction(action) } + } + } + + private fun requestSmartActions( + id: String, + image: Bitmap, + component: ComponentName, + user: UserHandle, + uri: Uri?, + actionType: ScreenshotNotificationSmartActionsProvider.ScreenshotSmartActionType, + timeoutMs: Long = 500, + onActions: (List<Notification.Action>) -> Unit + ) { + val enabled = isSmartActionsEnabled(user) + debugLog(DEBUG_ACTIONS) { + ("getSmartActionsFuture id=$id, uri=$uri, provider=$smartActions, " + + "actionType=$actionType, smartActionsEnabled=$enabled, userHandle=$user") + } + if (!enabled) { + debugLog(DEBUG_ACTIONS) { "Screenshot Intelligence not enabled, returning empty list" } + onActions(listOf()) + return + } + if (image.config != Bitmap.Config.HARDWARE) { + debugLog(DEBUG_ACTIONS) { + "Bitmap expected: Hardware, Bitmap found: ${image.config}. Returning empty list." + } + onActions(listOf()) + return + } + var smartActionsFuture: CompletableFuture<List<Notification.Action>> + val startTimeMs = SystemClock.uptimeMillis() + try { + smartActionsFuture = + smartActions.getActions(id, uri, image, component, actionType, user) + } catch (e: Throwable) { + val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs + debugLog(DEBUG_ACTIONS, error = e) { + "Failed to get future for screenshot notification smart actions." + } + notifyScreenshotOp( + id, + ScreenshotNotificationSmartActionsProvider.ScreenshotOp.REQUEST_SMART_ACTIONS, + ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR, + waitTimeMs + ) + onActions(listOf()) + return + } + try { + val actions = smartActionsFuture.get(timeoutMs, TimeUnit.MILLISECONDS) + val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs + debugLog(DEBUG_ACTIONS) { + ("Got ${actions.size} smart actions. Wait time: $waitTimeMs ms, " + + "actionType=$actionType") + } + notifyScreenshotOp( + id, + ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS, + ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.SUCCESS, + waitTimeMs + ) + onActions(actions) + } catch (e: Throwable) { + val waitTimeMs = SystemClock.uptimeMillis() - startTimeMs + debugLog(DEBUG_ACTIONS, error = e) { + "Error getting smart actions. Wait time: $waitTimeMs ms, actionType=$actionType" + } + val status = + if (e is TimeoutException) { + ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.TIMEOUT + } else { + ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus.ERROR + } + notifyScreenshotOp( + id, + ScreenshotNotificationSmartActionsProvider.ScreenshotOp.WAIT_FOR_SMART_ACTIONS, + status, + waitTimeMs + ) + onActions(listOf()) + } + } + + private fun notifyScreenshotOp( + screenshotId: String, + op: ScreenshotNotificationSmartActionsProvider.ScreenshotOp, + status: ScreenshotNotificationSmartActionsProvider.ScreenshotOpStatus, + durationMs: Long + ) { + debugLog(DEBUG_ACTIONS) { + "$smartActions notifyOp: $op id=$screenshotId, status=$status, durationMs=$durationMs" + } + try { + smartActions.notifyOp(screenshotId, op, status, durationMs) + } catch (e: Throwable) { + Log.e(TAG, "Error in notifyScreenshotOp: ", e) + } + } + private fun isSmartActionsEnabled(user: UserHandle): Boolean { + // Smart actions don't yet work for cross-user saves. + val savingToOtherUser = user !== Process.myUserHandle() + val actionsEnabled = + DeviceConfig.getBoolean( + DeviceConfig.NAMESPACE_SYSTEMUI, + SystemUiDeviceConfigFlags.ENABLE_SCREENSHOT_NOTIFICATION_SMART_ACTIONS, + true + ) + return !savingToOtherUser && actionsEnabled + } + + companion object { + private const val TAG = "SmartActionsProvider" + private const val SCREENSHOT_SHARE_SUBJECT_TEMPLATE = "Screenshot (%s)" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt index ea05884096c8..b191a1a52616 100644 --- a/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt +++ b/packages/SystemUI/src/com/android/systemui/screenshot/ui/binder/ScreenshotShelfViewBinder.kt @@ -16,6 +16,7 @@ package com.android.systemui.screenshot.ui.binder +import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java index 343f37744ce9..dcfd47b7b303 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java +++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java @@ -3174,6 +3174,7 @@ public final class NotificationPanelViewController implements ShadeSurface, Dump } notifyExpandingFinished(); } + // TODO(b/332732878): replace this call when scene container is enabled mNotificationStackScrollLayoutController.setAnimationsEnabled(!disabled); } diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt index c5e07e818b6b..ebebbe65d54b 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerSceneImpl.kt @@ -23,6 +23,7 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.log.LogBuffer import com.android.systemui.log.dagger.ShadeTouchLog import com.android.systemui.scene.domain.interactor.SceneInteractor @@ -61,6 +62,7 @@ constructor( private val shadeInteractor: ShadeInteractor, private val sceneInteractor: SceneInteractor, private val deviceEntryInteractor: DeviceEntryInteractor, + private val deviceUnlockedInteractor: DeviceUnlockedInteractor, private val notificationStackScrollLayout: NotificationStackScrollLayout, @ShadeTouchLog private val touchLog: LogBuffer, private val vibratorHelper: VibratorHelper, @@ -148,7 +150,11 @@ constructor( } private fun getCollapseDestinationScene(): SceneKey { - return if (deviceEntryInteractor.isDeviceEntered.value) { + // Always check whether device is unlocked before transitioning to gone scene. + return if ( + deviceUnlockedInteractor.deviceUnlockStatus.value.isUnlocked && + deviceEntryInteractor.isDeviceEntered.value + ) { Scenes.Gone } else { Scenes.Lockscreen diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt index adb29287e40e..ec4018c7d238 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeSurfaceImpl.kt @@ -46,7 +46,7 @@ class ShadeSurfaceImpl @Inject constructor() : ShadeSurface, ShadeViewController } override fun setTouchAndAnimationDisabled(disabled: Boolean) { - // TODO(b/322197941): determine if still needed + // TODO(b/332732878): determine if still needed } override fun setWillPlayDelayedDozeAmountAnimation(willPlay: Boolean) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java index d6858cad6d0b..78e108d444d0 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java @@ -40,6 +40,7 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.text.Editable; +import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.util.Pair; @@ -57,6 +58,7 @@ import android.view.View; import android.view.View.AccessibilityDelegate; import android.view.ViewGroup; import android.view.Window; +import android.view.WindowInsets; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.Button; @@ -104,6 +106,7 @@ public final class KeyboardShortcutListSearch { private WindowManager mWindowManager; private EditText mSearchEditText; + private ImageButton mEditTextCancel; private String mQueryString; private int mCurrentCategoryIndex = 0; private Map<Integer, Boolean> mKeySearchResultMap = new HashMap<>(); @@ -143,7 +146,7 @@ public final class KeyboardShortcutListSearch { @VisibleForTesting KeyboardShortcutListSearch(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( - context, android.R.style.Theme_DeviceDefault_Settings); + context, R.style.KeyboardShortcutHelper); this.mPackageManager = AppGlobals.getPackageManager(); if (windowManager != null) { this.mWindowManager = windowManager; @@ -853,13 +856,14 @@ public final class KeyboardShortcutListSearch { List<List<KeyboardShortcutMultiMappingGroup>> keyboardShortcutMultiMappingGroupList) { mQueryString = null; LayoutInflater inflater = mContext.getSystemService(LayoutInflater.class); - mKeyboardShortcutsBottomSheetDialog = - new BottomSheetDialog(mContext); + mKeyboardShortcutsBottomSheetDialog = new BottomSheetDialog(mContext); final View keyboardShortcutsView = inflater.inflate( R.layout.keyboard_shortcuts_search_view, null); LinearLayout shortcutsContainer = keyboardShortcutsView.findViewById( R.id.keyboard_shortcuts_container); mNoSearchResults = keyboardShortcutsView.findViewById(R.id.shortcut_search_no_result); + Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow(); + setWindowProperties(keyboardShortcutsWindow); mKeyboardShortcutsBottomSheetDialog.setContentView(keyboardShortcutsView); setButtonsDefaultStatus(keyboardShortcutsView); populateCurrentAppButton(); @@ -874,25 +878,11 @@ public final class KeyboardShortcutListSearch { } BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet); + behavior.setDraggable(true); behavior.setState(BottomSheetBehavior.STATE_EXPANDED); behavior.setSkipCollapsed(true); - behavior.setBottomSheetCallback(new BottomSheetBehavior.BottomSheetCallback() { - @Override - public void onStateChanged(@NonNull View bottomSheet, int newState) { - if (newState == BottomSheetBehavior.STATE_DRAGGING) { - behavior.setState(BottomSheetBehavior.STATE_EXPANDED); - } - } - @Override - public void onSlide(@NonNull View bottomSheet, float slideOffset) { - // Do nothing. - } - }); - mKeyboardShortcutsBottomSheetDialog.setCanceledOnTouchOutside(true); - Window keyboardShortcutsWindow = mKeyboardShortcutsBottomSheetDialog.getWindow(); - keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG); synchronized (sLock) { // show KeyboardShortcutsBottomSheetDialog only if it has not been dismissed already if (sInstance != null) { @@ -908,6 +898,8 @@ public final class KeyboardShortcutListSearch { } } mSearchEditText = keyboardShortcutsView.findViewById(R.id.keyboard_shortcuts_search); + mEditTextCancel = keyboardShortcutsView.findViewById( + R.id.keyboard_shortcuts_search_cancel); mSearchEditText.addTextChangedListener( new TextWatcher() { @Override @@ -921,6 +913,8 @@ public final class KeyboardShortcutListSearch { shortcutsContainer.setAccessibilityPaneTitle(mContext.getString( R.string.keyboard_shortcut_a11y_show_search_results)); } + mEditTextCancel.setVisibility( + TextUtils.isEmpty(mQueryString) ? View.GONE : View.VISIBLE); } @Override @@ -933,9 +927,28 @@ public final class KeyboardShortcutListSearch { // Do nothing. } }); - ImageButton editTextCancel = keyboardShortcutsView.findViewById( - R.id.keyboard_shortcuts_search_cancel); - editTextCancel.setOnClickListener(v -> mSearchEditText.setText(null)); + + mEditTextCancel.setOnClickListener(v -> mSearchEditText.setText(null)); + } + + private static void setWindowProperties(Window keyboardShortcutsWindow) { + keyboardShortcutsWindow.setType(TYPE_SYSTEM_DIALOG); + WindowManager.LayoutParams params = new WindowManager.LayoutParams(); + params.copyFrom(keyboardShortcutsWindow.getAttributes()); + // Allows the bottom sheet dialog to render all the way to the bottom of the screen, + // behind the gesture navigation bar. + params.flags |= WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS; + params.setFitInsetsTypes(WindowInsets.Type.statusBars()); + keyboardShortcutsWindow.setAttributes(params); + keyboardShortcutsWindow.getDecorView().setOnApplyWindowInsetsListener((v, insets) -> { + int bottom = insets.getInsets(WindowInsets.Type.navigationBars()).bottom; + View container = v.findViewById(R.id.keyboard_shortcuts_container); + container.setPadding(container.getPaddingLeft(), container.getPaddingTop(), + container.getPaddingRight(), bottom); + return WindowInsets.CONSUMED; + }); + keyboardShortcutsWindow.setWindowAnimations( + R.style.KeyboardShortcutHelper_BottomSheetDialogAnimation); } private void populateKeyboardShortcutSearchList(LinearLayout keyboardShortcutsLayout) { @@ -1256,10 +1269,10 @@ public final class KeyboardShortcutListSearch { if (mContext.getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) { lp.width = (int) (display.getWidth() * 0.8); - lp.height = (int) (display.getHeight() * 0.7); + lp.height = (int) (display.getHeight() * 0.8); } else { lp.width = (int) (display.getWidth() * 0.7); - lp.height = (int) (display.getHeight() * 0.8); + lp.height = (int) (display.getHeight() * 0.95); } window.setGravity(Gravity.BOTTOM); window.setAttributes(lp); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java index 2539372d49fd..aa6bec1f06f8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java @@ -46,6 +46,7 @@ import com.android.keyguard.KeyguardClockSwitch; import com.android.systemui.DejankUtils; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor; +import com.android.systemui.deviceentry.shared.model.DeviceUnlockStatus; import com.android.systemui.keyguard.MigrateClocksToBlueprint; import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor; import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener; @@ -198,7 +199,7 @@ public class StatusBarStateControllerImpl implements if (SceneContainerFlag.isEnabled()) { mJavaAdapter.alwaysCollectFlow( combineFlows( - mDeviceUnlockedInteractorLazy.get().isDeviceUnlocked(), + mDeviceUnlockedInteractorLazy.get().getDeviceUnlockStatus(), mSceneInteractorLazy.get().getCurrentScene(), this::calculateStateFromSceneFramework), this::onStatusBarStateChanged); @@ -646,11 +647,11 @@ public class StatusBarStateControllerImpl implements } private int calculateStateFromSceneFramework( - boolean isDeviceUnlocked, + DeviceUnlockStatus deviceUnlockStatus, SceneKey currentScene) { SceneContainerFlag.isUnexpectedlyInLegacyMode(); - if (isDeviceUnlocked) { + if (deviceUnlockStatus.isUnlocked()) { return StatusBarState.SHADE; } else { return Preconditions.checkNotNull(sStatusBarStateByLockedSceneKey.get(currentScene)); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt index 8531eaa46804..1a223c110ad5 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/KeyguardCoordinator.kt @@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.collection.coordinator +import android.os.SystemProperties import android.os.UserHandle import android.provider.Settings import androidx.annotation.VisibleForTesting @@ -340,12 +341,41 @@ constructor( var hasFilteredAnyNotifs = false + /** + * the [notificationMinimalismPrototype] will now show seen notifications on the locked + * shade by default, but this property read allows that to be quickly disabled for + * testing + */ + private val minimalismShowOnLockedShade + get() = + SystemProperties.getBoolean( + "persist.notification_minimalism_prototype.show_on_locked_shade", + true + ) + + /** + * Encapsulates a definition of "being on the keyguard". Note that these two definitions + * are wildly different: [StatusBarState.KEYGUARD] is when on the lock screen and does + * not include shade or occluded states, whereas [KeyguardRepository.isKeyguardShowing] + * is any state where the keyguard has not been dismissed, including locked shade and + * occluded lock screen. + * + * Returning false for locked shade and occluded states means that this filter will + * allow seen notifications to appear in the locked shade. + */ + private fun isOnKeyguard(): Boolean = + if (notificationMinimalismPrototype() && minimalismShowOnLockedShade) { + statusBarStateController.state == StatusBarState.KEYGUARD + } else { + keyguardRepository.isKeyguardShowing() + } + override fun shouldFilterOut(entry: NotificationEntry, now: Long): Boolean = when { // Don't apply filter if the setting is disabled !unseenFilterEnabled -> false // Don't apply filter if the keyguard isn't currently showing - !keyguardRepository.isKeyguardShowing() -> false + !isOnKeyguard() -> false // Don't apply the filter if the notification is unseen unseenNotifications.contains(entry) -> false // Don't apply the filter to (non-promoted) group summaries diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt new file mode 100644 index 000000000000..0344b32dd6ad --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/shared/NotificationHeadsUpCycling.kt @@ -0,0 +1,53 @@ +/* + * 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.shared + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the heads-up cycling flag state. */ +@Suppress("NOTHING_TO_INLINE") +object NotificationHeadsUpCycling { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_NOTIFICATION_HEADS_UP_CYCLING + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the heads-up cycling animation enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.notificationContentAlphaOptimization() + + /** + * 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/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java index 3367dc427f43..6a7a5cd8f6c3 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java @@ -230,7 +230,8 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable private final ArrayList<View> mSwipedOutViews = new ArrayList<>(); private NotificationStackSizeCalculator mNotificationStackSizeCalculator; private final StackStateAnimator mStateAnimator; - private boolean mAnimationsEnabled; + // TODO(b/332732878): call setAnimationsEnabled with scene container enabled, then remove this + private boolean mAnimationsEnabled = SceneContainerFlag.isEnabled(); private boolean mChangePositionInProgress; private boolean mChildTransferInProgress; @@ -2904,6 +2905,7 @@ public class NotificationStackScrollLayout extends ViewGroup implements Dumpable } public void setAnimationsEnabled(boolean animationsEnabled) { + // TODO(b/332732878): remove the initial value of this field once the setter is called mAnimationsEnabled = animationsEnabled; updateNotificationAnimationStates(); if (!animationsEnabled) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt index 1b53cbed8354..5bd4c758d678 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackSizeCalculator.kt @@ -17,14 +17,15 @@ package com.android.systemui.statusbar.notification.stack import android.content.res.Resources +import android.os.SystemProperties import android.util.Log import android.view.View.GONE import androidx.annotation.VisibleForTesting import com.android.systemui.Flags.notificationMinimalismPrototype -import com.android.systemui.res.R import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.media.controls.domain.pipeline.MediaDataManager +import com.android.systemui.res.R import com.android.systemui.statusbar.LockscreenShadeTransitionController import com.android.systemui.statusbar.StatusBarState.KEYGUARD import com.android.systemui.statusbar.SysuiStatusBarStateController @@ -177,8 +178,8 @@ constructor( // TODO: Avoid making this split shade assumption by simply checking the stack for media val isMediaShowing = mediaDataManager.hasActiveMediaOrRecommendation() - val isMediaShowingInStack = isMediaShowing && !splitShadeStateController - .shouldUseSplitNotificationShade(resources) + val isMediaShowingInStack = + isMediaShowing && !splitShadeStateController.shouldUseSplitNotificationShade(resources) log { "\tGet maxNotifWithoutSavingSpace ---" } val maxNotifWithoutSavingSpace = @@ -378,8 +379,17 @@ constructor( } fun updateResources() { - maxKeyguardNotifications = if (notificationMinimalismPrototype()) 1 - else infiniteIfNegative(resources.getInteger(R.integer.keyguard_max_notification_count)) + maxKeyguardNotifications = + infiniteIfNegative( + if (notificationMinimalismPrototype()) { + SystemProperties.getInt( + "persist.notification_minimalism_prototype.lock_screen_max_notifs", + 1 + ) + } else { + resources.getInteger(R.integer.keyguard_max_notification_count) + } + ) maxNotificationsExcludesMedia = notificationMinimalismPrototype() dividerHeight = 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 2798dbfc62e4..5baf6a069602 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java @@ -28,7 +28,6 @@ import static androidx.lifecycle.Lifecycle.State.RESUMED; import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME; import static com.android.systemui.Flags.lightRevealMigration; import static com.android.systemui.Flags.newAodTransition; -import static com.android.systemui.Flags.predictiveBackSysui; import static com.android.systemui.Flags.truncatedStatusBarIconsFix; import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL; import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF; @@ -837,7 +836,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { mLightRevealScrimViewModelLazy = lightRevealScrimViewModelLazy; mLightRevealScrim = lightRevealScrim; - if (predictiveBackSysui()) { + if (PredictiveBackSysUiFlag.isEnabled()) { mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true); } } @@ -3061,7 +3060,7 @@ public class CentralSurfacesImpl implements CoreStartable, CentralSurfaces { public void onConfigChanged(Configuration newConfig) { updateResources(); updateDisplaySize(); // populates mDisplayMetrics - if (predictiveBackSysui()) { + if (PredictiveBackSysUiFlag.isEnabled()) { mContext.getApplicationInfo().setEnableOnBackInvokedCallback(true); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt new file mode 100644 index 000000000000..74d6ba57a8ef --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PredictiveBackSysUiFlag.kt @@ -0,0 +1,53 @@ +/* + * 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.phone + +import com.android.systemui.Flags +import com.android.systemui.flags.FlagToken +import com.android.systemui.flags.RefactorFlagUtils + +/** Helper for reading or using the predictive back flag state. */ +@Suppress("NOTHING_TO_INLINE") +object PredictiveBackSysUiFlag { + /** The aconfig flag name */ + const val FLAG_NAME = Flags.FLAG_PREDICTIVE_BACK_SYSUI + + /** A token used for dependency declaration */ + val token: FlagToken + get() = FlagToken(FLAG_NAME, isEnabled) + + /** Is the refactor enabled */ + @JvmStatic + inline val isEnabled + get() = Flags.predictiveBackSysui() + + /** + * 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/statusbar/policy/AvalancheController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt index 55a0f59bf461..9cdecef3f6e8 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/AvalancheController.kt @@ -17,9 +17,12 @@ package com.android.systemui.statusbar.policy import android.util.Log import androidx.annotation.VisibleForTesting +import com.android.systemui.Dumpable import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dump.DumpManager import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun import com.android.systemui.statusbar.policy.BaseHeadsUpManager.HeadsUpEntry +import java.io.PrintWriter import javax.inject.Inject /* @@ -27,7 +30,9 @@ import javax.inject.Inject * succession, by delaying visual listener side effects and removal handling from BaseHeadsUpManager */ @SysUISingleton -class AvalancheController @Inject constructor() { +class AvalancheController @Inject constructor( + dumpManager: DumpManager, +) : Dumpable { private val tag = "AvalancheController" private val debug = false @@ -54,22 +59,26 @@ class AvalancheController @Inject constructor() { // For debugging only @VisibleForTesting var debugDropSet: MutableSet<HeadsUpEntry> = HashSet() - /** - * Run or delay Runnable for given HeadsUpEntry - */ - fun update(entry: HeadsUpEntry, runnable: Runnable, label: String) { + init { + dumpManager.registerNormalDumpable(tag, /* module */ this) + } + + /** Run or delay Runnable for given HeadsUpEntry */ + fun update(entry: HeadsUpEntry?, runnable: Runnable, label: String) { if (!NotificationThrottleHun.isEnabled) { runnable.run() return } val fn = "[$label] => AvalancheController.update ${getKey(entry)}" - + if (entry == null) { + log { "Entry is NULL, stop update." } + return; + } if (debug) { debugRunnableLabelMap[runnable] = label } - if (isShowing(entry)) { - log {"$fn => [update showing]" } + log { "$fn => [update showing]" } runnable.run() } else if (entry in nextMap) { log { "$fn => [update next]" } @@ -164,9 +173,7 @@ class AvalancheController @Inject constructor() { } } - /** - * Return true if entry is waiting to show. - */ + /** Return true if entry is waiting to show. */ fun isWaiting(key: String): Boolean { if (!NotificationThrottleHun.isEnabled) { return false @@ -179,9 +186,7 @@ class AvalancheController @Inject constructor() { return false } - /** - * Return list of keys for huns waiting - */ + /** Return list of keys for huns waiting */ fun getWaitingKeys(): MutableList<String> { if (!NotificationThrottleHun.isEnabled) { return mutableListOf() @@ -254,12 +259,15 @@ class AvalancheController @Inject constructor() { } } - // TODO(b/315362456) expose as dumpable for bugreports + private fun getStateStr(): String { + return "SHOWING: ${getKey(headsUpEntryShowing)}" + + "\tNEXT LIST: $nextListStr\tMAP: $nextMapStr" + + "\tDROP: $dropSetStr" + } + private fun logState(reason: String) { - log { "state $reason" } - log { "showing: " + getKey(headsUpEntryShowing) } - log { "next list: $nextListStr map: $nextMapStr" } - log { "drop: $dropSetStr" } + log { "REASON $reason" } + log { getStateStr() } } private val dropSetStr: String @@ -298,4 +306,8 @@ class AvalancheController @Inject constructor() { } return entry.mEntry!!.key } + + override fun dump(pw: PrintWriter, args: Array<out String>) { + pw.println("AvalancheController: ${getStateStr()}") + } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java index 20a82a403eb7..d99af2ddb95d 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java @@ -165,6 +165,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { public void showNotification(@NonNull NotificationEntry entry) { HeadsUpEntry headsUpEntry = createHeadsUpEntry(entry); + mLogger.logShowNotificationRequest(entry); + Runnable runnable = () -> { // TODO(b/315362456) log outside runnable too mLogger.logShowNotification(entry); @@ -219,6 +221,8 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ public void updateNotification(@NonNull String key, boolean shouldHeadsUpAgain) { HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); + mLogger.logUpdateNotificationRequest(key, shouldHeadsUpAgain, headsUpEntry != null); + Runnable runnable = () -> { updateNotificationInternal(key, shouldHeadsUpAgain); }; @@ -378,8 +382,11 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { */ protected final void removeEntry(@NonNull String key) { HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key); + mLogger.logRemoveEntryRequest(key); Runnable runnable = () -> { + mLogger.logRemoveEntry(key); + if (headsUpEntry == null) { return; } @@ -566,8 +573,10 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { public void unpinAll(boolean userUnPinned) { for (String key : mHeadsUpEntryMap.keySet()) { HeadsUpEntry headsUpEntry = getHeadsUpEntry(key); - + mLogger.logUnpinEntryRequest(key); Runnable runnable = () -> { + mLogger.logUnpinEntry(key); + setEntryPinned(headsUpEntry, false /* isPinned */); // maybe it got un sticky headsUpEntry.updateEntry(false /* updatePostTime */, "unpinAll"); @@ -886,6 +895,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { * Clear any pending removal runnables. */ public void cancelAutoRemovalCallbacks(@Nullable String reason) { + mLogger.logAutoRemoveCancelRequest(this.mEntry, reason); Runnable runnable = () -> { final boolean removed = cancelAutoRemovalCallbackInternal(); @@ -900,6 +910,7 @@ public abstract class BaseHeadsUpManager implements HeadsUpManager { public void scheduleAutoRemovalCallback(FinishTimeUpdater finishTimeCalculator, @NonNull String reason) { + mLogger.logAutoRemoveRequest(this.mEntry, reason); Runnable runnable = () -> { long delayMs = finishTimeCalculator.updateAndGetTimeRemaining(); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt index f6154afec273..a30660645990 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt @@ -58,6 +58,14 @@ class HeadsUpManagerLogger @Inject constructor( }) } + fun logShowNotificationRequest(entry: NotificationEntry) { + buffer.log(TAG, INFO, { + str1 = entry.logKey + }, { + "request: show notification $str1" + }) + } + fun logShowNotification(entry: NotificationEntry) { buffer.log(TAG, INFO, { str1 = entry.logKey @@ -76,6 +84,15 @@ class HeadsUpManagerLogger @Inject constructor( }) } + fun logAutoRemoveRequest(entry: NotificationEntry, reason: String) { + buffer.log(TAG, INFO, { + str1 = entry.logKey + str2 = reason + }, { + "request: reschedule auto remove of $str1 reason: $str2" + }) + } + fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) { buffer.log(TAG, INFO, { str1 = entry.logKey @@ -86,6 +103,15 @@ class HeadsUpManagerLogger @Inject constructor( }) } + fun logAutoRemoveCancelRequest(entry: NotificationEntry, reason: String?) { + buffer.log(TAG, INFO, { + str1 = entry.logKey + str2 = reason ?: "unknown" + }, { + "request: cancel auto remove of $str1 reason: $str2" + }) + } + fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) { buffer.log(TAG, INFO, { str1 = entry.logKey @@ -95,6 +121,38 @@ class HeadsUpManagerLogger @Inject constructor( }) } + fun logRemoveEntryRequest(key: String) { + buffer.log(TAG, INFO, { + str1 = logKey(key) + }, { + "request: remove entry $str1" + }) + } + + fun logRemoveEntry(key: String) { + buffer.log(TAG, INFO, { + str1 = logKey(key) + }, { + "remove entry $str1" + }) + } + + fun logUnpinEntryRequest(key: String) { + buffer.log(TAG, INFO, { + str1 = logKey(key) + }, { + "request: unpin entry $str1" + }) + } + + fun logUnpinEntry(key: String) { + buffer.log(TAG, INFO, { + str1 = logKey(key) + }, { + "unpin entry $str1" + }) + } + fun logRemoveNotification(key: String, releaseImmediately: Boolean) { buffer.log(TAG, INFO, { str1 = logKey(key) @@ -112,6 +170,16 @@ class HeadsUpManagerLogger @Inject constructor( }) } + fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) { + buffer.log(TAG, INFO, { + str1 = logKey(key) + bool1 = alert + bool2 = hasEntry + }, { + "request: update notification $str1 alert: $bool1 hasEntry: $bool2 reason: $str2" + }) + } + fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) { buffer.log(TAG, INFO, { str1 = logKey(key) diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt index 988564ae907c..db4e605dc9bb 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/PolicyModule.kt @@ -14,7 +14,12 @@ package com.android.systemui.statusbar.policy -import android.os.UserManager +import android.hardware.SensorPrivacyManager.Sensors.CAMERA +import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE +import android.os.UserManager.DISALLOW_CAMERA_TOGGLE +import android.os.UserManager.DISALLOW_CONFIG_LOCATION +import android.os.UserManager.DISALLOW_MICROPHONE_TOGGLE +import android.os.UserManager.DISALLOW_SHARE_LOCATION import com.android.systemui.qs.QsEventLogger import com.android.systemui.qs.pipeline.shared.TileSpec import com.android.systemui.qs.tileimpl.QSTileImpl @@ -39,6 +44,11 @@ import com.android.systemui.qs.tiles.impl.location.domain.LocationTileMapper import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileDataInteractor import com.android.systemui.qs.tiles.impl.location.domain.interactor.LocationTileUserActionInteractor import com.android.systemui.qs.tiles.impl.location.domain.model.LocationTileModel +import com.android.systemui.qs.tiles.impl.sensorprivacy.SensorPrivacyToggleTileDataInteractor +import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.SensorPrivacyToggleTileUserActionInteractor +import com.android.systemui.qs.tiles.impl.sensorprivacy.domain.model.SensorPrivacyToggleTileModel +import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyTileResources +import com.android.systemui.qs.tiles.impl.sensorprivacy.ui.SensorPrivacyToggleTileMapper import com.android.systemui.qs.tiles.impl.uimodenight.domain.UiModeNightTileMapper import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileDataInteractor import com.android.systemui.qs.tiles.impl.uimodenight.domain.interactor.UiModeNightTileUserActionInteractor @@ -76,6 +86,8 @@ interface PolicyModule { const val ALARM_TILE_SPEC = "alarm" const val UIMODENIGHT_TILE_SPEC = "dark" const val WORK_MODE_TILE_SPEC = "work" + const val CAMERA_TOGGLE_TILE_SPEC = "cameratoggle" + const val MIC_TOGGLE_TILE_SPEC = "mictoggle" /** Inject flashlight config */ @Provides @@ -125,8 +137,8 @@ interface PolicyModule { policy = QSTilePolicy.Restricted( listOf( - UserManager.DISALLOW_SHARE_LOCATION, - UserManager.DISALLOW_CONFIG_LOCATION + DISALLOW_SHARE_LOCATION, + DISALLOW_CONFIG_LOCATION ) ) ) @@ -243,6 +255,72 @@ interface PolicyModule { stateInteractor, mapper, ) + + /** Inject camera toggle config */ + @Provides + @IntoMap + @StringKey(CAMERA_TOGGLE_TILE_SPEC) + fun provideCameraToggleTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(CAMERA_TOGGLE_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_camera_access_icon_off, + labelRes = R.string.quick_settings_camera_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + policy = QSTilePolicy.Restricted(listOf(DISALLOW_CAMERA_TOGGLE)), + ) + + /** Inject camera toggle tile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(CAMERA_TOGGLE_TILE_SPEC) + fun provideCameraToggleTileViewModel( + factory: QSTileViewModelFactory.Static<SensorPrivacyToggleTileModel>, + mapper: SensorPrivacyToggleTileMapper.Factory, + stateInteractor: SensorPrivacyToggleTileDataInteractor.Factory, + userActionInteractor: SensorPrivacyToggleTileUserActionInteractor.Factory, + ): QSTileViewModel = + factory.create( + TileSpec.create(CAMERA_TOGGLE_TILE_SPEC), + userActionInteractor.create(CAMERA), + stateInteractor.create(CAMERA), + mapper.create(SensorPrivacyTileResources.CameraPrivacyTileResources), + ) + + /** Inject microphone toggle config */ + @Provides + @IntoMap + @StringKey(MIC_TOGGLE_TILE_SPEC) + fun provideMicrophoneToggleTileConfig(uiEventLogger: QsEventLogger): QSTileConfig = + QSTileConfig( + tileSpec = TileSpec.create(MIC_TOGGLE_TILE_SPEC), + uiConfig = + QSTileUIConfig.Resource( + iconRes = R.drawable.qs_mic_access_off, + labelRes = R.string.quick_settings_mic_label, + ), + instanceId = uiEventLogger.getNewInstanceId(), + policy = QSTilePolicy.Restricted(listOf(DISALLOW_MICROPHONE_TOGGLE)), + ) + + /** Inject microphone toggle tile into tileViewModelMap in QSModule */ + @Provides + @IntoMap + @StringKey(MIC_TOGGLE_TILE_SPEC) + fun provideMicrophoneToggleTileViewModel( + factory: QSTileViewModelFactory.Static<SensorPrivacyToggleTileModel>, + mapper: SensorPrivacyToggleTileMapper.Factory, + stateInteractor: SensorPrivacyToggleTileDataInteractor.Factory, + userActionInteractor: SensorPrivacyToggleTileUserActionInteractor.Factory, + ): QSTileViewModel = + factory.create( + TileSpec.create(MIC_TOGGLE_TILE_SPEC), + userActionInteractor.create(MICROPHONE), + stateInteractor.create(MICROPHONE), + mapper.create(SensorPrivacyTileResources.MicrophonePrivacyTileResources), + ) } /** Inject FlashlightTile into tileMap in QSModule */ diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java index 860068c137a3..0d53277e51dc 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerImpl.java @@ -20,6 +20,7 @@ import static android.permission.flags.Flags.sensitiveNotificationAppProtection; import static android.provider.Settings.Global.DISABLE_SCREEN_SHARE_PROTECTIONS_FOR_APPS_AND_NOTIFICATIONS; import static com.android.server.notification.Flags.screenshareNotificationHiding; +import static com.android.systemui.Flags.screenshareNotificationHidingBugFix; import android.annotation.MainThread; import android.app.IActivityManager; @@ -31,6 +32,7 @@ import android.media.projection.MediaProjectionManager; import android.os.Handler; import android.os.RemoteException; import android.os.Trace; +import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.ArraySet; import android.util.Log; @@ -316,6 +318,10 @@ public class SensitiveNotificationProtectionControllerImpl return false; } + if (screenshareNotificationHidingBugFix() && UserHandle.isCore(sbn.getUid())) { + return false; // do not hide/redact notifications from system uid + } + // Only protect/redact notifications if the developer has not explicitly set notification // visibility as public and users has not adjusted default channel visibility to private boolean notificationRequestsRedaction = entry.isNotificationVisibilityPrivate(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt index 9b8cf592d496..6a0462b72544 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/domain/ui/viewmodel/UdfpsAccessibilityOverlayViewModelTest.kt @@ -122,7 +122,6 @@ class UdfpsAccessibilityOverlayViewModelTest : SysuiTestCase() { fingerprintPropertyRepository.supportsUdfps() biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true) deviceEntryFingerprintAuthRepository.setIsRunning(true) - deviceEntryRepository.setUnlocked(false) // Lockscreen keyguardTransitionRepository.sendTransitionStep( diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java index 1c6f25147a1e..a127631a536d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/DreamOverlayTouchMonitorTest.java @@ -32,6 +32,7 @@ import android.graphics.Region; import android.testing.AndroidTestingRunner; import android.util.Pair; import android.view.GestureDetector; +import android.view.IWindowManager; import android.view.InputEvent; import android.view.MotionEvent; @@ -83,11 +84,14 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { private final GestureDetector.OnGestureListener mGestureListener; private final DisplayHelper mDisplayHelper; private final FakeExecutor mExecutor = new FakeExecutor(new FakeSystemClock()); + private final FakeExecutor mBackgroundExecutor = new FakeExecutor(new FakeSystemClock()); private final Rect mDisplayBounds = Mockito.mock(Rect.class); + private final IWindowManager mIWindowManager; Environment(Set<DreamTouchHandler> handlers) { mLifecycle = Mockito.mock(Lifecycle.class); mLifecycleOwner = Mockito.mock(LifecycleOwner.class); + mIWindowManager = Mockito.mock(IWindowManager.class); mInputFactory = Mockito.mock(InputSessionComponent.Factory.class); final InputSessionComponent inputComponent = Mockito.mock(InputSessionComponent.class); @@ -100,8 +104,8 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { mDisplayHelper = Mockito.mock(DisplayHelper.class); when(mDisplayHelper.getMaxBounds(anyInt(), anyInt())) .thenReturn(mDisplayBounds); - mMonitor = new DreamOverlayTouchMonitor(mExecutor, mLifecycle, mInputFactory, - mDisplayHelper, handlers); + mMonitor = new DreamOverlayTouchMonitor(mExecutor, mBackgroundExecutor, + mLifecycle, mInputFactory, mDisplayHelper, handlers, mIWindowManager, 0); mMonitor.init(); final ArgumentCaptor<LifecycleObserver> lifecycleObserverCaptor = @@ -163,7 +167,8 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { environment.publishInputEvent(initialEvent); // Verify display bounds passed into TouchHandler#getTouchInitiationRegion - verify(touchHandler).getTouchInitiationRegion(eq(environment.getDisplayBounds()), any()); + verify(touchHandler).getTouchInitiationRegion( + eq(environment.getDisplayBounds()), any(), any()); final ArgumentCaptor<DreamTouchHandler.TouchSession> touchSessionArgumentCaptor = ArgumentCaptor.forClass(DreamTouchHandler.TouchSession.class); verify(touchHandler).onSessionStart(touchSessionArgumentCaptor.capture()); @@ -182,7 +187,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { final Region region = (Region) invocation.getArguments()[1]; region.set(touchArea); return null; - }).when(touchHandler).getTouchInitiationRegion(any(), any()); + }).when(touchHandler).getTouchInitiationRegion(any(), any(), any()); final Environment environment = new Environment(Stream.of(touchHandler) .collect(Collectors.toCollection(HashSet::new))); @@ -211,7 +216,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { final Region region = (Region) invocation.getArguments()[1]; region.set(touchArea); return null; - }).when(touchHandler).getTouchInitiationRegion(any(), any()); + }).when(touchHandler).getTouchInitiationRegion(any(), any(), any()); final Environment environment = new Environment(Stream.of(touchHandler, unzonedTouchHandler) .collect(Collectors.toCollection(HashSet::new))); @@ -264,7 +269,7 @@ public class DreamOverlayTouchMonitorTest extends SysuiTestCase { // Make sure there is no active session. verify(touchHandler, never()).onSessionStart(any()); - verify(touchHandler, never()).getTouchInitiationRegion(any(), any()); + verify(touchHandler, never()).getTouchInitiationRegion(any(), any(), any()); } @Test diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt deleted file mode 100644 index e044eeca8303..000000000000 --- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionDialogDelegateTest.kt +++ /dev/null @@ -1,141 +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.mediaprojection.permission - -import android.app.AlertDialog -import android.media.projection.MediaProjectionConfig -import android.testing.AndroidTestingRunner -import android.testing.TestableLooper -import android.view.WindowManager -import android.widget.Spinner -import android.widget.TextView -import androidx.test.filters.SmallTest -import com.android.systemui.SysuiTestCase -import com.android.systemui.flags.FeatureFlagsClassic -import com.android.systemui.flags.Flags -import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger -import com.android.systemui.res.R -import com.android.systemui.statusbar.phone.AlertDialogWithDelegate -import com.android.systemui.statusbar.phone.SystemUIDialog -import com.android.systemui.util.mockito.mock -import junit.framework.Assert.assertEquals -import org.junit.After -import org.junit.Before -import org.junit.Test -import org.junit.runner.RunWith -import org.mockito.Mockito.`when` as whenever - -@SmallTest -@RunWith(AndroidTestingRunner::class) -@TestableLooper.RunWithLooper(setAsMainLooper = true) -class MediaProjectionPermissionDialogDelegateTest : SysuiTestCase() { - - private lateinit var dialog: AlertDialog - - private val flags = mock<FeatureFlagsClassic>() - private val onStartRecordingClicked = mock<Runnable>() - private val mediaProjectionMetricsLogger = mock<MediaProjectionMetricsLogger>() - - private val mediaProjectionConfig: MediaProjectionConfig = - MediaProjectionConfig.createConfigForDefaultDisplay() - private val appName: String = "testApp" - private val hostUid: Int = 12345 - - private val resIdSingleApp = R.string.screen_share_permission_dialog_option_single_app - private val resIdFullScreen = R.string.screen_share_permission_dialog_option_entire_screen - private val resIdSingleAppDisabled = - R.string.media_projection_entry_app_permission_dialog_single_app_disabled - - @Before - fun setUp() { - whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true) - } - - @After - fun teardown() { - if (::dialog.isInitialized) { - dialog.dismiss() - } - } - - @Test - fun showDialog_forceShowPartialScreenShareFalse() { - // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and - // overrideDisableSingleAppOption = false - val overrideDisableSingleAppOption = false - setUpAndShowDialog(overrideDisableSingleAppOption) - - val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) - val secondOptionText = - spinner.adapter - .getDropDownView(1, null, spinner) - .findViewById<TextView>(android.R.id.text2) - ?.text - - // check that the first option is full screen and enabled - assertEquals(context.getString(resIdFullScreen), spinner.selectedItem) - - // check that the second option is single app and disabled - assertEquals(context.getString(resIdSingleAppDisabled, appName), secondOptionText) - } - - @Test - fun showDialog_forceShowPartialScreenShareTrue() { - // Set up dialog with MediaProjectionConfig.createConfigForDefaultDisplay() and - // overrideDisableSingleAppOption = true - val overrideDisableSingleAppOption = true - setUpAndShowDialog(overrideDisableSingleAppOption) - - val spinner = dialog.requireViewById<Spinner>(R.id.screen_share_mode_spinner) - val secondOptionText = - spinner.adapter - .getDropDownView(1, null, spinner) - .findViewById<TextView>(android.R.id.text1) - ?.text - - // check that the first option is single app and enabled - assertEquals(context.getString(resIdSingleApp), spinner.selectedItem) - - // check that the second option is full screen and enabled - assertEquals(context.getString(resIdFullScreen), secondOptionText) - } - - private fun setUpAndShowDialog(overrideDisableSingleAppOption: Boolean) { - val delegate = - MediaProjectionPermissionDialogDelegate( - context, - mediaProjectionConfig, - {}, - onStartRecordingClicked, - appName, - overrideDisableSingleAppOption, - hostUid, - mediaProjectionMetricsLogger - ) - - dialog = AlertDialogWithDelegate(context, R.style.Theme_SystemUI_Dialog, delegate) - SystemUIDialog.applyFlags(dialog) - SystemUIDialog.setDialogSize(dialog) - - dialog.window?.addSystemFlags( - WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS - ) - - delegate.onCreate(dialog, savedInstanceState = null) - dialog.show() - } -} diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt index f49c6e25c2d3..4a5cf57e6fa8 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/DefaultScreenshotActionsProviderTest.kt @@ -18,13 +18,18 @@ package com.android.systemui.screenshot import android.app.ActivityOptions import android.app.ExitTransitionCoordinator +import android.app.Notification +import android.app.PendingIntent import android.content.Intent import android.net.Uri import android.os.UserHandle import android.testing.AndroidTestingRunner import android.view.accessibility.AccessibilityManager import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger import com.android.systemui.SysuiTestCase +import com.android.systemui.clipboardoverlay.EditTextActivity +import com.android.systemui.res.R import com.android.systemui.screenshot.ui.viewmodel.ScreenshotViewModel import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture @@ -41,7 +46,11 @@ import org.junit.Assert.assertNotNull import org.junit.Before import org.junit.runner.RunWith import org.mockito.Mockito.verifyNoMoreInteractions +import org.mockito.kotlin.any +import org.mockito.kotlin.never +import org.mockito.kotlin.verify import org.mockito.kotlin.verifyBlocking +import org.mockito.kotlin.whenever @RunWith(AndroidTestingRunner::class) @SmallTest @@ -52,15 +61,19 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { private val actionIntentExecutor = mock<ActionIntentExecutor>() private val accessibilityManager = mock<AccessibilityManager>() + private val uiEventLogger = mock<UiEventLogger>() + private val smartActionsProvider = mock<SmartActionsProvider>() private val transition = mock<android.util.Pair<ActivityOptions, ExitTransitionCoordinator>>() + private val requestDismissal = mock<() -> Unit>() private val request = ScreenshotData.forTesting() private val invalidResult = ScreenshotController.SavedImageData() private val validResult = ScreenshotController.SavedImageData().apply { uri = Uri.EMPTY - owner = UserHandle.CURRENT + owner = UserHandle.OWNER subject = "Test" + imageTime = 0 } private lateinit var viewModel: ScreenshotViewModel @@ -69,20 +82,13 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { @Before fun setUp() { viewModel = ScreenshotViewModel(accessibilityManager) - actionsProvider = - DefaultScreenshotActionsProvider( - context, - viewModel, - actionIntentExecutor, - testScope, - request - ) { - transition - } + request.userHandle = UserHandle.OWNER } @Test fun previewActionAccessed_beforeScreenshotCompleted_doesNothing() { + actionsProvider = createActionsProvider() + assertNotNull(viewModel.previewAction.value) viewModel.previewAction.value!!.invoke() verifyNoMoreInteractions(actionIntentExecutor) @@ -90,6 +96,8 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { @Test fun actionButtonsAccessed_beforeScreenshotCompleted_doesNothing() { + actionsProvider = createActionsProvider() + assertThat(viewModel.actions.value.size).isEqualTo(2) val firstAction = viewModel.actions.value[0] assertThat(firstAction.onClicked).isNotNull() @@ -102,6 +110,8 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { @Test fun actionAccessed_withInvalidResult_doesNothing() { + actionsProvider = createActionsProvider() + actionsProvider.setCompletedScreenshot(invalidResult) viewModel.previewAction.value!!.invoke() viewModel.actions.value[1].onClicked!!.invoke() @@ -112,10 +122,13 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { @Test @Ignore("b/332526567") fun actionAccessed_withResult_launchesIntent() = runTest { + actionsProvider = createActionsProvider() + actionsProvider.setCompletedScreenshot(validResult) viewModel.actions.value[0].onClicked!!.invoke() scheduler.advanceUntilIdle() + verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_EDIT_TAPPED), eq(0), eq("")) val intentCaptor = argumentCaptor<Intent>() verifyBlocking(actionIntentExecutor) { launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(true)) @@ -126,16 +139,65 @@ class DefaultScreenshotActionsProviderTest : SysuiTestCase() { @Test @Ignore("b/332526567") fun actionAccessed_whilePending_launchesMostRecentAction() = runTest { + actionsProvider = createActionsProvider() + viewModel.actions.value[0].onClicked!!.invoke() viewModel.previewAction.value!!.invoke() viewModel.actions.value[1].onClicked!!.invoke() actionsProvider.setCompletedScreenshot(validResult) scheduler.advanceUntilIdle() + verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SHARE_TAPPED), eq(0), eq("")) val intentCaptor = argumentCaptor<Intent>() verifyBlocking(actionIntentExecutor) { launchIntent(capture(intentCaptor), eq(transition), eq(UserHandle.CURRENT), eq(false)) } assertThat(intentCaptor.value.action).isEqualTo(Intent.ACTION_CHOOSER) } + + @Test + fun quickShareTapped_wrapsAndSendsIntent() = runTest { + val quickShare = + Notification.Action( + R.drawable.ic_screenshot_edit, + "TestQuickShare", + PendingIntent.getActivity( + context, + 0, + Intent(context, EditTextActivity::class.java), + PendingIntent.FLAG_MUTABLE + ) + ) + whenever(smartActionsProvider.requestQuickShare(any(), any(), any())).then { + (it.getArgument(2) as ((Notification.Action) -> Unit)).invoke(quickShare) + } + whenever(smartActionsProvider.wrapIntent(any(), any(), any(), any())).thenAnswer { + it.getArgument(0) + } + actionsProvider = createActionsProvider() + + viewModel.actions.value[2].onClicked?.invoke() + verify(uiEventLogger, never()) + .log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), any(), any()) + verify(smartActionsProvider, never()).wrapIntent(any(), any(), any(), any()) + actionsProvider.setCompletedScreenshot(validResult) + verify(smartActionsProvider) + .wrapIntent(eq(quickShare), eq(validResult.uri), eq(validResult.subject), eq("testid")) + verify(uiEventLogger).log(eq(ScreenshotEvent.SCREENSHOT_SMART_ACTION_TAPPED), eq(0), eq("")) + } + + private fun createActionsProvider(): ScreenshotActionsProvider { + return DefaultScreenshotActionsProvider( + context, + viewModel, + actionIntentExecutor, + smartActionsProvider, + uiEventLogger, + testScope, + request, + "testid", + { transition }, + requestDismissal, + ) + } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt index e54b53225320..de6108632153 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt @@ -33,7 +33,6 @@ import com.android.systemui.classifier.FalsingCollectorFake import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.flags.EnableSceneContainer @@ -42,6 +41,7 @@ import com.android.systemui.jank.interactionJankMonitor import com.android.systemui.keyguard.data.repository.FakeCommandQueue import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository +import com.android.systemui.keyguard.data.repository.fakeDeviceEntryFingerprintAuthRepository import com.android.systemui.keyguard.domain.interactor.FromLockscreenTransitionInteractor import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor @@ -50,6 +50,7 @@ import com.android.systemui.keyguard.domain.interactor.fromLockscreenTransitionI import com.android.systemui.keyguard.domain.interactor.fromPrimaryBouncerTransitionInteractor import com.android.systemui.keyguard.domain.interactor.keyguardClockInteractor import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor +import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus import com.android.systemui.kosmos.testScope import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.FakePowerRepository @@ -309,17 +310,20 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { underTest.addCallback(listener) val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + val deviceUnlockStatus by + collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus) + kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - kosmos.fakeDeviceEntryRepository.setUnlocked(false) runCurrent() + assertThat(deviceUnlockStatus!!.isUnlocked).isFalse() + kosmos.sceneInteractor.changeScene( toScene = Scenes.Lockscreen, loggingReason = "reason" ) runCurrent() - assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isFalse() assertThat(currentScene).isEqualTo(Scenes.Lockscreen) // Call start to begin hydrating based on the scene framework: @@ -371,14 +375,20 @@ class StatusBarStateControllerImplTest : SysuiTestCase() { underTest.addCallback(listener) val currentScene by collectLastValue(kosmos.sceneInteractor.currentScene) + val deviceUnlockStatus by + collectLastValue(kosmos.deviceUnlockedInteractor.deviceUnlockStatus) kosmos.fakeAuthenticationRepository.setAuthenticationMethod( AuthenticationMethodModel.Password ) - kosmos.fakeDeviceEntryRepository.setUnlocked(true) + kosmos.fakeDeviceEntryFingerprintAuthRepository.setAuthenticationStatus( + SuccessFingerprintAuthenticationStatus(0, true) + ) runCurrent() + + assertThat(deviceUnlockStatus!!.isUnlocked).isTrue() + kosmos.sceneInteractor.changeScene(toScene = Scenes.Gone, loggingReason = "reason") runCurrent() - assertThat(kosmos.deviceUnlockedInteractor.isDeviceUnlocked.value).isTrue() assertThat(currentScene).isEqualTo(Scenes.Gone) // Call start to begin hydrating based on the scene framework: diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt index 581ca3b14822..4ace163164f1 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/SensitiveNotificationProtectionControllerTest.kt @@ -28,8 +28,10 @@ import android.app.NotificationManager.VISIBILITY_NO_OVERRIDE import android.content.pm.PackageManager import android.media.projection.MediaProjectionInfo import android.media.projection.MediaProjectionManager +import android.os.Process import android.os.UserHandle import android.permission.flags.Flags.FLAG_SENSITIVE_NOTIFICATION_APP_PROTECTION +import android.platform.test.annotations.DisableFlags import android.platform.test.annotations.EnableFlags import android.platform.test.annotations.RequiresFlagsDisabled import android.platform.test.annotations.RequiresFlagsEnabled @@ -41,7 +43,8 @@ import androidx.test.filters.SmallTest import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession import com.android.dx.mockito.inline.extended.ExtendedMockito.verify import com.android.internal.util.FrameworkStatsLog -import com.android.server.notification.Flags +import com.android.server.notification.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING +import com.android.systemui.Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX import com.android.systemui.SysuiTestCase import com.android.systemui.log.logcatLogBuffer import com.android.systemui.statusbar.RankingBuilder @@ -77,7 +80,7 @@ import org.mockito.quality.Strictness @SmallTest @RunWith(AndroidTestingRunner::class) @RunWithLooper -@EnableFlags(Flags.FLAG_SCREENSHARE_NOTIFICATION_HIDING) +@EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING) class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { @get:Rule val checkFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule() @@ -384,13 +387,33 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { } @Test + @DisableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) + fun shouldProtectNotification_projectionActive_isFromCoreApp_fixDisabled_true() { + mediaProjectionCallback.onStart(mediaProjectionInfo) + + val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) + + assertTrue(controller.shouldProtectNotification(notificationEntry)) + } + + @Test + @EnableFlags(FLAG_SCREENSHARE_NOTIFICATION_HIDING_BUG_FIX) + fun shouldProtectNotification_projectionActive_isFromCoreApp_false() { + mediaProjectionCallback.onStart(mediaProjectionInfo) + + val notificationEntry = setupCoreAppNotificationEntry(TEST_PROJECTION_PACKAGE_NAME) + + assertFalse(controller.shouldProtectNotification(notificationEntry)) + } + + @Test fun shouldProtectNotification_projectionActive_sysuiExempt_false() { // SystemUi context package name is exempt, but in test scenarios its // com.android.systemui.tests so use that instead of hardcoding setShareFullScreenViaSystemUi() mediaProjectionCallback.onStart(mediaProjectionInfo) - val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false) + val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) assertFalse(controller.shouldProtectNotification(notificationEntry)) } @@ -407,7 +430,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { .thenReturn(PackageManager.PERMISSION_GRANTED) mediaProjectionCallback.onStart(mediaProjectionInfo) - val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false) + val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) assertTrue(controller.shouldProtectNotification(notificationEntry)) } @@ -424,7 +447,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { .thenReturn(PackageManager.PERMISSION_GRANTED) mediaProjectionCallback.onStart(mediaProjectionInfo) - val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false) + val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) assertFalse(controller.shouldProtectNotification(notificationEntry)) } @@ -434,7 +457,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { setShareFullScreenViaBugReportHandler() mediaProjectionCallback.onStart(mediaProjectionInfo) - val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME, false) + val notificationEntry = setupNotificationEntry(TEST_PACKAGE_NAME) assertFalse(controller.shouldProtectNotification(notificationEntry)) } @@ -657,6 +680,7 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { private fun setupNotificationEntry( packageName: String, isFgs: Boolean = false, + isCoreApp: Boolean = false, overrideVisibility: Boolean = false, overrideChannelVisibility: Boolean = false, ): NotificationEntry { @@ -668,8 +692,14 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { // Developer has marked notification as public notification.visibility = VISIBILITY_PUBLIC } - val notificationEntry = - NotificationEntryBuilder().setNotification(notification).setPkg(packageName).build() + val notificationEntryBuilder = + NotificationEntryBuilder().setNotification(notification).setPkg(packageName) + if (isCoreApp) { + notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID - 10) + } else { + notificationEntryBuilder.setUid(Process.FIRST_APPLICATION_UID + 10) + } + val notificationEntry = notificationEntryBuilder.build() val channel = NotificationChannel("1", "1", IMPORTANCE_HIGH) if (overrideChannelVisibility) { // User doesn't allow private notifications at the channel level @@ -688,6 +718,10 @@ class SensitiveNotificationProtectionControllerTest : SysuiTestCase() { return setupNotificationEntry(packageName, isFgs = true) } + private fun setupCoreAppNotificationEntry(packageName: String): NotificationEntry { + return setupNotificationEntry(packageName, isCoreApp = true) + } + private fun setupPublicNotificationEntry(packageName: String): NotificationEntry { return setupNotificationEntry(packageName, overrideVisibility = true) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt index 77caeaa6da4d..045bd5d286df 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/data/repository/FakeDeviceEntryRepository.kt @@ -21,7 +21,6 @@ import dagger.Module import javax.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow /** Fake implementation of [DeviceEntryRepository] */ @SysUISingleton @@ -31,21 +30,10 @@ class FakeDeviceEntryRepository @Inject constructor() : DeviceEntryRepository { private val _isBypassEnabled = MutableStateFlow(false) override val isBypassEnabled: StateFlow<Boolean> = _isBypassEnabled - private val _isUnlocked = MutableStateFlow(false) - override val isUnlocked: StateFlow<Boolean> = _isUnlocked.asStateFlow() - override suspend fun isLockscreenEnabled(): Boolean { return isLockscreenEnabled } - override fun reportSuccessfulAuthentication() { - _isUnlocked.value = true - } - - fun setUnlocked(isUnlocked: Boolean) { - _isUnlocked.value = isUnlocked - } - fun setLockscreenEnabled(isLockscreenEnabled: Boolean) { this.isLockscreenEnabled = isLockscreenEnabled } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt index e73e2950bbb9..bff10a191d5a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt @@ -23,7 +23,6 @@ import com.android.systemui.keyguard.domain.interactor.trustInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.scene.domain.interactor.sceneInteractor -import com.android.systemui.scene.shared.flag.sceneContainerFlags import kotlinx.coroutines.ExperimentalCoroutinesApi @ExperimentalCoroutinesApi @@ -35,11 +34,10 @@ val Kosmos.deviceEntryInteractor by authenticationInteractor = authenticationInteractor, sceneInteractor = sceneInteractor, faceAuthInteractor = deviceEntryFaceAuthInteractor, - trustInteractor = trustInteractor, - flags = sceneContainerFlags, - deviceUnlockedInteractor = deviceUnlockedInteractor, fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, biometricSettingsInteractor = deviceEntryBiometricSettingsInteractor, + trustInteractor = trustInteractor, + deviceUnlockedInteractor = deviceUnlockedInteractor, systemPropertiesHelper = fakeSystemPropertiesHelper, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt index df1cdc2f72cb..14210bc8d15c 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt @@ -18,14 +18,20 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.authentication.domain.interactor.authenticationInteractor import com.android.systemui.deviceentry.data.repository.deviceEntryRepository +import com.android.systemui.keyguard.domain.interactor.trustInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.power.domain.interactor.powerInteractor val Kosmos.deviceUnlockedInteractor by Fixture { DeviceUnlockedInteractor( applicationScope = applicationCoroutineScope, authenticationInteractor = authenticationInteractor, deviceEntryRepository = deviceEntryRepository, + trustInteractor = trustInteractor, + faceAuthInteractor = deviceEntryFaceAuthInteractor, + fingerprintAuthInteractor = deviceEntryFingerprintAuthInteractor, + powerInteractor = powerInteractor, ) } 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 f65c74fcebc8..5934e0493efd 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 @@ -22,6 +22,7 @@ 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_MEDIA_IN_SCENE_CONTAINER import com.android.systemui.Flags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT +import com.android.systemui.Flags.FLAG_PREDICTIVE_BACK_SYSUI import com.android.systemui.Flags.FLAG_SCENE_CONTAINER /** @@ -35,6 +36,7 @@ import com.android.systemui.Flags.FLAG_SCENE_CONTAINER FLAG_COMPOSE_LOCKSCREEN, FLAG_MEDIA_IN_SCENE_CONTAINER, FLAG_KEYGUARD_WM_STATE_REFACTOR, + FLAG_PREDICTIVE_BACK_SYSUI, ) @Retention(AnnotationRetention.RUNTIME) @Target(AnnotationTarget.FUNCTION, AnnotationTarget.CLASS) diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt index 394c8733487a..695e59484014 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/statusbar/StatusBarStateControllerKosmos.kt @@ -24,9 +24,10 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.shade.domain.interactor.shadeInteractor import com.android.systemui.statusbar.StatusBarStateControllerImpl +import com.android.systemui.statusbar.SysuiStatusBarStateController import com.android.systemui.util.mockito.mock -var Kosmos.statusBarStateController by +var Kosmos.statusBarStateController: SysuiStatusBarStateController by Kosmos.Fixture { StatusBarStateControllerImpl( uiEventLogger, diff --git a/packages/SystemUI/src-release/com/android/systemui/util/Compile.java b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt index 8a6376326660..b7e31db2e42f 100644 --- a/packages/SystemUI/src-release/com/android/systemui/util/Compile.java +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/sensorprivacy/SensorPrivacyTileKosmos.kt @@ -1,5 +1,5 @@ /* - * 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,10 +14,14 @@ * limitations under the License. */ -package com.android.systemui.util; +package com.android.systemui.qs.tiles.impl.sensorprivacy -/** Constants that vary by compilation configuration. */ -public class Compile { - /** Whether SystemUI was compiled in debug mode, and supports debug features */ - public static final boolean IS_DEBUG = false; -} +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.qs.qsEventLogger +import com.android.systemui.statusbar.policy.PolicyModule + +val Kosmos.qsCameraSensorPrivacyToggleTileConfig by + Kosmos.Fixture { PolicyModule.provideCameraToggleTileConfig(qsEventLogger) } + +val Kosmos.qsMicrophoneSensorPrivacyToggleTileConfig by + Kosmos.Fixture { PolicyModule.provideMicrophoneToggleTileConfig(qsEventLogger) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt index 4a2eaf0f7bf6..d08855f190ed 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/ShadeControllerKosmos.kt @@ -21,6 +21,7 @@ package com.android.systemui.shade import com.android.systemui.assist.AssistManager import com.android.systemui.concurrency.fakeExecutor import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor +import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher @@ -58,6 +59,7 @@ val Kosmos.shadeControllerSceneImpl by statusBarKeyguardViewManager = mock<StatusBarKeyguardViewManager>(), notificationShadeWindowController = mock<NotificationShadeWindowController>(), assistManagerLazy = { mock<AssistManager>() }, + deviceUnlockedInteractor = deviceUnlockedInteractor, ) } diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java new file mode 100644 index 000000000000..c11c1bba25a7 --- /dev/null +++ b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodAndroidApiTest.java @@ -0,0 +1,47 @@ +/* + * 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.platform.test.ravenwood.bivalenttest; + +import static org.junit.Assert.assertEquals; + +import android.util.ArrayMap; +import android.util.Size; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Map; + +// Tests for calling simple Android APIs. +@RunWith(AndroidJUnit4.class) +public class RavenwoodAndroidApiTest { + @Test + public void testArrayMapSimple() { + final Map<String, String> map = new ArrayMap<>(); + + map.put("key1", "value1"); + assertEquals("value1", map.get("key1")); + } + + @Test + public void testSizeSimple() { + final var size = new Size(1, 2); + + assertEquals(2, size.getHeight()); + } +} diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java new file mode 100644 index 000000000000..6f2465c406d3 --- /dev/null +++ b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleDeviceOnlyTest.java @@ -0,0 +1,39 @@ +/* + * 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.platform.test.ravenwood.bivalenttest; + +import android.platform.test.annotations.DisabledOnRavenwood; +import android.platform.test.ravenwood.RavenwoodClassRule; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@DisabledOnRavenwood +public class RavenwoodClassRuleDeviceOnlyTest { + @ClassRule + public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule(); + + @Test + public void testDeviceOnly() { + Assert.assertFalse(RavenwoodRule.isOnRavenwood()); + } +} diff --git a/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java new file mode 100644 index 000000000000..21b31d1ca3e8 --- /dev/null +++ b/ravenwood/bivalenttest/test/com/android/platform/test/ravenwood/bivalenttest/RavenwoodClassRuleRavenwoodOnlyTest.java @@ -0,0 +1,43 @@ +/* + * 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.platform.test.ravenwood.bivalenttest; + +import android.platform.test.ravenwood.RavenwoodClassRule; +import android.platform.test.ravenwood.RavenwoodRule; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +// TODO: atest RavenwoodBivalentTest_device fails with the following message. +// `RUNNER ERROR: Instrumentation reported numtests=7 but only ran 6` +// @android.platform.test.annotations.DisabledOnNonRavenwood +// Figure it out and then make DisabledOnNonRavenwood support TYPEs as well. +@Ignore +public class RavenwoodClassRuleRavenwoodOnlyTest { + @ClassRule + public static final RavenwoodClassRule sRavenwood = new RavenwoodClassRule(); + + @Test + public void testRavenwoodOnly() { + Assert.assertTrue(RavenwoodRule.isOnRavenwood()); + } +} diff --git a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java index 2cd585ff6c9c..4ee9a9c94826 100644 --- a/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java +++ b/ravenwood/coretest/test/com/android/platform/test/ravenwood/coretest/RavenwoodTestRunnerValidationTest.java @@ -40,7 +40,7 @@ public class RavenwoodTestRunnerValidationTest { public final RuleChain chain = RuleChain.outerRule(mThrown).around(mRavenwood); public RavenwoodTestRunnerValidationTest() { - Assume.assumeTrue(mRavenwood._ravenwood_private$isOptionalValidationEnabled()); + Assume.assumeTrue(RavenwoodRule._$RavenwoodPrivate.isOptionalValidationEnabled()); // Because RavenwoodRule will throw this error before executing the test method, // we can't do it in the test method itself. // So instead, we initialize it here. diff --git a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java index 8ca34bafc2c6..9d47f3a6bc5d 100644 --- a/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java +++ b/ravenwood/junit-src/android/platform/test/annotations/DisabledOnNonRavenwood.java @@ -31,13 +31,17 @@ import java.lang.annotation.Target; * which means if a test class has this annotation, you can't negate it in subclasses or * on a per-method basis. * + * THIS ANNOTATION CANNOT BE ADDED TO CLASSES AT THIS PONINT. + * See {@link com.android.platform.test.ravenwood.bivalenttest.RavenwoodClassRuleRavenwoodOnlyTest} + * for the reason. + * * The {@code RAVENWOOD_RUN_DISABLED_TESTS} environmental variable won't work because it won't be * propagated to the device. (We may support it in the future, possibly using a debug. sysprop.) * * @hide */ @Inherited -@Target({ElementType.METHOD, ElementType.TYPE}) +@Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface DisabledOnNonRavenwood { /** diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java index 9a4d4886d6f0..f4b7ec360dbf 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodClassRule.java @@ -25,6 +25,7 @@ import static android.platform.test.ravenwood.RavenwoodRule.shouldStillIgnoreInP import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.EnabledOnRavenwood; +import org.junit.Assert; import org.junit.Assume; import org.junit.rules.TestRule; import org.junit.runner.Description; @@ -41,27 +42,16 @@ import org.junit.runners.model.Statement; public class RavenwoodClassRule implements TestRule { @Override public Statement apply(Statement base, Description description) { - // No special treatment when running outside Ravenwood; run tests as-is if (!IS_ON_RAVENWOOD) { - Assume.assumeTrue(shouldEnableOnDevice(description)); - return base; - } - - if (ENABLE_PROBE_IGNORED) { + // This should be "Assume", not Assert, but if we use assume here, the device side + // test runner would complain. + // See the TODO comment in RavenwoodClassRuleRavenwoodOnlyTest. + Assert.assertTrue(shouldEnableOnDevice(description)); + } else if (ENABLE_PROBE_IGNORED) { Assume.assumeFalse(shouldStillIgnoreInProbeIgnoreMode(description)); - // Pass through to possible underlying RavenwoodRule for both environment - // configuration and handling method-level annotations - return base; } else { - return new Statement() { - @Override - public void evaluate() throws Throwable { - Assume.assumeTrue(shouldEnableOnRavenwood(description)); - // Pass through to possible underlying RavenwoodRule for both environment - // configuration and handling method-level annotations - base.evaluate(); - } - }; + Assume.assumeTrue(shouldEnableOnRavenwood(description)); } + return base; } } diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java index 52ea3402fa62..a2e8ec17bbf7 100644 --- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java +++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java @@ -28,7 +28,6 @@ import android.platform.test.annotations.DisabledOnNonRavenwood; import android.platform.test.annotations.DisabledOnRavenwood; import android.platform.test.annotations.EnabledOnRavenwood; import android.platform.test.annotations.IgnoreUnderRavenwood; -import android.util.ArraySet; import org.junit.Assume; import org.junit.rules.TestRule; @@ -278,6 +277,9 @@ public class RavenwoodRule implements TestRule { return false; } } + if (description.getTestClass().getAnnotation(DisabledOnNonRavenwood.class) != null) { + return false; + } return true; } @@ -413,10 +415,9 @@ public class RavenwoodRule implements TestRule { }; } - /** - * Do not use it outside ravenwood core classes. - */ - public boolean _ravenwood_private$isOptionalValidationEnabled() { - return ENABLE_OPTIONAL_VALIDATION; + public static class _$RavenwoodPrivate { + public static boolean isOptionalValidationEnabled() { + return ENABLE_OPTIONAL_VALIDATION; + } } } diff --git a/services/Android.bp b/services/Android.bp index 29d1acf5f350..881d6e12ddc7 100644 --- a/services/Android.bp +++ b/services/Android.bp @@ -286,6 +286,11 @@ filegroup { // API stub // ============================================================= +soong_config_module_type_import { + from: "frameworks/base/api/Android.bp", + module_types: ["non_updatable_exportable_droidstubs"], +} + stubs_defaults { name: "services-stubs-default", installable: false, @@ -301,10 +306,12 @@ stubs_defaults { filter_packages: ["com.android."], } -droidstubs { +non_updatable_exportable_droidstubs { name: "services-non-updatable-stubs", srcs: [":services-non-updatable-sources"], - defaults: ["services-stubs-default"], + defaults: [ + "services-stubs-default", + ], check_api: { current: { api_file: "api/current.txt", @@ -321,14 +328,34 @@ droidstubs { targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "android-non-updatable.txt", - tag: ".api.txt", }, { targets: ["sdk"], dir: "apistubs/android/system-server/api", dest: "android-non-updatable-removed.txt", - tag: ".removed-api.txt", }, ], + soong_config_variables: { + release_hidden_api_exportable_stubs: { + dists: [ + { + tag: ".exportable.api.txt", + }, + { + tag: ".exportable.removed-api.txt", + }, + ], + conditions_default: { + dists: [ + { + tag: ".api.txt", + }, + { + tag: ".removed-api.txt", + }, + ], + }, + }, + }, api_surface: "system-server", } diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java index 8ac1eb9c90b7..4dae6d5f31e7 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java @@ -103,10 +103,10 @@ import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; import com.android.server.companion.datatransfer.contextsync.CrossDeviceCall; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncControllerCallback; -import com.android.server.companion.presence.CompanionAppBinder; -import com.android.server.companion.presence.DevicePresenceProcessor; -import com.android.server.companion.presence.ObservableUuid; -import com.android.server.companion.presence.ObservableUuidStore; +import com.android.server.companion.devicepresence.CompanionAppBinder; +import com.android.server.companion.devicepresence.DevicePresenceProcessor; +import com.android.server.companion.devicepresence.ObservableUuid; +import com.android.server.companion.devicepresence.ObservableUuidStore; import com.android.server.companion.transport.CompanionTransportManager; import com.android.server.pm.UserManagerInternal; import com.android.server.wm.ActivityTaskManagerInternal; diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java index daa8fdbcab75..9cfb5351f6cf 100644 --- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java +++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java @@ -36,8 +36,8 @@ import com.android.server.companion.association.DisassociationProcessor; import com.android.server.companion.datatransfer.SystemDataTransferProcessor; import com.android.server.companion.datatransfer.contextsync.BitmapUtils; import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController; -import com.android.server.companion.presence.DevicePresenceProcessor; -import com.android.server.companion.presence.ObservableUuid; +import com.android.server.companion.devicepresence.DevicePresenceProcessor; +import com.android.server.companion.devicepresence.ObservableUuid; import com.android.server.companion.transport.CompanionTransportManager; import java.io.PrintWriter; diff --git a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java index acf683d387a3..8c1116b7a612 100644 --- a/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java +++ b/services/companion/java/com/android/server/companion/association/DisassociationProcessor.java @@ -37,8 +37,8 @@ import android.os.UserHandle; import android.util.Slog; import com.android.server.companion.datatransfer.SystemDataTransferRequestStore; -import com.android.server.companion.presence.CompanionAppBinder; -import com.android.server.companion.presence.DevicePresenceProcessor; +import com.android.server.companion.devicepresence.CompanionAppBinder; +import com.android.server.companion.devicepresence.DevicePresenceProcessor; import com.android.server.companion.transport.CompanionTransportManager; /** diff --git a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java index 407b9daed11c..6cdc02ec67a2 100644 --- a/services/companion/java/com/android/server/companion/presence/BleCompanionDeviceScanner.java +++ b/services/companion/java/com/android/server/companion/devicepresence/BleDeviceProcessor.java @@ -15,27 +15,15 @@ */ -package com.android.server.companion.presence; +package com.android.server.companion.devicepresence; import static android.bluetooth.BluetoothAdapter.ACTION_BLE_STATE_CHANGED; import static android.bluetooth.BluetoothAdapter.ACTION_STATE_CHANGED; -import static android.bluetooth.BluetoothAdapter.EXTRA_PREVIOUS_STATE; -import static android.bluetooth.BluetoothAdapter.EXTRA_STATE; -import static android.bluetooth.BluetoothAdapter.nameForState; -import static android.bluetooth.le.ScanCallback.SCAN_FAILED_ALREADY_STARTED; -import static android.bluetooth.le.ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED; -import static android.bluetooth.le.ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED; -import static android.bluetooth.le.ScanCallback.SCAN_FAILED_INTERNAL_ERROR; -import static android.bluetooth.le.ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES; -import static android.bluetooth.le.ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_ALL_MATCHES; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_FIRST_MATCH; import static android.bluetooth.le.ScanSettings.CALLBACK_TYPE_MATCH_LOST; import static android.bluetooth.le.ScanSettings.SCAN_MODE_LOW_POWER; -import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG; -import static com.android.server.companion.utils.Utils.btDeviceToString; - import static java.util.Objects.requireNonNull; import android.annotation.MainThread; @@ -56,21 +44,19 @@ import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; import android.os.Looper; -import android.util.Log; import android.util.Slog; import com.android.server.companion.association.AssociationStore; import com.android.server.companion.association.AssociationStore.ChangeType; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashSet; import java.util.List; import java.util.Set; @SuppressLint("LongLogTag") -class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { - private static final String TAG = "CDM_BleCompanionDeviceScanner"; +class BleDeviceProcessor implements AssociationStore.OnChangeListener { + private static final String TAG = "CDM_BleDeviceProcessor"; interface Callback { void onBleCompanionDeviceFound(int associationId, int userId); @@ -78,26 +64,27 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { void onBleCompanionDeviceLost(int associationId, int userId); } - private final @NonNull AssociationStore mAssociationStore; - private final @NonNull Callback mCallback; + @NonNull + private final AssociationStore mAssociationStore; + @NonNull + private final Callback mCallback; // Non-null after init(). - private @Nullable BluetoothAdapter mBtAdapter; + @Nullable + private BluetoothAdapter mBtAdapter; // Non-null after init() and when BLE is available. Otherwise - null. - private @Nullable BluetoothLeScanner mBleScanner; + @Nullable + private BluetoothLeScanner mBleScanner; // Only accessed from the Main thread. private boolean mScanning = false; - BleCompanionDeviceScanner( - @NonNull AssociationStore associationStore, @NonNull Callback callback) { + BleDeviceProcessor(@NonNull AssociationStore associationStore, @NonNull Callback callback) { mAssociationStore = associationStore; mCallback = callback; } @MainThread void init(@NonNull Context context, @NonNull BluetoothAdapter btAdapter) { - if (DEBUG) Log.i(TAG, "init()"); - if (mBtAdapter != null) { throw new IllegalStateException(getClass().getSimpleName() + " is already initialized"); } @@ -113,9 +100,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { final void restartScan() { enforceInitialized(); - if (DEBUG) Log.i(TAG , "restartScan()"); if (mBleScanner == null) { - if (DEBUG) Log.d(TAG, " > BLE is not available"); return; } @@ -138,12 +123,8 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { enforceInitialized(); final boolean bleAvailable = mBtAdapter.isLeEnabled(); - if (DEBUG) { - Log.i(TAG, "checkBleState() bleAvailable=" + bleAvailable); - } if ((bleAvailable && mBleScanner != null) || (!bleAvailable && mBleScanner == null)) { // Nothing changed. - if (DEBUG) Log.i(TAG, " > BLE status did not change"); return; } @@ -153,12 +134,9 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { // Oops, that's a race condition. Can return. return; } - if (DEBUG) Log.i(TAG, " > BLE is now available"); startScan(); } else { - if (DEBUG) Log.i(TAG, " > BLE is now unavailable"); - stopScanIfNeeded(); mBleScanner = null; } @@ -194,13 +172,7 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { } } if (macAddresses.isEmpty()) { - if (DEBUG) Log.i(TAG, " > there are no (associated) devices to Scan for."); return; - } else { - if (DEBUG) { - Log.d(TAG, " > addresses=(n=" + macAddresses.size() + ")" - + "[" + String.join(", ", macAddresses) + "]"); - } } final List<ScanFilter> filters = new ArrayList<>(macAddresses.size()); @@ -230,7 +202,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { Slog.i(TAG, "stopBleScan()"); if (!mScanning) { - if (DEBUG) Log.d(TAG, " > not scanning."); return; } // mScanCallback is non-null here - it cannot be null when mScanning is true. @@ -252,26 +223,16 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { @MainThread private void notifyDeviceFound(@NonNull BluetoothDevice device) { - if (DEBUG) Log.i(TAG, "notifyDevice_Found()" + btDeviceToString(device)); - - final List<AssociationInfo> associations = - mAssociationStore.getActiveAssociationsByAddress(device.getAddress()); - if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); - - for (AssociationInfo association : associations) { + for (AssociationInfo association : mAssociationStore.getActiveAssociationsByAddress( + device.getAddress())) { mCallback.onBleCompanionDeviceFound(association.getId(), association.getUserId()); } } @MainThread private void notifyDeviceLost(@NonNull BluetoothDevice device) { - if (DEBUG) Log.i(TAG, "notifyDevice_Lost()" + btDeviceToString(device)); - - final List<AssociationInfo> associations = - mAssociationStore.getActiveAssociationsByAddress(device.getAddress()); - if (DEBUG) Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); - - for (AssociationInfo association : associations) { + for (AssociationInfo association : mAssociationStore.getActiveAssociationsByAddress( + device.getAddress())) { mCallback.onBleCompanionDeviceLost(association.getId(), association.getUserId()); } } @@ -280,17 +241,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { final BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - final int prevState = intent.getIntExtra(EXTRA_PREVIOUS_STATE, -1); - final int state = intent.getIntExtra(EXTRA_STATE, -1); - - if (DEBUG) { - // The action is either STATE_CHANGED or BLE_STATE_CHANGED. - final String action = - intent.getAction().replace("android.bluetooth.adapter.", "bt."); - Log.d(TAG, "on(Broadcast)Receive() " + action + ": " - + nameForBtState(prevState) + "->" + nameForBtState(state)); - } - checkBleState(); } }; @@ -313,16 +263,6 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { public void onScanResult(int callbackType, ScanResult result) { final BluetoothDevice device = result.getDevice(); - if (DEBUG) { - Log.d(TAG, "onScanResult() " + nameForBleScanCallbackType(callbackType) - + " device=" + btDeviceToString(device)); - Log.v(TAG, " > scanResult=" + result); - - final List<AssociationInfo> associations = - mAssociationStore.getActiveAssociationsByAddress(device.getAddress()); - Log.v(TAG, " > associations=" + Arrays.toString(associations.toArray())); - } - switch (callbackType) { case CALLBACK_TYPE_FIRST_MATCH: notifyDeviceFound(device); @@ -342,60 +282,20 @@ class BleCompanionDeviceScanner implements AssociationStore.OnChangeListener { @MainThread @Override public void onScanFailed(int errorCode) { - if (DEBUG) Log.w(TAG, "onScanFailed() " + nameForBleScanErrorCode(errorCode)); mScanning = false; } }; - private static String nameForBtState(int state) { - return nameForState(state) + "(" + state + ")"; - } - private static String nameForBleScanCallbackType(int callbackType) { - final String name; - switch (callbackType) { - case CALLBACK_TYPE_ALL_MATCHES: - name = "ALL_MATCHES"; - break; - case CALLBACK_TYPE_FIRST_MATCH: - name = "FIRST_MATCH"; - break; - case CALLBACK_TYPE_MATCH_LOST: - name = "MATCH_LOST"; - break; - default: - name = "Unknown"; - } + final String name = switch (callbackType) { + case CALLBACK_TYPE_ALL_MATCHES -> "ALL_MATCHES"; + case CALLBACK_TYPE_FIRST_MATCH -> "FIRST_MATCH"; + case CALLBACK_TYPE_MATCH_LOST -> "MATCH_LOST"; + default -> "Unknown"; + }; return name + "(" + callbackType + ")"; } - private static String nameForBleScanErrorCode(int errorCode) { - final String name; - switch (errorCode) { - case SCAN_FAILED_ALREADY_STARTED: - name = "ALREADY_STARTED"; - break; - case SCAN_FAILED_APPLICATION_REGISTRATION_FAILED: - name = "APPLICATION_REGISTRATION_FAILED"; - break; - case SCAN_FAILED_INTERNAL_ERROR: - name = "INTERNAL_ERROR"; - break; - case SCAN_FAILED_FEATURE_UNSUPPORTED: - name = "FEATURE_UNSUPPORTED"; - break; - case SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES: - name = "OUT_OF_HARDWARE_RESOURCES"; - break; - case SCAN_FAILED_SCANNING_TOO_FREQUENTLY: - name = "SCANNING_TOO_FREQUENTLY"; - break; - default: - name = "Unknown"; - } - return name + "(" + errorCode + ")"; - } - private static final ScanSettings SCAN_SETTINGS = new ScanSettings.Builder() .setCallbackType(CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST) .setScanMode(SCAN_MODE_LOW_POWER) diff --git a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java b/services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java index e1a8db41f433..612c156d867e 100644 --- a/services/companion/java/com/android/server/companion/presence/BluetoothCompanionDeviceConnectionListener.java +++ b/services/companion/java/com/android/server/companion/devicepresence/BluetoothDeviceProcessor.java @@ -14,14 +14,11 @@ * limitations under the License. */ -package com.android.server.companion.presence; +package com.android.server.companion.devicepresence; import static android.companion.DevicePresenceEvent.EVENT_BT_CONNECTED; import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED; -import static com.android.server.companion.presence.DevicePresenceProcessor.DEBUG; -import static com.android.server.companion.utils.Utils.btDeviceToString; - import android.annotation.NonNull; import android.annotation.SuppressLint; import android.bluetooth.BluetoothAdapter; @@ -32,8 +29,6 @@ import android.os.Handler; import android.os.HandlerExecutor; import android.os.ParcelUuid; import android.os.UserHandle; -import android.os.UserManager; -import android.util.Log; import com.android.internal.util.ArrayUtils; import com.android.server.companion.association.AssociationStore; @@ -45,10 +40,10 @@ import java.util.List; import java.util.Map; @SuppressLint("LongLogTag") -public class BluetoothCompanionDeviceConnectionListener +public class BluetoothDeviceProcessor extends BluetoothAdapter.BluetoothConnectionCallback implements AssociationStore.OnChangeListener { - private static final String TAG = "CDM_BluetoothCompanionDeviceConnectionListener"; + private static final String TAG = "CDM_BluetoothDeviceProcessor"; interface Callback { void onBluetoothCompanionDeviceConnected(int associationId, int userId); @@ -58,24 +53,25 @@ public class BluetoothCompanionDeviceConnectionListener void onDevicePresenceEventByUuid(ObservableUuid uuid, int event); } - private final @NonNull AssociationStore mAssociationStore; - private final @NonNull Callback mCallback; - /** A set of ALL connected BT device (not only companion.) */ - private final @NonNull Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>(); + @NonNull + private final AssociationStore mAssociationStore; + @NonNull + private final ObservableUuidStore mObservableUuidStore; + @NonNull + private final Callback mCallback; - private final @NonNull ObservableUuidStore mObservableUuidStore; + /** A set of ALL connected BT device (not only companion.) */ + @NonNull + private final Map<MacAddress, BluetoothDevice> mAllConnectedDevices = new HashMap<>(); - BluetoothCompanionDeviceConnectionListener(UserManager userManager, - @NonNull AssociationStore associationStore, + BluetoothDeviceProcessor(@NonNull AssociationStore associationStore, @NonNull ObservableUuidStore observableUuidStore, @NonNull Callback callback) { mAssociationStore = associationStore; mObservableUuidStore = observableUuidStore; mCallback = callback; } - public void init(@NonNull BluetoothAdapter btAdapter) { - if (DEBUG) Log.i(TAG, "init()"); - + void init(@NonNull BluetoothAdapter btAdapter) { btAdapter.registerBluetoothConnectionCallback( new HandlerExecutor(Handler.getMain()), /* callback */this); mAssociationStore.registerLocalListener(this); @@ -87,13 +83,9 @@ public class BluetoothCompanionDeviceConnectionListener */ @Override public void onDeviceConnected(@NonNull BluetoothDevice device) { - if (DEBUG) Log.i(TAG, "onDevice_Connected() " + btDeviceToString(device)); - final MacAddress macAddress = MacAddress.fromString(device.getAddress()); - final int userId = UserHandle.myUserId(); if (mAllConnectedDevices.put(macAddress, device) != null) { - if (DEBUG) Log.w(TAG, "Device " + btDeviceToString(device) + " is already connected."); return; } @@ -108,18 +100,9 @@ public class BluetoothCompanionDeviceConnectionListener @Override public void onDeviceDisconnected(@NonNull BluetoothDevice device, int reason) { - if (DEBUG) { - Log.i(TAG, "onDevice_Disconnected() " + btDeviceToString(device)); - Log.d(TAG, " reason=" + disconnectReasonToString(reason)); - } - final MacAddress macAddress = MacAddress.fromString(device.getAddress()); - final int userId = UserHandle.myUserId(); if (mAllConnectedDevices.remove(macAddress) == null) { - if (DEBUG) { - Log.w(TAG, "The device wasn't tracked as connected " + btDeviceToString(device)); - } return; } @@ -130,22 +113,6 @@ public class BluetoothCompanionDeviceConnectionListener int userId = UserHandle.myUserId(); final List<AssociationInfo> associations = mAssociationStore.getActiveAssociationsByAddress(device.getAddress()); - final List<ObservableUuid> observableUuids = - mObservableUuidStore.getObservableUuidsForUser(userId); - final ParcelUuid[] bluetoothDeviceUuids = device.getUuids(); - - final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids) - ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids); - - if (DEBUG) { - Log.d(TAG, "onDevice_ConnectivityChanged() " + btDeviceToString(device) - + " connected=" + connected); - if (associations.isEmpty()) { - Log.d(TAG, " > No CDM associations"); - } else { - Log.d(TAG, " > associations=" + Arrays.toString(associations.toArray())); - } - } for (AssociationInfo association : associations) { if (!association.isNotifyOnDeviceNearby()) continue; @@ -157,44 +124,25 @@ public class BluetoothCompanionDeviceConnectionListener } } + final List<ObservableUuid> observableUuids = + mObservableUuidStore.getObservableUuidsForUser(userId); + final ParcelUuid[] bluetoothDeviceUuids = device.getUuids(); + final List<ParcelUuid> deviceUuids = ArrayUtils.isEmpty(bluetoothDeviceUuids) + ? Collections.emptyList() : Arrays.asList(bluetoothDeviceUuids); + for (ObservableUuid uuid : observableUuids) { if (deviceUuids.contains(uuid.getUuid())) { mCallback.onDevicePresenceEventByUuid(uuid, connected ? EVENT_BT_CONNECTED - : EVENT_BT_DISCONNECTED); + : EVENT_BT_DISCONNECTED); } } } @Override public void onAssociationAdded(AssociationInfo association) { - if (DEBUG) Log.d(TAG, "onAssociation_Added() " + association); - if (mAllConnectedDevices.containsKey(association.getDeviceMacAddress())) { mCallback.onBluetoothCompanionDeviceConnected( association.getId(), association.getUserId()); } } - - @Override - public void onAssociationRemoved(AssociationInfo association) { - // Intentionally do nothing: CompanionDevicePresenceMonitor will do all the bookkeeping - // required. - } - - @Override - public void onAssociationUpdated(AssociationInfo association, boolean addressChanged) { - if (DEBUG) { - Log.d(TAG, "onAssociation_Updated() addrChange=" + addressChanged - + " " + association); - } - - if (!addressChanged) { - // Don't need to do anything. - return; - } - - // At the moment CDM does allow changing association addresses, so we will never come here. - // This will be implemented when CDM support updating addresses. - throw new IllegalArgumentException("Address changes are not supported."); - } } diff --git a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java index b6348ea9594d..60f46887fa5c 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionAppBinder.java +++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionAppBinder.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion.presence; +package com.android.server.companion.devicepresence; import android.annotation.NonNull; import android.annotation.Nullable; diff --git a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java b/services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java index c01c3195e04d..5923e70c2300 100644 --- a/services/companion/java/com/android/server/companion/presence/CompanionServiceConnector.java +++ b/services/companion/java/com/android/server/companion/devicepresence/CompanionServiceConnector.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion.presence; +package com.android.server.companion.devicepresence; import static android.content.Context.BIND_ALMOST_PERCEPTIBLE; import static android.content.Context.BIND_TREAT_LIKE_VISIBLE_FOREGROUND_SERVICE; diff --git a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java index 092886cbfe18..cfb7f337242b 100644 --- a/services/companion/java/com/android/server/companion/presence/DevicePresenceProcessor.java +++ b/services/companion/java/com/android/server/companion/devicepresence/DevicePresenceProcessor.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion.presence; +package com.android.server.companion.devicepresence; import static android.companion.AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION; import static android.companion.DevicePresenceEvent.EVENT_BLE_APPEARED; @@ -24,6 +24,7 @@ import static android.companion.DevicePresenceEvent.EVENT_BT_DISCONNECTED; import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_APPEARED; import static android.companion.DevicePresenceEvent.EVENT_SELF_MANAGED_DISAPPEARED; import static android.companion.DevicePresenceEvent.NO_ASSOCIATION; +import static android.content.Context.BLUETOOTH_SERVICE; import static android.os.Process.ROOT_UID; import static android.os.Process.SHELL_UID; @@ -35,6 +36,7 @@ import android.annotation.SuppressLint; import android.annotation.TestApi; import android.annotation.UserIdInt; import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothManager; import android.companion.AssociationInfo; import android.companion.DeviceNotAssociatedException; import android.companion.DevicePresenceEvent; @@ -49,7 +51,6 @@ import android.os.ParcelUuid; import android.os.PowerManagerInternal; import android.os.RemoteException; import android.os.UserManager; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; @@ -80,8 +81,7 @@ import java.util.Set; */ @SuppressLint("LongLogTag") public class DevicePresenceProcessor implements AssociationStore.OnChangeListener, - BluetoothCompanionDeviceConnectionListener.Callback, BleCompanionDeviceScanner.Callback { - static final boolean DEBUG = false; + BluetoothDeviceProcessor.Callback, BleDeviceProcessor.Callback { private static final String TAG = "CDM_DevicePresenceProcessor"; @NonNull @@ -93,9 +93,9 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene @NonNull private final ObservableUuidStore mObservableUuidStore; @NonNull - private final BluetoothCompanionDeviceConnectionListener mBtConnectionListener; + private final BluetoothDeviceProcessor mBluetoothDeviceProcessor; @NonNull - private final BleCompanionDeviceScanner mBleScanner; + private final BleDeviceProcessor mBleDeviceProcessor; @NonNull private final PowerManagerInternal mPowerManagerInternal; @NonNull @@ -142,7 +142,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene public DevicePresenceProcessor(@NonNull Context context, @NonNull CompanionAppBinder companionAppBinder, - UserManager userManager, + @NonNull UserManager userManager, @NonNull AssociationStore associationStore, @NonNull ObservableUuidStore observableUuidStore, @NonNull PowerManagerInternal powerManagerInternal) { @@ -151,26 +151,28 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene mAssociationStore = associationStore; mObservableUuidStore = observableUuidStore; mUserManager = userManager; - mBtConnectionListener = new BluetoothCompanionDeviceConnectionListener(userManager, - associationStore, mObservableUuidStore, - /* BluetoothCompanionDeviceConnectionListener.Callback */ this); - mBleScanner = new BleCompanionDeviceScanner(associationStore, - /* BleCompanionDeviceScanner.Callback */ this); + mBluetoothDeviceProcessor = new BluetoothDeviceProcessor(associationStore, + mObservableUuidStore, this); + mBleDeviceProcessor = new BleDeviceProcessor(associationStore, this); mPowerManagerInternal = powerManagerInternal; } /** Initialize {@link DevicePresenceProcessor} */ public void init(Context context) { - if (DEBUG) Slog.i(TAG, "init()"); - - final BluetoothAdapter btAdapter = BluetoothAdapter.getDefaultAdapter(); - if (btAdapter != null) { - mBtConnectionListener.init(btAdapter); - mBleScanner.init(context, btAdapter); - } else { + BluetoothManager bm = (BluetoothManager) context.getSystemService(BLUETOOTH_SERVICE); + if (bm == null) { + Slog.w(TAG, "BluetoothManager is not available."); + return; + } + final BluetoothAdapter btAdapter = bm.getAdapter(); + if (btAdapter == null) { Slog.w(TAG, "BluetoothAdapter is NOT available."); + return; } + mBluetoothDeviceProcessor.init(btAdapter); + mBleDeviceProcessor.init(context, btAdapter); + mAssociationStore.registerLocalListener(this); } @@ -280,7 +282,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * For legacy device presence below Android V. * * @deprecated Use {@link #startObservingDevicePresence(ObservingDevicePresenceRequest, String, - * int)} + * int)} */ @Deprecated public void startObservingDevicePresence(int userId, String packageName, String deviceAddress) @@ -310,7 +312,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene * For legacy device presence below Android V. * * @deprecated Use {@link #stopObservingDevicePresence(ObservingDevicePresenceRequest, String, - * int)} + * int)} */ @Deprecated public void stopObservingDevicePresence(int userId, String packageName, String deviceAddress) @@ -496,7 +498,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene // Stop the BLE scan if all devices report BT connected status and BLE was present. if (canStopBleScan()) { - mBleScanner.stopScanIfNeeded(); + mBleDeviceProcessor.stopScanIfNeeded(); } } @@ -513,7 +515,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene } // Start BLE scanning when the device is disconnected. - mBleScanner.startScan(); + mBleDeviceProcessor.startScan(); onDevicePresenceEvent(mConnectedBtDevices, associationId, EVENT_BT_DISCONNECTED); // If current device is BLE present but BT is disconnected , means it will be @@ -724,7 +726,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene final ParcelUuid parcelUuid = uuid.getUuid(); final int userId = uuid.getUserId(); if (!mUserManager.isUserUnlockingOrUnlocked(userId)) { - onDeviceLocked(/* associationId */ -1, userId, eventType, parcelUuid); + onDeviceLocked(NO_ASSOCIATION, userId, eventType, parcelUuid); return; } @@ -930,10 +932,6 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene @Override public void onAssociationRemoved(@NonNull AssociationInfo association) { final int id = association.getId(); - if (DEBUG) { - Log.i(TAG, "onAssociationRemoved() id=" + id); - Log.d(TAG, " > association=" + association); - } mConnectedBtDevices.remove(id); mNearbyBleDevices.remove(id); @@ -1004,8 +1002,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene if (deviceEvents != null) { deviceEvents.removeIf(deviceEvent -> deviceEvent.getEvent() == EVENT_BLE_APPEARED - && Objects.equals(deviceEvent.getUuid(), uuid) - && deviceEvent.getAssociationId() == associationId); + && Objects.equals(deviceEvent.getUuid(), uuid) + && deviceEvent.getAssociationId() == associationId); } } } @@ -1018,8 +1016,8 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene if (deviceEvents != null) { deviceEvents.removeIf(deviceEvent -> deviceEvent.getEvent() == EVENT_BT_CONNECTED - && Objects.equals(deviceEvent.getUuid(), uuid) - && deviceEvent.getAssociationId() == associationId); + && Objects.equals(deviceEvent.getUuid(), uuid) + && deviceEvent.getAssociationId() == associationId); } } } @@ -1054,7 +1052,7 @@ public class DevicePresenceProcessor implements AssociationStore.OnChangeListene return; } - switch(event) { + switch (event) { case EVENT_BLE_APPEARED: onBleCompanionDeviceFound( associationInfo.getId(), associationInfo.getUserId()); diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuid.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java index 9cfa2705cb2f..c9f60ca639d4 100644 --- a/services/companion/java/com/android/server/companion/presence/ObservableUuid.java +++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuid.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion.presence; +package com.android.server.companion.devicepresence; import android.annotation.NonNull; import android.annotation.UserIdInt; diff --git a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java index fa0f6bd92acb..4678a165b83f 100644 --- a/services/companion/java/com/android/server/companion/presence/ObservableUuidStore.java +++ b/services/companion/java/com/android/server/companion/devicepresence/ObservableUuidStore.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.server.companion.presence; +package com.android.server.companion.devicepresence; import static com.android.internal.util.XmlUtils.readIntAttribute; import static com.android.internal.util.XmlUtils.readLongAttribute; diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java index da90bdbb9041..93243fca9dd2 100644 --- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java +++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java @@ -1108,10 +1108,10 @@ final class VirtualDeviceImpl extends IVirtualDevice.Stub mParams.dump(fout, indent + indent); fout.println(indent + "mVirtualDisplayIds: "); synchronized (mVirtualDeviceLock) { - fout.println(" mDevicePolicies: " + mDevicePolicies); for (int i = 0; i < mVirtualDisplays.size(); i++) { fout.println(indent + " " + mVirtualDisplays.keyAt(i)); } + fout.println(" mDevicePolicies: " + mDevicePolicies); fout.println(indent + "mDefaultShowPointerIcon: " + mDefaultShowPointerIcon); } mInputController.dump(fout); diff --git a/services/core/Android.bp b/services/core/Android.bp index 7f5867fb1a74..c7d99424f72b 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -246,7 +246,7 @@ java_library_static { "biometrics_flags_lib", "am_flags_lib", "com_android_server_accessibility_flags_lib", - "com_android_systemui_shared_flags_lib", + "//frameworks/libs/systemui:com_android_systemui_shared_flags_lib", "com_android_wm_shell_flags_lib", "com.android.server.utils_aconfig-java", "service-jobscheduler-deviceidle.flags-aconfig-java", diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index 0a2aaeba57b4..7ea82b095533 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -5555,6 +5555,7 @@ public final class ActiveServices { boolean enqueueOomAdj, @ServiceBindingOomAdjPolicy int serviceBindingOomAdjPolicy) throws TransactionTooLargeException { if (r.app != null && r.app.isThreadReady()) { + r.updateOomAdjSeq(); sendServiceArgsLocked(r, execInFg, false); return null; } diff --git a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java index 55b161ad6348..dcda5c228ceb 100644 --- a/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java +++ b/services/core/java/com/android/server/am/ActivityManagerDebugConfig.java @@ -55,9 +55,7 @@ class ActivityManagerDebugConfig { static final boolean DEBUG_BACKGROUND_CHECK = DEBUG_ALL || false; static final boolean DEBUG_BACKUP = DEBUG_ALL || false; static final boolean DEBUG_BROADCAST = DEBUG_ALL || false; - static final boolean DEBUG_BROADCAST_BACKGROUND = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; - static final boolean DEBUG_BROADCAST_DEFERRAL = DEBUG_BROADCAST || false; static final boolean DEBUG_COMPACTION = DEBUG_ALL || false; static final boolean DEBUG_FREEZER = DEBUG_ALL || false; static final boolean DEBUG_LRU = DEBUG_ALL || false; diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index a66c23f7cf63..b20135c958cd 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -12084,18 +12084,23 @@ public class ActivityManagerService extends IActivityManager.Stub for (int i=0; i<items.size(); i++) { MemItem mi = items.get(i); if (!isCompact) { - pw.printf("%s%s: %s%s\n", prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss), + String printFormat = "%s%s: %s%s\n"; + if ((dumpPss && dumpSwapPss) || dumpPrivateDirty) { + StringBuilder format = new StringBuilder(); + format.append("%s%s: %-60s%s"); + if (dumpSwapPss) { + format.append(String.format("(%s in swap%s", stringifyKBSize(mi.swapPss), + dumpPrivateDirty ? ", " : ")")); + } + if (dumpPrivateDirty) { + format.append(String.format("%s%s private dirty)", dumpSwapPss ? "" : "(", + stringifyKBSize(mi.mPrivateDirty))); + } + printFormat = format.append("\n").toString(); + } + pw.printf(printFormat, prefix, stringifyKBSize(dumpPss ? mi.pss : mi.mRss), mi.label, mi.userId != UserHandle.USER_SYSTEM ? " (user " + mi.userId + ")" : ""); - if (dumpPss && dumpSwapPss) { - pw.printf("(%s in swap%s", stringifyKBSize(mi.swapPss), - dumpPrivateDirty ? ", " : ")"); - } - if (dumpPrivateDirty) { - pw.printf("%s%s private dirty)", dumpSwapPss ? "" : "(", - stringifyKBSize(mi.mPrivateDirty)); - } - pw.printf("\n"); } else if (mi.isProc) { pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel); pw.print(","); pw.print(mi.id); pw.print(","); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index 48a9d6af0df4..6779f7a37f20 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -351,6 +351,7 @@ public final class ProcessList { // LMK_UPDATE_PROPS // LMK_KILL_OCCURRED // LMK_START_MONITORING + // LMK_BOOT_COMPLETED static final byte LMK_TARGET = 0; static final byte LMK_PROCPRIO = 1; static final byte LMK_PROCREMOVE = 2; @@ -361,6 +362,7 @@ public final class ProcessList { static final byte LMK_UPDATE_PROPS = 7; static final byte LMK_KILL_OCCURRED = 8; // Msg to subscribed clients on kill occurred event static final byte LMK_START_MONITORING = 9; // Start monitoring if delayed earlier + static final byte LMK_BOOT_COMPLETED = 10; // Low Memory Killer Daemon command codes. // These must be kept in sync with async_event_type definitions in lmkd.h diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java index 951f676dd098..656611a79595 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java +++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java @@ -1820,18 +1820,17 @@ public class AudioDeviceBroker { + "received with null profile proxy: " + btInfo)).printLog(TAG)); } else { - @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec = + final Pair<Integer, Boolean> codecAndChanged = mBtHelper.getCodecWithFallback(btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput, "MSG_L_SET_BT_ACTIVE_DEVICE"); synchronized (mSetModeLock) { synchronized (mDeviceStateLock) { - mDeviceInventory.onSetBtActiveDevice(btInfo, codec, - (btInfo.mProfile - != BluetoothProfile.LE_AUDIO + mDeviceInventory.onSetBtActiveDevice(btInfo, codecAndChanged.first, + (btInfo.mProfile != BluetoothProfile.LE_AUDIO || btInfo.mIsLeOutput) - ? mAudioService.getBluetoothContextualVolumeStream() - : AudioSystem.STREAM_DEFAULT); + ? mAudioService.getBluetoothContextualVolumeStream() + : AudioSystem.STREAM_DEFAULT); if (btInfo.mProfile == BluetoothProfile.LE_AUDIO || btInfo.mProfile == BluetoothProfile.HEARING_AID) { @@ -1866,13 +1865,13 @@ public class AudioDeviceBroker { break; case MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE: { final BtDeviceInfo btInfo = (BtDeviceInfo) msg.obj; - @AudioSystem.AudioFormatNativeEnumForBtCodec final int codec = - mBtHelper.getCodecWithFallback(btInfo.mDevice, - btInfo.mProfile, btInfo.mIsLeOutput, - "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE"); + final Pair<Integer, Boolean> codecAndChanged = mBtHelper.getCodecWithFallback( + btInfo.mDevice, btInfo.mProfile, btInfo.mIsLeOutput, + "MSG_L_BLUETOOTH_DEVICE_CONFIG_CHANGE"); synchronized (mDeviceStateLock) { - mDeviceInventory.onBluetoothDeviceConfigChange( - btInfo, codec, BtHelper.EVENT_DEVICE_CONFIG_CHANGE); + mDeviceInventory.onBluetoothDeviceConfigChange(btInfo, + codecAndChanged.first, codecAndChanged.second, + BtHelper.EVENT_DEVICE_CONFIG_CHANGE); } } break; case MSG_BROADCAST_AUDIO_BECOMING_NOISY: diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java index 14428c41ef26..ebe9c636d6f6 100644 --- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java +++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java @@ -868,7 +868,8 @@ public class AudioDeviceInventory { @GuardedBy("mDeviceBroker.mDeviceStateLock") /*package*/ void onBluetoothDeviceConfigChange( @NonNull AudioDeviceBroker.BtDeviceInfo btInfo, - @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, int event) { + @AudioSystem.AudioFormatNativeEnumForBtCodec int codec, + boolean codecChanged, int event) { MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId + "onBluetoothDeviceConfigChange") .set(MediaMetrics.Property.EVENT, BtHelper.deviceEventToString(event)); @@ -916,14 +917,12 @@ public class AudioDeviceInventory { if (event == BtHelper.EVENT_DEVICE_CONFIG_CHANGE) { - boolean codecChange = false; if (btInfo.mProfile == BluetoothProfile.A2DP || btInfo.mProfile == BluetoothProfile.LE_AUDIO || btInfo.mProfile == BluetoothProfile.LE_AUDIO_BROADCAST) { - if (di.mDeviceCodecFormat != codec) { + if (codecChanged) { di.mDeviceCodecFormat = codec; mConnectedDevices.replace(key, di); - codecChange = true; final int res = mAudioSystem.handleDeviceConfigChange( btInfo.mAudioSystemDevice, address, BtHelper.getName(btDevice), codec); @@ -947,7 +946,7 @@ public class AudioDeviceInventory { } } } - if (!codecChange) { + if (!codecChanged) { updateBluetoothPreferredModes_l(btDevice /*connectedDevice*/); } } diff --git a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java index 85acf707677a..570d4e9857e6 100644 --- a/services/core/java/com/android/server/audio/AudioManagerShellCommand.java +++ b/services/core/java/com/android/server/audio/AudioManagerShellCommand.java @@ -53,6 +53,12 @@ class AudioManagerShellCommand extends ShellCommand { return getSoundDoseValue(); case "reset-sound-dose-timeout": return resetSoundDoseTimeout(); + case "set-volume": + return setVolume(); + case "adj-mute": + return adjMute(); + case "adj-unmute": + return adjUnmute(); } return 0; } @@ -78,6 +84,12 @@ class AudioManagerShellCommand extends ShellCommand { pw.println(" Returns the current sound dose value"); pw.println(" reset-sound-dose-timeout"); pw.println(" Resets the sound dose timeout used for momentary exposure"); + pw.println(" set-volume STREAM_TYPE VOLUME_INDEX"); + pw.println(" Sets the volume for STREAM_TYPE to VOLUME_INDEX"); + pw.println(" adj-mute STREAM_TYPE"); + pw.println(" mutes the STREAM_TYPE"); + pw.println(" adj-unmute STREAM_TYPE"); + pw.println(" unmutes the STREAM_TYPE"); } private int setSurroundFormatEnabled() { @@ -216,4 +228,54 @@ class AudioManagerShellCommand extends ShellCommand { getOutPrintWriter().println("Reset sound dose momentary exposure timeout"); return 0; } + + private int setVolume() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + final int index = readIntArg(); + getOutPrintWriter().println("calling AudioManager.setStreamVolume(" + + stream + ", " + index + ", 0)"); + am.setStreamVolume(stream, index, 0); + return 0; + } + + private int adjMute() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + getOutPrintWriter().println("calling AudioManager.adjustStreamVolume(" + + stream + ", AudioManager.ADJUST_MUTE, 0)"); + am.adjustStreamVolume(stream, AudioManager.ADJUST_MUTE, 0); + return 0; + } + + private int adjUnmute() { + final Context context = mService.mContext; + final AudioManager am = context.getSystemService(AudioManager.class); + final int stream = readIntArg(); + getOutPrintWriter().println("calling AudioManager.adjustStreamVolume(" + + stream + ", AudioManager.ADJUST_UNMUTE, 0)"); + am.adjustStreamVolume(stream, AudioManager.ADJUST_UNMUTE, 0); + return 0; + } + + private int readIntArg() throws IllegalArgumentException { + String argText = getNextArg(); + + if (argText == null) { + getErrPrintWriter().println("Error: no argument provided"); + throw new IllegalArgumentException("No argument provided"); + } + + int argIntVal = Integer.MIN_VALUE; + try { + argIntVal = Integer.parseInt(argText); + } catch (NumberFormatException e) { + getErrPrintWriter().println("Error: wrong format for argument " + argText); + throw new IllegalArgumentException("Wrong format for argument " + argText); + } + + return argIntVal; + } } diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java index ed58c4033c4c..40099581c68b 100644 --- a/services/core/java/com/android/server/audio/AudioService.java +++ b/services/core/java/com/android/server/audio/AudioService.java @@ -47,6 +47,7 @@ import static com.android.media.audio.Flags.alarmMinVolumeZero; import static com.android.media.audio.Flags.disablePrescaleAbsoluteVolume; import static com.android.media.audio.Flags.ringerModeAffectsAlarm; import static com.android.media.audio.Flags.setStreamVolumeOrder; +import static com.android.media.audio.Flags.vgsVssSyncMuteOrder; import static com.android.server.audio.SoundDoseHelper.ACTION_CHECK_MUSIC_ACTIVE; import static com.android.server.utils.EventLogger.Event.ALOGE; import static com.android.server.utils.EventLogger.Event.ALOGI; @@ -4544,6 +4545,8 @@ public class AudioService extends IAudioService.Stub + setStreamVolumeOrder()); pw.println("\tandroid.media.audio.roForegroundAudioControl:" + roForegroundAudioControl()); + pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder:" + + vgsVssSyncMuteOrder()); } private void dumpAudioMode(PrintWriter pw) { @@ -8317,13 +8320,23 @@ public class AudioService extends IAudioService.Stub synced = true; continue; } + if (vgsVssSyncMuteOrder()) { + if ((isMuted() != streamMuted) && isVssMuteBijective( + stream)) { + mStreamStates[stream].mute(isMuted(), + "VGS.applyAllVolumes#1"); + } + } if (indexForStream != index) { mStreamStates[stream].setIndex(index * 10, device, caller, true /*hasModifyAudioSettings*/); } - if ((isMuted() != streamMuted) && isVssMuteBijective(stream)) { - mStreamStates[stream].mute(isMuted(), - "VGS.applyAllVolumes#1"); + if (!vgsVssSyncMuteOrder()) { + if ((isMuted() != streamMuted) && isVssMuteBijective( + stream)) { + mStreamStates[stream].mute(isMuted(), + "VGS.applyAllVolumes#1"); + } } } } diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java index f3a5fdb3cacc..edeabdc5243c 100644 --- a/services/core/java/com/android/server/audio/BtHelper.java +++ b/services/core/java/com/android/server/audio/BtHelper.java @@ -98,9 +98,16 @@ public class BtHelper { private @Nullable BluetoothLeAudio mLeAudio; + private @Nullable BluetoothLeAudioCodecConfig mLeAudioCodecConfig; + // Reference to BluetoothA2dp to query for AbsoluteVolume. private @Nullable BluetoothA2dp mA2dp; + private @Nullable BluetoothCodecConfig mA2dpCodecConfig; + + private @AudioSystem.AudioFormatNativeEnumForBtCodec + int mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT; + // If absolute volume is supported in AVRCP device private boolean mAvrcpAbsVolSupported = false; @@ -265,12 +272,15 @@ public class BtHelper { } } - /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec int getCodec( + private synchronized Pair<Integer, Boolean> getCodec( @NonNull BluetoothDevice device, @AudioService.BtProfile int profile) { + switch (profile) { case BluetoothProfile.A2DP: { + boolean changed = mA2dpCodecConfig != null; if (mA2dp == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; + mA2dpCodecConfig = null; + return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); } BluetoothCodecStatus btCodecStatus = null; try { @@ -279,17 +289,24 @@ public class BtHelper { Log.e(TAG, "Exception while getting status of " + device, e); } if (btCodecStatus == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; + mA2dpCodecConfig = null; + return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); } final BluetoothCodecConfig btCodecConfig = btCodecStatus.getCodecConfig(); if (btCodecConfig == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; + mA2dpCodecConfig = null; + return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); } - return AudioSystem.bluetoothA2dpCodecToAudioFormat(btCodecConfig.getCodecType()); + changed = !btCodecConfig.equals(mA2dpCodecConfig); + mA2dpCodecConfig = btCodecConfig; + return new Pair<>(AudioSystem.bluetoothA2dpCodecToAudioFormat( + btCodecConfig.getCodecType()), changed); } case BluetoothProfile.LE_AUDIO: { + boolean changed = mLeAudioCodecConfig != null; if (mLeAudio == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; + mLeAudioCodecConfig = null; + return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); } BluetoothLeAudioCodecStatus btLeCodecStatus = null; int groupId = mLeAudio.getGroupId(device); @@ -299,42 +316,54 @@ public class BtHelper { Log.e(TAG, "Exception while getting status of " + device, e); } if (btLeCodecStatus == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; + mLeAudioCodecConfig = null; + return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); } BluetoothLeAudioCodecConfig btLeCodecConfig = btLeCodecStatus.getOutputCodecConfig(); if (btLeCodecConfig == null) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; + mLeAudioCodecConfig = null; + return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, changed); } - return AudioSystem.bluetoothLeCodecToAudioFormat(btLeCodecConfig.getCodecType()); + changed = !btLeCodecConfig.equals(mLeAudioCodecConfig); + mLeAudioCodecConfig = btLeCodecConfig; + return new Pair<>(AudioSystem.bluetoothLeCodecToAudioFormat( + btLeCodecConfig.getCodecType()), changed); + } + case BluetoothProfile.LE_AUDIO_BROADCAST: { + // We assume LC3 for LE Audio broadcast codec as there is no API to get the codec + // config on LE Broadcast profile proxy. + boolean changed = mLeAudioBroadcastCodec != AudioSystem.AUDIO_FORMAT_LC3; + mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_LC3; + return new Pair<>(mLeAudioBroadcastCodec, changed); } default: - return AudioSystem.AUDIO_FORMAT_DEFAULT; + return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false); } } - /*package*/ synchronized @AudioSystem.AudioFormatNativeEnumForBtCodec - int getCodecWithFallback( - @NonNull BluetoothDevice device, @AudioService.BtProfile int profile, - boolean isLeOutput, @NonNull String source) { + /*package*/ synchronized Pair<Integer, Boolean> + getCodecWithFallback(@NonNull BluetoothDevice device, + @AudioService.BtProfile int profile, + boolean isLeOutput, @NonNull String source) { // For profiles other than A2DP and LE Audio output, the audio codec format must be // AUDIO_FORMAT_DEFAULT as native audio policy manager expects a specific audio format // only if audio HW module selection based on format is supported for the device type. if (!(profile == BluetoothProfile.A2DP || (isLeOutput && ((profile == BluetoothProfile.LE_AUDIO) || (profile == BluetoothProfile.LE_AUDIO_BROADCAST))))) { - return AudioSystem.AUDIO_FORMAT_DEFAULT; + return new Pair<>(AudioSystem.AUDIO_FORMAT_DEFAULT, false); } - @AudioSystem.AudioFormatNativeEnumForBtCodec int codec = + Pair<Integer, Boolean> codecAndChanged = getCodec(device, profile); - if (codec == AudioSystem.AUDIO_FORMAT_DEFAULT) { + if (codecAndChanged.first == AudioSystem.AUDIO_FORMAT_DEFAULT) { AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent( "getCodec DEFAULT from " + source + " fallback to " + (profile == BluetoothProfile.A2DP ? "SBC" : "LC3"))); - return profile == BluetoothProfile.A2DP - ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3; + return new Pair<>(profile == BluetoothProfile.A2DP + ? AudioSystem.AUDIO_FORMAT_SBC : AudioSystem.AUDIO_FORMAT_LC3, true); } - return codec; + return codecAndChanged; } // @GuardedBy("mDeviceBroker.mSetModeLock") @@ -539,15 +568,19 @@ public class BtHelper { break; case BluetoothProfile.A2DP: mA2dp = null; + mA2dpCodecConfig = null; break; case BluetoothProfile.HEARING_AID: mHearingAid = null; break; case BluetoothProfile.LE_AUDIO: mLeAudio = null; + mLeAudioCodecConfig = null; break; - case BluetoothProfile.A2DP_SINK: case BluetoothProfile.LE_AUDIO_BROADCAST: + mLeAudioBroadcastCodec = AudioSystem.AUDIO_FORMAT_DEFAULT; + break; + case BluetoothProfile.A2DP_SINK: // nothing to do in BtHelper break; default: diff --git a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java index 3ede0a2597d9..028b9b0bcbc0 100644 --- a/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/BroadcastRadioService.java @@ -26,7 +26,7 @@ import com.android.server.SystemService; import java.util.ArrayList; -public class BroadcastRadioService extends SystemService { +public final class BroadcastRadioService extends SystemService { private final IRadioService mServiceImpl; public BroadcastRadioService(Context context) { diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java index 42b2682cf530..16514fa813dc 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceAidlImpl.java @@ -52,7 +52,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { private static final List<String> SERVICE_NAMES = Arrays.asList( IBroadcastRadio.DESCRIPTOR + "/amfm", IBroadcastRadio.DESCRIPTOR + "/dab"); - private final BroadcastRadioServiceImpl mHalAidl; + private final BroadcastRadioServiceImpl mAidlHalClient; private final BroadcastRadioService mService; /** @@ -77,14 +77,14 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { @VisibleForTesting IRadioServiceAidlImpl(BroadcastRadioService service, BroadcastRadioServiceImpl halAidl) { mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null"); - mHalAidl = Objects.requireNonNull(halAidl, + mAidlHalClient = Objects.requireNonNull(halAidl, "Broadcast radio service implementation for AIDL HAL cannot be null"); } @Override public List<RadioManager.ModuleProperties> listModules() { mService.enforcePolicyAccess(); - return mHalAidl.listModules(); + return mAidlHalClient.listModules(); } @Override @@ -97,7 +97,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { if (callback == null) { throw new IllegalArgumentException("Callback must not be null"); } - return mHalAidl.openSession(moduleId, bandConfig, withAudio, callback); + return mAidlHalClient.openSession(moduleId, bandConfig, withAudio, callback); } @Override @@ -110,7 +110,7 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { Objects.requireNonNull(listener, "Announcement listener cannot be null"); mService.enforcePolicyAccess(); - return mHalAidl.addAnnouncementListener(enabledTypes, listener); + return mAidlHalClient.addAnnouncementListener(enabledTypes, listener); } @Override @@ -126,10 +126,10 @@ final class IRadioServiceAidlImpl extends IRadioService.Stub { radioPrintWriter.printf("BroadcastRadioService\n"); radioPrintWriter.increaseIndent(); - radioPrintWriter.printf("AIDL HAL:\n"); + radioPrintWriter.printf("AIDL HAL client:\n"); radioPrintWriter.increaseIndent(); - mHalAidl.dumpInfo(radioPrintWriter); + mAidlHalClient.dumpInfo(radioPrintWriter); radioPrintWriter.decreaseIndent(); radioPrintWriter.decreaseIndent(); diff --git a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java index bc72a4be18be..ab083429a200 100644 --- a/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java +++ b/services/core/java/com/android/server/broadcastradio/IRadioServiceHidlImpl.java @@ -49,8 +49,8 @@ import java.util.OptionalInt; final class IRadioServiceHidlImpl extends IRadioService.Stub { private static final String TAG = "BcRadioSrvHidl"; - private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1; - private final com.android.server.broadcastradio.hal2.BroadcastRadioService mHal2; + private final com.android.server.broadcastradio.hal1.BroadcastRadioService mHal1Client; + private final com.android.server.broadcastradio.hal2.BroadcastRadioService mHal2Client; private final Object mLock = new Object(); @@ -61,10 +61,10 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { IRadioServiceHidlImpl(BroadcastRadioService service) { mService = Objects.requireNonNull(service, "broadcast radio service cannot be null"); - mHal1 = new com.android.server.broadcastradio.hal1.BroadcastRadioService(); - mV1Modules = mHal1.loadModules(); + mHal1Client = new com.android.server.broadcastradio.hal1.BroadcastRadioService(); + mV1Modules = mHal1Client.loadModules(); OptionalInt max = mV1Modules.stream().mapToInt(RadioManager.ModuleProperties::getId).max(); - mHal2 = new com.android.server.broadcastradio.hal2.BroadcastRadioService( + mHal2Client = new com.android.server.broadcastradio.hal2.BroadcastRadioService( max.isPresent() ? max.getAsInt() + 1 : 0); } @@ -73,17 +73,17 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { com.android.server.broadcastradio.hal1.BroadcastRadioService hal1, com.android.server.broadcastradio.hal2.BroadcastRadioService hal2) { mService = Objects.requireNonNull(service, "Broadcast radio service cannot be null"); - mHal1 = Objects.requireNonNull(hal1, + mHal1Client = Objects.requireNonNull(hal1, "Broadcast radio service implementation for HIDL 1 HAL cannot be null"); - mV1Modules = mHal1.loadModules(); - mHal2 = Objects.requireNonNull(hal2, + mV1Modules = mHal1Client.loadModules(); + mHal2Client = Objects.requireNonNull(hal2, "Broadcast radio service implementation for HIDL 2 HAL cannot be null"); } @Override public List<RadioManager.ModuleProperties> listModules() { mService.enforcePolicyAccess(); - Collection<RadioManager.ModuleProperties> v2Modules = mHal2.listModules(); + Collection<RadioManager.ModuleProperties> v2Modules = mHal2Client.listModules(); List<RadioManager.ModuleProperties> modules; synchronized (mLock) { modules = new ArrayList<>(mV1Modules.size() + v2Modules.size()); @@ -102,10 +102,10 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { mService.enforcePolicyAccess(); Objects.requireNonNull(callback, "Callback must not be null"); synchronized (mLock) { - if (mHal2.hasModule(moduleId)) { - return mHal2.openSession(moduleId, bandConfig, withAudio, callback); + if (mHal2Client.hasModule(moduleId)) { + return mHal2Client.openSession(moduleId, bandConfig, withAudio, callback); } else { - return mHal1.openTuner(moduleId, bandConfig, withAudio, callback); + return mHal1Client.openTuner(moduleId, bandConfig, withAudio, callback); } } } @@ -121,12 +121,12 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { mService.enforcePolicyAccess(); synchronized (mLock) { - if (!mHal2.hasAnyModules()) { + if (!mHal2Client.hasAnyModules()) { Slog.w(TAG, "There are no HAL 2.0 modules registered"); return new AnnouncementAggregator(listener, mLock); } - return mHal2.addAnnouncementListener(enabledTypes, listener); + return mHal2Client.addAnnouncementListener(enabledTypes, listener); } } @@ -143,18 +143,18 @@ final class IRadioServiceHidlImpl extends IRadioService.Stub { radioPw.printf("BroadcastRadioService\n"); radioPw.increaseIndent(); - radioPw.printf("HAL1: %s\n", mHal1); + radioPw.printf("HAL1 client: %s\n", mHal1Client); radioPw.increaseIndent(); synchronized (mLock) { - radioPw.printf("Modules of HAL1: %s\n", mV1Modules); + radioPw.printf("Modules of HAL1 client: %s\n", mV1Modules); } radioPw.decreaseIndent(); - radioPw.printf("HAL2:\n"); + radioPw.printf("HAL2 client:\n"); radioPw.increaseIndent(); - mHal2.dumpInfo(radioPw); + mHal2Client.dumpInfo(radioPw); radioPw.decreaseIndent(); radioPw.decreaseIndent(); diff --git a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java index 4b847a27c4de..c705ebe686f2 100644 --- a/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java +++ b/services/core/java/com/android/server/broadcastradio/RadioServiceUserController.java @@ -48,8 +48,8 @@ public final class RadioServiceUserController { * @return foreground user id. */ public static int getCurrentUser() { - final long identity = Binder.clearCallingIdentity(); int userId = UserHandle.USER_NULL; + final long identity = Binder.clearCallingIdentity(); try { userId = ActivityManager.getCurrentUser(); } catch (RuntimeException e) { diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Convert.java b/services/core/java/com/android/server/broadcastradio/hal1/Convert.java index 219ee4c3229a..08ff6627785a 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/Convert.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/Convert.java @@ -18,12 +18,13 @@ package com.android.server.broadcastradio.hal1; import android.annotation.NonNull; import android.annotation.Nullable; -import android.util.Slog; + +import com.android.server.utils.Slogf; import java.util.Map; import java.util.Set; -class Convert { +final class Convert { private static final String TAG = "BcRadio1Srv.Convert"; @@ -34,12 +35,12 @@ class Convert { * side, which requires several separate java calls for each element. * * @param map map to convert. - * @returns array (sized the same as map) of two-element string arrays - * (first element is the key, second is value). + * @return array (sized the same as map) of two-element string arrays + * (first element is the key, second is value). */ static @NonNull String[][] stringMapToNative(@Nullable Map<String, String> map) { if (map == null) { - Slog.v(TAG, "map is null, returning zero-elements array"); + Slogf.v(TAG, "map is null, returning zero-elements array"); return new String[0][0]; } @@ -54,7 +55,7 @@ class Convert { i++; } - Slog.v(TAG, "converted " + i + " element(s)"); + Slogf.v(TAG, "converted " + i + " element(s)"); return arr; } } diff --git a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java index 8e5f6b5b8624..7cac4091c583 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/Tuner.java @@ -26,7 +26,6 @@ import android.hardware.radio.ProgramSelector; import android.hardware.radio.RadioManager; import android.os.IBinder; import android.os.RemoteException; -import android.util.Slog; import com.android.server.broadcastradio.RadioServiceUserController; import com.android.server.utils.Slogf; @@ -44,9 +43,9 @@ class Tuner extends ITuner.Stub { private final long mNativeContext; private final Object mLock = new Object(); - @NonNull private final TunerCallback mTunerCallback; - @NonNull private final ITunerCallback mClientCallback; - @NonNull private final IBinder.DeathRecipient mDeathRecipient; + private final TunerCallback mTunerCallback; + private final ITunerCallback mClientCallback; + private final IBinder.DeathRecipient mDeathRecipient; private boolean mIsClosed = false; private boolean mIsMuted = false; @@ -122,7 +121,7 @@ class Tuner extends ITuner.Stub { private boolean checkConfiguredLocked() { if (mTunerCallback.isInitialConfigurationDone()) return true; - Slog.w(TAG, "Initial configuration is still pending, skipping the operation"); + Slogf.w(TAG, "Initial configuration is still pending, skipping the operation"); return false; } @@ -159,14 +158,14 @@ class Tuner extends ITuner.Stub { checkNotClosedLocked(); if (mIsMuted == mute) return; mIsMuted = mute; - Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app"); + Slogf.w(TAG, "Mute via RadioService is not implemented - please handle it via app"); } } @Override public boolean isMuted() { if (!mWithAudio) { - Slog.w(TAG, "Tuner did not request audio, pretending it was muted"); + Slogf.w(TAG, "Tuner did not request audio, pretending it was muted"); return true; } synchronized (mLock) { @@ -210,7 +209,7 @@ class Tuner extends ITuner.Stub { if (selector == null) { throw new IllegalArgumentException("The argument must not be a null pointer"); } - Slog.i(TAG, "Tuning to " + selector); + Slogf.i(TAG, "Tuning to " + selector); synchronized (mLock) { checkNotClosedLocked(); if (!checkConfiguredLocked()) return; diff --git a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java index aa43b7581fe7..e013643a812d 100644 --- a/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java +++ b/services/core/java/com/android/server/broadcastradio/hal1/TunerCallback.java @@ -25,7 +25,8 @@ import android.hardware.radio.RadioManager; import android.hardware.radio.RadioTuner; import android.os.IBinder; import android.os.RemoteException; -import android.util.Slog; + +import com.android.server.utils.Slogf; import java.util.List; import java.util.Map; @@ -42,8 +43,8 @@ class TunerCallback implements ITunerCallback { */ private final long mNativeContext; - @NonNull private final Tuner mTuner; - @NonNull private final ITunerCallback mClientCallback; + private final Tuner mTuner; + private final ITunerCallback mClientCallback; private final AtomicReference<ProgramList.Filter> mProgramListFilter = new AtomicReference<>(); private boolean mInitialConfigurationDone = false; @@ -76,7 +77,7 @@ class TunerCallback implements ITunerCallback { try { func.run(); } catch (RemoteException e) { - Slog.e(TAG, "client died", e); + Slogf.e(TAG, "client died", e); } } @@ -107,7 +108,7 @@ class TunerCallback implements ITunerCallback { @Override public void onTuneFailed(int result, ProgramSelector selector) { - Slog.e(TAG, "Not applicable for HAL 1.x"); + Slogf.e(TAG, "Not applicable for HAL 1.x"); } @Override @@ -160,7 +161,7 @@ class TunerCallback implements ITunerCallback { try { modified = mTuner.getProgramList(filter.getVendorFilter()); } catch (IllegalStateException ex) { - Slog.d(TAG, "Program list not ready yet"); + Slogf.d(TAG, "Program list not ready yet"); return; } Set<RadioManager.ProgramInfo> modifiedSet = modified.stream().collect(Collectors.toSet()); @@ -175,12 +176,12 @@ class TunerCallback implements ITunerCallback { @Override public void onConfigFlagUpdated(int flag, boolean value) { - Slog.w(TAG, "Not applicable for HAL 1.x"); + Slogf.w(TAG, "Not applicable for HAL 1.x"); } @Override public void onParametersUpdated(Map<String, String> parameters) { - Slog.w(TAG, "Not applicable for HAL 1.x"); + Slogf.w(TAG, "Not applicable for HAL 1.x"); } @Override diff --git a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java index 85c13aee37c9..0327ee79d39c 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/AnnouncementAggregator.java @@ -23,20 +23,20 @@ import android.hardware.radio.IAnnouncementListener; import android.hardware.radio.ICloseHandle; import android.os.IBinder; import android.os.RemoteException; -import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.server.utils.Slogf; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Objects; -public class AnnouncementAggregator extends ICloseHandle.Stub { +public final class AnnouncementAggregator extends ICloseHandle.Stub { private static final String TAG = "BcRadio2Srv.AnnAggr"; private final Object mLock; - @NonNull private final IAnnouncementListener mListener; + private final IAnnouncementListener mListener; private final IBinder.DeathRecipient mDeathRecipient = new DeathRecipient(); @GuardedBy("mLock") @@ -77,14 +77,16 @@ public class AnnouncementAggregator extends ICloseHandle.Stub { public void binderDied() { try { close(); - } catch (RemoteException ex) {} + } catch (RemoteException ex) { + Slogf.e(TAG, ex, "Cannot close Announcement aggregator for DeathRecipient"); + } } } private void onListUpdated() { synchronized (mLock) { if (mIsClosed) { - Slog.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks"); + Slogf.e(TAG, "Announcement aggregator is closed, it shouldn't receive callbacks"); return; } List<Announcement> combined = new ArrayList<>(); @@ -94,7 +96,7 @@ public class AnnouncementAggregator extends ICloseHandle.Stub { try { mListener.onListUpdated(combined); } catch (RemoteException ex) { - Slog.e(TAG, "mListener.onListUpdated() failed: ", ex); + Slogf.e(TAG, "mListener.onListUpdated() failed: ", ex); } } } @@ -111,7 +113,7 @@ public class AnnouncementAggregator extends ICloseHandle.Stub { try { closeHandle = module.addAnnouncementListener(enabledTypes, watcher); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to add announcement listener", ex); + Slogf.e(TAG, "Failed to add announcement listener", ex); return; } watcher.setCloseHandle(closeHandle); diff --git a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java index 1e31f200fd47..3198842c1ff3 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/BroadcastRadioService.java @@ -29,8 +29,8 @@ import android.hidl.manager.V1_0.IServiceManager; import android.hidl.manager.V1_0.IServiceNotification; import android.os.IHwBinder.DeathRecipient; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.IndentingPrintWriter; -import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; @@ -38,7 +38,6 @@ import com.android.server.broadcastradio.RadioServiceUserController; import com.android.server.utils.Slogf; import java.util.Collection; -import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -55,16 +54,17 @@ public final class BroadcastRadioService { private int mNextModuleId; @GuardedBy("mLock") - private final Map<String, Integer> mServiceNameToModuleIdMap = new HashMap<>(); + private final Map<String, Integer> mServiceNameToModuleIdMap = new ArrayMap<>(); // Map from module ID to RadioModule created by mServiceListener.onRegistration(). @GuardedBy("mLock") - private final Map<Integer, RadioModule> mModules = new HashMap<>(); + private final Map<Integer, RadioModule> mModules = new ArrayMap<>(); - private IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() { + private final IServiceNotification.Stub mServiceListener = new IServiceNotification.Stub() { @Override public void onRegistration(String fqName, String serviceName, boolean preexisting) { - Slog.v(TAG, "onRegistration(" + fqName + ", " + serviceName + ", " + preexisting + ")"); + Slogf.v(TAG, "onRegistration(" + fqName + ", " + serviceName + ", " + preexisting + + ")"); Integer moduleId; synchronized (mLock) { // If the service has been registered before, reuse its previous module ID. @@ -75,13 +75,13 @@ public final class BroadcastRadioService { moduleId = mNextModuleId; } - RadioModule module = RadioModule.tryLoadingModule(moduleId, serviceName); - if (module == null) { + RadioModule radioModule = RadioModule.tryLoadingModule(moduleId, serviceName); + if (radioModule == null) { return; } - Slog.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName + Slogf.v(TAG, "loaded broadcast radio module " + moduleId + ": " + serviceName + " (HAL 2.0)"); - RadioModule prevModule = mModules.put(moduleId, module); + RadioModule prevModule = mModules.put(moduleId, radioModule); if (prevModule != null) { prevModule.closeSessions(RadioTuner.ERROR_HARDWARE_FAILURE); } @@ -92,7 +92,7 @@ public final class BroadcastRadioService { } try { - module.getService().linkToDeath(mDeathRecipient, moduleId); + radioModule.getService().linkToDeath(mDeathRecipient, moduleId); } catch (RemoteException ex) { // Service has already died, so remove its entry from mModules. mModules.remove(moduleId); @@ -101,10 +101,10 @@ public final class BroadcastRadioService { } }; - private DeathRecipient mDeathRecipient = new DeathRecipient() { + private final DeathRecipient mDeathRecipient = new DeathRecipient() { @Override public void serviceDied(long cookie) { - Slog.v(TAG, "serviceDied(" + cookie + ")"); + Slogf.v(TAG, "serviceDied(" + cookie + ")"); synchronized (mLock) { int moduleId = (int) cookie; RadioModule prevModule = mModules.remove(moduleId); @@ -114,7 +114,7 @@ public final class BroadcastRadioService { for (Map.Entry<String, Integer> entry : mServiceNameToModuleIdMap.entrySet()) { if (entry.getValue() == moduleId) { - Slog.i(TAG, "service " + entry.getKey() + Slogf.i(TAG, "service " + entry.getKey() + " died; removed RadioModule with ID " + moduleId); return; } @@ -128,12 +128,12 @@ public final class BroadcastRadioService { try { IServiceManager manager = IServiceManager.getService(); if (manager == null) { - Slog.e(TAG, "failed to get HIDL Service Manager"); + Slogf.e(TAG, "failed to get HIDL Service Manager"); return; } manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener); } catch (RemoteException ex) { - Slog.e(TAG, "failed to register for service notifications: ", ex); + Slogf.e(TAG, "failed to register for service notifications: ", ex); } } @@ -144,12 +144,12 @@ public final class BroadcastRadioService { try { manager.registerForNotifications(IBroadcastRadio.kInterfaceName, "", mServiceListener); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to register for service notifications: ", ex); + Slogf.e(TAG, "Failed to register for service notifications: ", ex); } } public @NonNull Collection<RadioManager.ModuleProperties> listModules() { - Slog.v(TAG, "List HIDL 2.0 modules"); + Slogf.v(TAG, "List HIDL 2.0 modules"); synchronized (mLock) { return mModules.values().stream().map(module -> module.getProperties()) .collect(Collectors.toList()); @@ -170,7 +170,7 @@ public final class BroadcastRadioService { public ITuner openSession(int moduleId, @Nullable RadioManager.BandConfig legacyConfig, boolean withAudio, @NonNull ITunerCallback callback) throws RemoteException { - Slog.v(TAG, "Open HIDL 2.0 session with module id " + moduleId); + Slogf.v(TAG, "Open HIDL 2.0 session with module id " + moduleId); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.e(TAG, "Cannot open tuner on HAL 2.0 client for non-current user"); throw new IllegalStateException("Cannot open session for non-current user"); @@ -198,7 +198,7 @@ public final class BroadcastRadioService { public ICloseHandle addAnnouncementListener(@NonNull int[] enabledTypes, @NonNull IAnnouncementListener listener) { - Slog.v(TAG, "Add announcementListener"); + Slogf.v(TAG, "Add announcementListener"); AnnouncementAggregator aggregator = new AnnouncementAggregator(listener, mLock); boolean anySupported = false; synchronized (mLock) { @@ -207,12 +207,12 @@ public final class BroadcastRadioService { aggregator.watchModule(module, enabledTypes); anySupported = true; } catch (UnsupportedOperationException ex) { - Slog.v(TAG, "Announcements not supported for this module", ex); + Slogf.v(TAG, "Announcements not supported for this module", ex); } } } if (!anySupported) { - Slog.i(TAG, "There are no HAL modules that support announcements"); + Slogf.i(TAG, "There are no HAL modules that support announcements"); } return aggregator; } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java index fb1138f4bc24..34bfa6cb2d46 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/Convert.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/Convert.java @@ -37,21 +37,22 @@ import android.hardware.radio.RadioManager; import android.hardware.radio.RadioMetadata; import android.hardware.radio.RadioTuner; import android.os.ParcelableException; -import android.util.Slog; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.SparseArray; + +import com.android.server.utils.Slogf; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; -class Convert { +final class Convert { private static final String TAG = "BcRadio2Srv.convert"; @@ -111,7 +112,7 @@ class Convert { elem.key = entry.getKey(); elem.value = entry.getValue(); if (elem.key == null || elem.value == null) { - Slog.w(TAG, "VendorKeyValue contains null pointers"); + Slogf.w(TAG, "VendorKeyValue contains null pointers"); continue; } list.add(elem); @@ -120,20 +121,21 @@ class Convert { return list; } - static @NonNull Map<String, String> - vendorInfoFromHal(@Nullable List<VendorKeyValue> info) { - if (info == null) return Collections.emptyMap(); + static @NonNull Map<String, String> vendorInfoFromHal(@Nullable List<VendorKeyValue> info) { + Map<String, String> vendorInfoMap = new ArrayMap<>(); + if (info == null) { + return vendorInfoMap; + } - Map<String, String> map = new HashMap<>(); for (VendorKeyValue kvp : info) { if (kvp.key == null || kvp.value == null) { - Slog.w(TAG, "VendorKeyValue contains null pointers"); + Slogf.w(TAG, "VendorKeyValue contains null pointers"); continue; } - map.put(kvp.key, kvp.value); + vendorInfoMap.put(kvp.key, kvp.value); } - return map; + return vendorInfoMap; } private static @ProgramSelector.ProgramType int identifierTypeToProgramType( @@ -168,7 +170,7 @@ class Convert { private static @NonNull int[] identifierTypesToProgramTypes(@NonNull int[] idTypes) { - Set<Integer> pTypes = new HashSet<>(); + Set<Integer> pTypes = new ArraySet<>(); for (int idType : idTypes) { int pType = identifierTypeToProgramType(idType); @@ -202,7 +204,7 @@ class Convert { for (AmFmBandRange range : config.ranges) { FrequencyBand bandType = Utils.getBand(range.lowerBound); if (bandType == FrequencyBand.UNKNOWN) { - Slog.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz"); + Slogf.e(TAG, "Unknown frequency band at " + range.lowerBound + "kHz"); continue; } if (bandType == FrequencyBand.FM) { @@ -304,7 +306,7 @@ class Convert { @NonNull android.hardware.broadcastradio.V2_0.ProgramSelector sel) { if (sel.primaryId.type != 0) return false; if (sel.primaryId.value != 0) return false; - if (sel.secondaryIds.size() != 0) return false; + if (!sel.secondaryIds.isEmpty()) return false; return true; } @@ -319,7 +321,7 @@ class Convert { return new ProgramSelector( identifierTypeToProgramType(sel.primaryId.type), Objects.requireNonNull(programIdentifierFromHal(sel.primaryId)), - secondaryIds, null); + secondaryIds, /* vendorIds= */ null); } private enum MetadataType { @@ -335,40 +337,40 @@ class Convert { } } - private static final Map<Integer, MetadataDef> metadataKeys; + private static final SparseArray<MetadataDef> METADATA_KEYS; static { - metadataKeys = new HashMap<>(); - metadataKeys.put(MetadataKey.RDS_PS, new MetadataDef( + METADATA_KEYS = new SparseArray<>(); + METADATA_KEYS.put(MetadataKey.RDS_PS, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_PS)); - metadataKeys.put(MetadataKey.RDS_PTY, new MetadataDef( + METADATA_KEYS.put(MetadataKey.RDS_PTY, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_RDS_PTY)); - metadataKeys.put(MetadataKey.RBDS_PTY, new MetadataDef( + METADATA_KEYS.put(MetadataKey.RBDS_PTY, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_RBDS_PTY)); - metadataKeys.put(MetadataKey.RDS_RT, new MetadataDef( + METADATA_KEYS.put(MetadataKey.RDS_RT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_RDS_RT)); - metadataKeys.put(MetadataKey.SONG_TITLE, new MetadataDef( + METADATA_KEYS.put(MetadataKey.SONG_TITLE, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_TITLE)); - metadataKeys.put(MetadataKey.SONG_ARTIST, new MetadataDef( + METADATA_KEYS.put(MetadataKey.SONG_ARTIST, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_ARTIST)); - metadataKeys.put(MetadataKey.SONG_ALBUM, new MetadataDef( + METADATA_KEYS.put(MetadataKey.SONG_ALBUM, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_ALBUM)); - metadataKeys.put(MetadataKey.STATION_ICON, new MetadataDef( + METADATA_KEYS.put(MetadataKey.STATION_ICON, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_ICON)); - metadataKeys.put(MetadataKey.ALBUM_ART, new MetadataDef( + METADATA_KEYS.put(MetadataKey.ALBUM_ART, new MetadataDef( MetadataType.INT, RadioMetadata.METADATA_KEY_ART)); - metadataKeys.put(MetadataKey.PROGRAM_NAME, new MetadataDef( + METADATA_KEYS.put(MetadataKey.PROGRAM_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_PROGRAM_NAME)); - metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef( + METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME)); - metadataKeys.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef( + METADATA_KEYS.put(MetadataKey.DAB_ENSEMBLE_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_ENSEMBLE_NAME_SHORT)); - metadataKeys.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef( + METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME)); - metadataKeys.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef( + METADATA_KEYS.put(MetadataKey.DAB_SERVICE_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_SERVICE_NAME_SHORT)); - metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef( + METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME)); - metadataKeys.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef( + METADATA_KEYS.put(MetadataKey.DAB_COMPONENT_NAME_SHORT, new MetadataDef( MetadataType.STRING, RadioMetadata.METADATA_KEY_DAB_COMPONENT_NAME_SHORT)); } @@ -376,9 +378,9 @@ class Convert { RadioMetadata.Builder builder = new RadioMetadata.Builder(); for (Metadata entry : meta) { - MetadataDef keyDef = metadataKeys.get(entry.key); + MetadataDef keyDef = METADATA_KEYS.get(entry.key); if (keyDef == null) { - Slog.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key)); + Slogf.i(TAG, "Ignored unknown metadata entry: " + MetadataKey.toString(entry.key)); continue; } if (keyDef.type == MetadataType.STRING) { diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java index 48112c452f02..b8d12280ac05 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioEventLogger.java @@ -19,7 +19,8 @@ package com.android.server.broadcastradio.hal2; import android.util.IndentingPrintWriter; import android.util.LocalLog; import android.util.Log; -import android.util.Slog; + +import com.android.server.utils.Slogf; final class RadioEventLogger { private final String mTag; @@ -30,11 +31,12 @@ final class RadioEventLogger { mEventLogger = new LocalLog(loggerQueueSize); } + @SuppressWarnings("AnnotateFormatMethod") void logRadioEvent(String logFormat, Object... args) { String log = String.format(logFormat, args); mEventLogger.log(log); if (Log.isLoggable(mTag, Log.DEBUG)) { - Slog.d(mTag, log); + Slogf.d(mTag, log); } } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java index a54af2ef6e44..0e11df8282a7 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/RadioModule.java @@ -39,16 +39,16 @@ import android.os.Handler; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.MutableInt; -import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.server.broadcastradio.RadioServiceUserController; +import com.android.server.utils.Slogf; import java.util.ArrayList; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; @@ -59,12 +59,12 @@ final class RadioModule { private static final String TAG = "BcRadio2Srv.module"; private static final int RADIO_EVENT_LOGGER_QUEUE_SIZE = 25; - @NonNull private final IBroadcastRadio mService; - @NonNull private final RadioManager.ModuleProperties mProperties; + private final IBroadcastRadio mService; + private final RadioManager.ModuleProperties mProperties; private final Object mLock = new Object(); - @NonNull private final Handler mHandler; - @NonNull private final RadioEventLogger mEventLogger; + private final Handler mHandler; + private final RadioEventLogger mEventLogger; @GuardedBy("mLock") private ITunerSession mHalTunerSession; @@ -144,7 +144,7 @@ final class RadioModule { // Collection of active AIDL tuner sessions created through openSession(). @GuardedBy("mLock") - private final Set<TunerSession> mAidlTunerSessions = new HashSet<>(); + private final Set<TunerSession> mAidlTunerSessions = new ArraySet<>(); @VisibleForTesting RadioModule(@NonNull IBroadcastRadio service, @@ -158,10 +158,10 @@ final class RadioModule { @Nullable static RadioModule tryLoadingModule(int idx, @NonNull String fqName) { try { - Slog.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName); + Slogf.i(TAG, "Try loading module for idx " + idx + ", fqName " + fqName); IBroadcastRadio service = IBroadcastRadio.getService(fqName); if (service == null) { - Slog.w(TAG, "No service found for fqName " + fqName); + Slogf.w(TAG, "No service found for fqName " + fqName); return null; } @@ -180,7 +180,7 @@ final class RadioModule { return new RadioModule(service, prop); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to load module " + fqName, ex); + Slogf.e(TAG, "Failed to load module " + fqName, ex); return null; } } @@ -256,8 +256,8 @@ final class RadioModule { } if (idTypes == null) { - idTypes = new HashSet<>(filter.getIdentifierTypes()); - ids = new HashSet<>(filter.getIdentifiers()); + idTypes = new ArraySet<>(filter.getIdentifierTypes()); + ids = new ArraySet<>(filter.getIdentifiers()); includeCategories = filter.areCategoriesIncluded(); excludeModifications = filter.areModificationsExcluded(); continue; @@ -305,7 +305,7 @@ final class RadioModule { try { mHalTunerSession.stopProgramListUpdates(); } catch (RemoteException ex) { - Slog.e(TAG, "mHalTunerSession.stopProgramListUpdates() failed: ", ex); + Slogf.e(TAG, "mHalTunerSession.stopProgramListUpdates() failed: ", ex); } return; } @@ -327,7 +327,7 @@ final class RadioModule { newFilter)); Convert.throwOnError("startProgramListUpdates", halResult); } catch (RemoteException ex) { - Slog.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex); + Slogf.e(TAG, "mHalTunerSession.startProgramListUpdates() failed: ", ex); } } @@ -348,7 +348,7 @@ final class RadioModule { try { mHalTunerSession.close(); } catch (RemoteException ex) { - Slog.e(TAG, "mHalTunerSession.close() failed: ", ex); + Slogf.e(TAG, "mHalTunerSession.close() failed: ", ex); } mHalTunerSession = null; } @@ -385,18 +385,17 @@ final class RadioModule { runnable.run(tunerSession.mCallback); } catch (DeadObjectException ex) { // The other side died without calling close(), so just purge it from our records. - Slog.e(TAG, "Removing dead TunerSession"); + Slogf.e(TAG, "Removing dead TunerSession"); if (deadSessions == null) { deadSessions = new ArrayList<>(); } deadSessions.add(tunerSession); } catch (RemoteException ex) { - Slog.e(TAG, "Failed to invoke ITunerCallback: ", ex); + Slogf.e(TAG, "Failed to invoke ITunerCallback: ", ex); } } if (deadSessions != null) { - onTunerSessionsClosedLocked(deadSessions.toArray( - new TunerSession[deadSessions.size()])); + onTunerSessionsClosedLocked(deadSessions.toArray(new TunerSession[0])); } } @@ -429,7 +428,7 @@ final class RadioModule { try { hwCloseHandle.value.close(); } catch (RemoteException ex) { - Slog.e(TAG, "Failed closing announcement listener", ex); + Slogf.e(TAG, "Failed closing announcement listener", ex); } hwCloseHandle.value = null; } @@ -447,7 +446,9 @@ final class RadioModule { rawImage[i] = rawList.get(i); } - if (rawImage == null || rawImage.length == 0) return null; + if (rawImage.length == 0) { + return null; + } return BitmapFactory.decodeByteArray(rawImage, 0, rawImage.length); } diff --git a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java index 978dc01d1219..6d435e38117f 100644 --- a/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java +++ b/services/core/java/com/android/server/broadcastradio/hal2/TunerSession.java @@ -30,27 +30,25 @@ import android.hardware.radio.RadioManager; import android.os.Binder; import android.os.RemoteException; import android.util.ArrayMap; +import android.util.ArraySet; import android.util.IndentingPrintWriter; import android.util.MutableBoolean; import android.util.MutableInt; -import android.util.Slog; import com.android.internal.annotations.GuardedBy; import com.android.server.broadcastradio.RadioServiceUserController; import com.android.server.utils.Slogf; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -class TunerSession extends ITuner.Stub { +final class TunerSession extends ITuner.Stub { private static final String TAG = "BcRadio2Srv.session"; - private static final String kAudioDeviceName = "Radio tuner source"; private static final int TUNER_EVENT_LOGGER_QUEUE_SIZE = 25; private final Object mLock = new Object(); - @NonNull private final RadioEventLogger mEventLogger; + private final RadioEventLogger mEventLogger; private final RadioModule mModule; private final ITunerSession mHwSession; @@ -99,7 +97,7 @@ class TunerSession extends ITuner.Stub { try { mCallback.onError(error); } catch (RemoteException ex) { - Slog.w(TAG, "mCallback.onError() failed: ", ex); + Slogf.w(TAG, "mCallback.onError() failed: ", ex); } } mModule.onTunerSessionClosed(this); @@ -129,7 +127,7 @@ class TunerSession extends ITuner.Stub { checkNotClosedLocked(); mDummyConfig = Objects.requireNonNull(config); } - Slog.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0"); + Slogf.i(TAG, "Ignoring setConfiguration - not applicable for broadcastradio HAL 2.0"); mModule.fanoutAidlCallback(cb -> cb.onConfigurationChanged(config)); } @@ -148,7 +146,7 @@ class TunerSession extends ITuner.Stub { if (mIsMuted == mute) return; mIsMuted = mute; } - Slog.w(TAG, "Mute via RadioService is not implemented - please handle it via app"); + Slogf.w(TAG, "Mute via RadioService is not implemented - please handle it via app"); } @Override @@ -205,7 +203,7 @@ class TunerSession extends ITuner.Stub { @Override public void cancel() { - Slog.i(TAG, "Cancel"); + Slogf.i(TAG, "Cancel"); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.w(TAG, "Cannot cancel on HAL 2.0 client from non-current user"); return; @@ -218,7 +216,8 @@ class TunerSession extends ITuner.Stub { @Override public void cancelAnnouncement() { - Slog.w(TAG, "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0"); + Slogf.w(TAG, + "Announcements control doesn't involve cancelling at the HAL level in HAL 2.0"); } @Override @@ -229,7 +228,7 @@ class TunerSession extends ITuner.Stub { @Override public boolean startBackgroundScan() { - Slog.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0"); + Slogf.w(TAG, "Explicit background scan trigger is not supported with HAL 2.0"); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.w(TAG, "Cannot start background scan on HAL 2.0 client from non-current user"); @@ -240,7 +239,7 @@ class TunerSession extends ITuner.Stub { } @Override - public void startProgramListUpdates(ProgramList.Filter filter) throws RemoteException { + public void startProgramListUpdates(ProgramList.Filter filter) { mEventLogger.logRadioEvent("start programList updates %s", filter); if (!RadioServiceUserController.isCurrentOrSystemUser()) { Slogf.w(TAG, @@ -250,8 +249,8 @@ class TunerSession extends ITuner.Stub { // If the AIDL client provides a null filter, it wants all updates, so use the most broad // filter. if (filter == null) { - filter = new ProgramList.Filter(new HashSet<Integer>(), - new HashSet<android.hardware.radio.ProgramSelector.Identifier>(), true, false); + filter = new ProgramList.Filter(new ArraySet<>(), new ArraySet<>(), + /* includeCategories= */ true, /* excludeModifications= */ false); } synchronized (mLock) { checkNotClosedLocked(); @@ -285,7 +284,7 @@ class TunerSession extends ITuner.Stub { if (mProgramInfoCache == null) { return; } - clientUpdateChunks = mProgramInfoCache.filterAndUpdateFrom(halCache, true); + clientUpdateChunks = mProgramInfoCache.filterAndUpdateFrom(halCache, /* purge= */ true); } dispatchClientUpdateChunks(clientUpdateChunks); } @@ -298,7 +297,7 @@ class TunerSession extends ITuner.Stub { try { mCallback.onProgramListUpdated(chunk); } catch (RemoteException ex) { - Slog.w(TAG, "mCallback.onProgramListUpdated() failed: ", ex); + Slogf.w(TAG, "mCallback.onProgramListUpdated() failed: ", ex); } } } diff --git a/services/core/java/com/android/server/devicestate/OWNERS b/services/core/java/com/android/server/devicestate/OWNERS index ae79fc0e2c2d..43f3f0c5f3e8 100644 --- a/services/core/java/com/android/server/devicestate/OWNERS +++ b/services/core/java/com/android/server/devicestate/OWNERS @@ -1,3 +1,4 @@ ogunwale@google.com akulian@google.com -darryljohnson@google.com +lihongyu@google.com +kennethford@google.com diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java index 4aab9d26dbcb..06dc7f54dd8b 100644 --- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java +++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java @@ -55,6 +55,7 @@ import com.android.internal.os.BackgroundThread; import com.android.server.EventLogTags; import com.android.server.display.brightness.BrightnessEvent; import com.android.server.display.brightness.clamper.BrightnessClamperController; +import com.android.server.display.config.HysteresisLevels; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -673,14 +674,10 @@ public class AutomaticBrightnessController { } pw.println(); - pw.println(" mAmbientBrightnessThresholds="); - mAmbientBrightnessThresholds.dump(pw); - pw.println(" mScreenBrightnessThresholds="); - mScreenBrightnessThresholds.dump(pw); - pw.println(" mScreenBrightnessThresholdsIdle="); - mScreenBrightnessThresholdsIdle.dump(pw); - pw.println(" mAmbientBrightnessThresholdsIdle="); - mAmbientBrightnessThresholdsIdle.dump(pw); + pw.println(" mAmbientBrightnessThresholds=" + mAmbientBrightnessThresholds); + pw.println(" mAmbientBrightnessThresholdsIdle=" + mAmbientBrightnessThresholdsIdle); + pw.println(" mScreenBrightnessThresholds=" + mScreenBrightnessThresholds); + pw.println(" mScreenBrightnessThresholdsIdle=" + mScreenBrightnessThresholdsIdle); } public float[] getLastSensorValues() { diff --git a/services/core/java/com/android/server/display/BrightnessRangeController.java b/services/core/java/com/android/server/display/BrightnessRangeController.java index 10030b3c9176..dc0e80c686a8 100644 --- a/services/core/java/com/android/server/display/BrightnessRangeController.java +++ b/services/core/java/com/android/server/display/BrightnessRangeController.java @@ -117,6 +117,7 @@ class BrightnessRangeController { () -> mNormalBrightnessModeController.setAutoBrightnessState(state), () -> mHbmController.setAutoBrightnessEnabled(state) ); + mHdrClamper.setAutoBrightnessState(state); } void onBrightnessChanged(float brightness, float unthrottledBrightness, diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java index 70668cbea719..85a231fafb0a 100644 --- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java +++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java @@ -33,7 +33,6 @@ import android.os.Environment; import android.os.PowerManager; import android.text.TextUtils; import android.util.MathUtils; -import android.util.Pair; import android.util.Slog; import android.util.SparseArray; import android.util.Spline; @@ -46,7 +45,6 @@ import com.android.internal.display.BrightnessSynchronizer; import com.android.server.display.config.AutoBrightness; import com.android.server.display.config.BlockingZoneConfig; import com.android.server.display.config.BrightnessLimitMap; -import com.android.server.display.config.BrightnessThresholds; import com.android.server.display.config.BrightnessThrottlingMap; import com.android.server.display.config.BrightnessThrottlingPoint; import com.android.server.display.config.Density; @@ -58,6 +56,7 @@ import com.android.server.display.config.EvenDimmerBrightnessData; import com.android.server.display.config.HbmTiming; import com.android.server.display.config.HdrBrightnessData; import com.android.server.display.config.HighBrightnessMode; +import com.android.server.display.config.HysteresisLevels; import com.android.server.display.config.IdleScreenRefreshRateTimeout; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholds; @@ -80,7 +79,6 @@ import com.android.server.display.config.SdrHdrRatioPoint; import com.android.server.display.config.SensorData; import com.android.server.display.config.ThermalStatus; import com.android.server.display.config.ThermalThrottling; -import com.android.server.display.config.ThresholdPoint; import com.android.server.display.config.UsiVersion; import com.android.server.display.config.XmlParser; import com.android.server.display.feature.DisplayManagerFlags; @@ -625,13 +623,6 @@ public class DisplayDeviceConfig { private static final int DEFAULT_HIGH_REFRESH_RATE = 0; private static final float[] DEFAULT_BRIGHTNESS_THRESHOLDS = new float[]{}; - private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f}; - private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f}; - private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f}; - private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f}; - private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f}; - private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f}; - private static final int INTERPOLATION_DEFAULT = 0; private static final int INTERPOLATION_LINEAR = 1; @@ -713,38 +704,16 @@ public class DisplayDeviceConfig { private long mBrightnessRampIncreaseMaxIdleMillis = 0; private int mAmbientHorizonLong = AMBIENT_LIGHT_LONG_HORIZON_MILLIS; private int mAmbientHorizonShort = AMBIENT_LIGHT_SHORT_HORIZON_MILLIS; - private float mScreenBrighteningMinThreshold = 0.0f; // Retain behaviour as though there is - private float mScreenBrighteningMinThresholdIdle = 0.0f; // no minimum threshold for change in - private float mScreenDarkeningMinThreshold = 0.0f; // screen brightness or ambient - private float mScreenDarkeningMinThresholdIdle = 0.0f; // brightness. - private float mAmbientLuxBrighteningMinThreshold = 0.0f; - private float mAmbientLuxBrighteningMinThresholdIdle = 0.0f; - private float mAmbientLuxDarkeningMinThreshold = 0.0f; - private float mAmbientLuxDarkeningMinThresholdIdle = 0.0f; - - // Screen brightness thresholds levels & percentages - private float[] mScreenBrighteningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS; - private float[] mScreenBrighteningPercentages = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS; - private float[] mScreenDarkeningLevels = DEFAULT_SCREEN_THRESHOLD_LEVELS; - private float[] mScreenDarkeningPercentages = DEFAULT_SCREEN_DARKENING_THRESHOLDS; - - // Screen brightness thresholds levels & percentages for idle mode - private float[] mScreenBrighteningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS; - private float[] mScreenBrighteningPercentagesIdle = DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS; - private float[] mScreenDarkeningLevelsIdle = DEFAULT_SCREEN_THRESHOLD_LEVELS; - private float[] mScreenDarkeningPercentagesIdle = DEFAULT_SCREEN_DARKENING_THRESHOLDS; - - // Ambient brightness thresholds levels & percentages - private float[] mAmbientBrighteningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS; - private float[] mAmbientBrighteningPercentages = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS; - private float[] mAmbientDarkeningLevels = DEFAULT_AMBIENT_THRESHOLD_LEVELS; - private float[] mAmbientDarkeningPercentages = DEFAULT_AMBIENT_DARKENING_THRESHOLDS; - - // Ambient brightness thresholds levels & percentages for idle mode - private float[] mAmbientBrighteningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS; - private float[] mAmbientBrighteningPercentagesIdle = DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS; - private float[] mAmbientDarkeningLevelsIdle = DEFAULT_AMBIENT_THRESHOLD_LEVELS; - private float[] mAmbientDarkeningPercentagesIdle = DEFAULT_AMBIENT_DARKENING_THRESHOLDS; + + // Hysteresis levels for screen/ambient brightness for normal/idle modes + private HysteresisLevels mScreenBrightnessHysteresis = + HysteresisLevels.loadDisplayBrightnessConfig(null, null); + private HysteresisLevels mScreenBrightnessIdleHysteresis = + HysteresisLevels.loadDisplayBrightnessIdleConfig(null, null); + private HysteresisLevels mAmbientBrightnessHysteresis = + HysteresisLevels.loadAmbientBrightnessConfig(null, null); + private HysteresisLevels mAmbientBrightnessIdleHysteresis = + HysteresisLevels.loadAmbientBrightnessIdleConfig(null, null); // A mapping between screen off sensor values and lux values private int[] mScreenOffBrightnessSensorValueToLux; @@ -1302,324 +1271,20 @@ public class DisplayDeviceConfig { return mAmbientHorizonShort; } - /** - * The minimum value for the screen brightness increase to actually occur. - * @return float value in brightness scale of 0 - 1. - */ - public float getScreenBrighteningMinThreshold() { - return mScreenBrighteningMinThreshold; - } - - /** - * The minimum value for the screen brightness decrease to actually occur. - * @return float value in brightness scale of 0 - 1. - */ - public float getScreenDarkeningMinThreshold() { - return mScreenDarkeningMinThreshold; - } - - /** - * The minimum value for the screen brightness increase to actually occur while in idle screen - * brightness mode. - * @return float value in brightness scale of 0 - 1. - */ - public float getScreenBrighteningMinThresholdIdle() { - return mScreenBrighteningMinThresholdIdle; - } - - /** - * The minimum value for the screen brightness decrease to actually occur while in idle screen - * brightness mode. - * @return float value in brightness scale of 0 - 1. - */ - public float getScreenDarkeningMinThresholdIdle() { - return mScreenDarkeningMinThresholdIdle; - } - - /** - * The minimum value for the ambient lux increase for a screen brightness change to actually - * occur. - * @return float value in lux. - */ - public float getAmbientLuxBrighteningMinThreshold() { - return mAmbientLuxBrighteningMinThreshold; - } - - /** - * The minimum value for the ambient lux decrease for a screen brightness change to actually - * occur. - * @return float value in lux. - */ - public float getAmbientLuxDarkeningMinThreshold() { - return mAmbientLuxDarkeningMinThreshold; - } - - /** - * The minimum value for the ambient lux increase for a screen brightness change to actually - * occur while in idle screen brightness mode. - * @return float value in lux. - */ - public float getAmbientLuxBrighteningMinThresholdIdle() { - return mAmbientLuxBrighteningMinThresholdIdle; - } - - /** - * The minimum value for the ambient lux decrease for a screen brightness change to actually - * occur while in idle screen brightness mode. - * @return float value in lux. - */ - public float getAmbientLuxDarkeningMinThresholdIdle() { - return mAmbientLuxDarkeningMinThresholdIdle; - } - - /** - * The array that describes the range of screen brightness that each threshold percentage - * applies within. - * - * The (zero-based) index is calculated as follows - * value = current screen brightness value - * level = mScreenBrighteningLevels - * - * condition return - * value < level[0] = 0.0f - * level[n] <= value < level[n+1] = mScreenBrighteningPercentages[n] - * level[MAX] <= value = mScreenBrighteningPercentages[MAX] - * - * @return the screen brightness levels between 0.0 and 1.0 for which each - * mScreenBrighteningPercentages applies - */ - public float[] getScreenBrighteningLevels() { - return mScreenBrighteningLevels; - } - - /** - * The array that describes the screen brightening threshold percentage change at each screen - * brightness level described in mScreenBrighteningLevels. - * - * @return the percentages between 0 and 100 of brightness increase required in order for the - * screen brightness to change - */ - public float[] getScreenBrighteningPercentages() { - return mScreenBrighteningPercentages; - } - - /** - * The array that describes the range of screen brightness that each threshold percentage - * applies within. - * - * The (zero-based) index is calculated as follows - * value = current screen brightness value - * level = mScreenDarkeningLevels - * - * condition return - * value < level[0] = 0.0f - * level[n] <= value < level[n+1] = mScreenDarkeningPercentages[n] - * level[MAX] <= value = mScreenDarkeningPercentages[MAX] - * - * @return the screen brightness levels between 0.0 and 1.0 for which each - * mScreenDarkeningPercentages applies - */ - public float[] getScreenDarkeningLevels() { - return mScreenDarkeningLevels; - } - - /** - * The array that describes the screen darkening threshold percentage change at each screen - * brightness level described in mScreenDarkeningLevels. - * - * @return the percentages between 0 and 100 of brightness decrease required in order for the - * screen brightness to change - */ - public float[] getScreenDarkeningPercentages() { - return mScreenDarkeningPercentages; - } - - /** - * The array that describes the range of ambient brightness that each threshold - * percentage applies within. - * - * The (zero-based) index is calculated as follows - * value = current ambient brightness value - * level = mAmbientBrighteningLevels - * - * condition return - * value < level[0] = 0.0f - * level[n] <= value < level[n+1] = mAmbientBrighteningPercentages[n] - * level[MAX] <= value = mAmbientBrighteningPercentages[MAX] - * - * @return the ambient brightness levels from 0 lux upwards for which each - * mAmbientBrighteningPercentages applies - */ - public float[] getAmbientBrighteningLevels() { - return mAmbientBrighteningLevels; - } - - /** - * The array that describes the ambient brightening threshold percentage change at each ambient - * brightness level described in mAmbientBrighteningLevels. - * - * @return the percentages between 0 and 100 of brightness increase required in order for the - * screen brightness to change - */ - public float[] getAmbientBrighteningPercentages() { - return mAmbientBrighteningPercentages; - } - - /** - * The array that describes the range of ambient brightness that each threshold percentage - * applies within. - * - * The (zero-based) index is calculated as follows - * value = current ambient brightness value - * level = mAmbientDarkeningLevels - * - * condition return - * value < level[0] = 0.0f - * level[n] <= value < level[n+1] = mAmbientDarkeningPercentages[n] - * level[MAX] <= value = mAmbientDarkeningPercentages[MAX] - * - * @return the ambient brightness levels from 0 lux upwards for which each - * mAmbientDarkeningPercentages applies - */ - public float[] getAmbientDarkeningLevels() { - return mAmbientDarkeningLevels; - } - - /** - * The array that describes the ambient darkening threshold percentage change at each ambient - * brightness level described in mAmbientDarkeningLevels. - * - * @return the percentages between 0 and 100 of brightness decrease required in order for the - * screen brightness to change - */ - public float[] getAmbientDarkeningPercentages() { - return mAmbientDarkeningPercentages; - } - - /** - * The array that describes the range of screen brightness that each threshold percentage - * applies within whilst in idle screen brightness mode. - * - * The (zero-based) index is calculated as follows - * value = current screen brightness value - * level = mScreenBrighteningLevelsIdle - * - * condition return - * value < level[0] = 0.0f - * level[n] <= value < level[n+1] = mScreenBrighteningPercentagesIdle[n] - * level[MAX] <= value = mScreenBrighteningPercentagesIdle[MAX] - * - * @return the screen brightness levels between 0.0 and 1.0 for which each - * mScreenBrighteningPercentagesIdle applies - */ - public float[] getScreenBrighteningLevelsIdle() { - return mScreenBrighteningLevelsIdle; - } - - /** - * The array that describes the screen brightening threshold percentage change at each screen - * brightness level described in mScreenBrighteningLevelsIdle. - * - * @return the percentages between 0 and 100 of brightness increase required in order for the - * screen brightness to change while in idle mode. - */ - public float[] getScreenBrighteningPercentagesIdle() { - return mScreenBrighteningPercentagesIdle; - } - - /** - * The array that describes the range of screen brightness that each threshold percentage - * applies within whilst in idle screen brightness mode. - * - * The (zero-based) index is calculated as follows - * value = current screen brightness value - * level = mScreenDarkeningLevelsIdle - * - * condition return - * value < level[0] = 0.0f - * level[n] <= value < level[n+1] = mScreenDarkeningPercentagesIdle[n] - * level[MAX] <= value = mScreenDarkeningPercentagesIdle[MAX] - * - * @return the screen brightness levels between 0.0 and 1.0 for which each - * mScreenDarkeningPercentagesIdle applies - */ - public float[] getScreenDarkeningLevelsIdle() { - return mScreenDarkeningLevelsIdle; + public HysteresisLevels getAmbientBrightnessHysteresis() { + return mAmbientBrightnessHysteresis; } - /** - * The array that describes the screen darkening threshold percentage change at each screen - * brightness level described in mScreenDarkeningLevelsIdle. - * - * @return the percentages between 0 and 100 of brightness decrease required in order for the - * screen brightness to change while in idle mode. - */ - public float[] getScreenDarkeningPercentagesIdle() { - return mScreenDarkeningPercentagesIdle; + public HysteresisLevels getAmbientBrightnessIdleHysteresis() { + return mAmbientBrightnessIdleHysteresis; } - /** - * The array that describes the range of ambient brightness that each threshold percentage - * applies within whilst in idle screen brightness mode. - * - * The (zero-based) index is calculated as follows - * value = current ambient brightness value - * level = mAmbientBrighteningLevelsIdle - * - * condition return - * value < level[0] = 0.0f - * level[n] <= value < level[n+1] = mAmbientBrighteningPercentagesIdle[n] - * level[MAX] <= value = mAmbientBrighteningPercentagesIdle[MAX] - * - * @return the ambient brightness levels from 0 lux upwards for which each - * mAmbientBrighteningPercentagesIdle applies - */ - public float[] getAmbientBrighteningLevelsIdle() { - return mAmbientBrighteningLevelsIdle; + public HysteresisLevels getScreenBrightnessHysteresis() { + return mScreenBrightnessHysteresis; } - /** - * The array that describes the ambient brightness threshold percentage change whilst in - * idle screen brightness mode at each ambient brightness level described in - * mAmbientBrighteningLevelsIdle. - * - * @return the percentages between 0 and 100 of ambient brightness increase required in order - * for the screen brightness to change - */ - public float[] getAmbientBrighteningPercentagesIdle() { - return mAmbientBrighteningPercentagesIdle; - } - - /** - * The array that describes the range of ambient brightness that each threshold percentage - * applies within whilst in idle screen brightness mode. - * - * The (zero-based) index is calculated as follows - * value = current ambient brightness value - * level = mAmbientDarkeningLevelsIdle - * - * condition return - * value < level[0] = 0.0f - * level[n] <= value < level[n+1] = mAmbientDarkeningPercentagesIdle[n] - * level[MAX] <= value = mAmbientDarkeningPercentagesIdle[MAX] - * - * @return the ambient brightness levels from 0 lux upwards for which each - * mAmbientDarkeningPercentagesIdle applies - */ - public float[] getAmbientDarkeningLevelsIdle() { - return mAmbientDarkeningLevelsIdle; - } - - /** - * The array that describes the ambient brightness threshold percentage change whilst in - * idle screen brightness mode at each ambient brightness level described in - * mAmbientDarkeningLevelsIdle. - * - * @return the percentages between 0 and 100 of ambient brightness decrease required in order - * for the screen brightness to change - */ - public float[] getAmbientDarkeningPercentagesIdle() { - return mAmbientDarkeningPercentagesIdle; + public HysteresisLevels getScreenBrightnessIdleHysteresis() { + return mScreenBrightnessIdleHysteresis; } public SensorData getAmbientLightSensor() { @@ -1985,49 +1650,13 @@ public class DisplayDeviceConfig { + "mAmbientHorizonLong=" + mAmbientHorizonLong + ", mAmbientHorizonShort=" + mAmbientHorizonShort + "\n" - + "mScreenDarkeningMinThreshold=" + mScreenDarkeningMinThreshold - + ", mScreenDarkeningMinThresholdIdle=" + mScreenDarkeningMinThresholdIdle - + ", mScreenBrighteningMinThreshold=" + mScreenBrighteningMinThreshold - + ", mScreenBrighteningMinThresholdIdle=" + mScreenBrighteningMinThresholdIdle - + ", mAmbientLuxDarkeningMinThreshold=" + mAmbientLuxDarkeningMinThreshold - + ", mAmbientLuxDarkeningMinThresholdIdle=" + mAmbientLuxDarkeningMinThresholdIdle - + ", mAmbientLuxBrighteningMinThreshold=" + mAmbientLuxBrighteningMinThreshold - + ", mAmbientLuxBrighteningMinThresholdIdle=" - + mAmbientLuxBrighteningMinThresholdIdle + + "mAmbientBrightnessHysteresis=" + mAmbientBrightnessHysteresis + + "\n" + + "mAmbientIdleHysteresis=" + mAmbientBrightnessIdleHysteresis + "\n" - + "mScreenBrighteningLevels=" + Arrays.toString( - mScreenBrighteningLevels) - + ", mScreenBrighteningPercentages=" + Arrays.toString( - mScreenBrighteningPercentages) - + ", mScreenDarkeningLevels=" + Arrays.toString( - mScreenDarkeningLevels) - + ", mScreenDarkeningPercentages=" + Arrays.toString( - mScreenDarkeningPercentages) - + ", mAmbientBrighteningLevels=" + Arrays.toString( - mAmbientBrighteningLevels) - + ", mAmbientBrighteningPercentages=" + Arrays.toString( - mAmbientBrighteningPercentages) - + ", mAmbientDarkeningLevels=" + Arrays.toString( - mAmbientDarkeningLevels) - + ", mAmbientDarkeningPercentages=" + Arrays.toString( - mAmbientDarkeningPercentages) + + "mScreenBrightnessHysteresis=" + mScreenBrightnessHysteresis + "\n" - + "mAmbientBrighteningLevelsIdle=" + Arrays.toString( - mAmbientBrighteningLevelsIdle) - + ", mAmbientBrighteningPercentagesIdle=" + Arrays.toString( - mAmbientBrighteningPercentagesIdle) - + ", mAmbientDarkeningLevelsIdle=" + Arrays.toString( - mAmbientDarkeningLevelsIdle) - + ", mAmbientDarkeningPercentagesIdle=" + Arrays.toString( - mAmbientDarkeningPercentagesIdle) - + ", mScreenBrighteningLevelsIdle=" + Arrays.toString( - mScreenBrighteningLevelsIdle) - + ", mScreenBrighteningPercentagesIdle=" + Arrays.toString( - mScreenBrighteningPercentagesIdle) - + ", mScreenDarkeningLevelsIdle=" + Arrays.toString( - mScreenDarkeningLevelsIdle) - + ", mScreenDarkeningPercentagesIdle=" + Arrays.toString( - mScreenDarkeningPercentagesIdle) + + "mScreenBrightnessIdleHysteresis=" + mScreenBrightnessIdleHysteresis + "\n" + "mAmbientLightSensor=" + mAmbientLightSensor + ", mScreenOffBrightnessSensor=" + mScreenOffBrightnessSensor @@ -3137,281 +2766,15 @@ public class DisplayDeviceConfig { } private void loadBrightnessChangeThresholds(DisplayConfiguration config) { - loadDisplayBrightnessThresholds(config); - loadAmbientBrightnessThresholds(config); - loadDisplayBrightnessThresholdsIdle(config); - loadAmbientBrightnessThresholdsIdle(config); - } - - private void loadDisplayBrightnessThresholds(DisplayConfiguration config) { - BrightnessThresholds brighteningScreen = null; - BrightnessThresholds darkeningScreen = null; - if (config != null && config.getDisplayBrightnessChangeThresholds() != null) { - brighteningScreen = - config.getDisplayBrightnessChangeThresholds().getBrighteningThresholds(); - darkeningScreen = - config.getDisplayBrightnessChangeThresholds().getDarkeningThresholds(); - - } - - // Screen bright/darkening threshold levels for active mode - Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage( - brighteningScreen, - com.android.internal.R.array.config_screenThresholdLevels, - com.android.internal.R.array.config_screenBrighteningThresholds, - DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS, - /* potentialOldBrightnessScale= */ true); - - mScreenBrighteningLevels = screenBrighteningPair.first; - mScreenBrighteningPercentages = screenBrighteningPair.second; - - Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage( - darkeningScreen, - com.android.internal.R.array.config_screenThresholdLevels, - com.android.internal.R.array.config_screenDarkeningThresholds, - DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS, - /* potentialOldBrightnessScale= */ true); - mScreenDarkeningLevels = screenDarkeningPair.first; - mScreenDarkeningPercentages = screenDarkeningPair.second; - - // Screen bright/darkening threshold minimums for active mode - if (brighteningScreen != null && brighteningScreen.getMinimum() != null) { - mScreenBrighteningMinThreshold = brighteningScreen.getMinimum().floatValue(); - } - if (darkeningScreen != null && darkeningScreen.getMinimum() != null) { - mScreenDarkeningMinThreshold = darkeningScreen.getMinimum().floatValue(); - } - } - - private void loadAmbientBrightnessThresholds(DisplayConfiguration config) { - // Ambient Brightness Threshold Levels - BrightnessThresholds brighteningAmbientLux = null; - BrightnessThresholds darkeningAmbientLux = null; - if (config != null && config.getAmbientBrightnessChangeThresholds() != null) { - brighteningAmbientLux = - config.getAmbientBrightnessChangeThresholds().getBrighteningThresholds(); - darkeningAmbientLux = - config.getAmbientBrightnessChangeThresholds().getDarkeningThresholds(); - } - - // Ambient bright/darkening threshold levels for active mode - Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage( - brighteningAmbientLux, - com.android.internal.R.array.config_ambientThresholdLevels, - com.android.internal.R.array.config_ambientBrighteningThresholds, - DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS); - mAmbientBrighteningLevels = ambientBrighteningPair.first; - mAmbientBrighteningPercentages = ambientBrighteningPair.second; - - Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage( - darkeningAmbientLux, - com.android.internal.R.array.config_ambientThresholdLevels, - com.android.internal.R.array.config_ambientDarkeningThresholds, - DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS); - mAmbientDarkeningLevels = ambientDarkeningPair.first; - mAmbientDarkeningPercentages = ambientDarkeningPair.second; - - // Ambient bright/darkening threshold minimums for active/idle mode - if (brighteningAmbientLux != null && brighteningAmbientLux.getMinimum() != null) { - mAmbientLuxBrighteningMinThreshold = - brighteningAmbientLux.getMinimum().floatValue(); - } - - if (darkeningAmbientLux != null && darkeningAmbientLux.getMinimum() != null) { - mAmbientLuxDarkeningMinThreshold = darkeningAmbientLux.getMinimum().floatValue(); - } - } - - private void loadDisplayBrightnessThresholdsIdle(DisplayConfiguration config) { - BrightnessThresholds brighteningScreenIdle = null; - BrightnessThresholds darkeningScreenIdle = null; - if (config != null && config.getDisplayBrightnessChangeThresholdsIdle() != null) { - brighteningScreenIdle = - config.getDisplayBrightnessChangeThresholdsIdle().getBrighteningThresholds(); - darkeningScreenIdle = - config.getDisplayBrightnessChangeThresholdsIdle().getDarkeningThresholds(); - } - - Pair<float[], float[]> screenBrighteningPair = getBrightnessLevelAndPercentage( - brighteningScreenIdle, - com.android.internal.R.array.config_screenThresholdLevels, - com.android.internal.R.array.config_screenBrighteningThresholds, - DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS, - /* potentialOldBrightnessScale= */ true); - mScreenBrighteningLevelsIdle = screenBrighteningPair.first; - mScreenBrighteningPercentagesIdle = screenBrighteningPair.second; - - Pair<float[], float[]> screenDarkeningPair = getBrightnessLevelAndPercentage( - darkeningScreenIdle, - com.android.internal.R.array.config_screenThresholdLevels, - com.android.internal.R.array.config_screenDarkeningThresholds, - DEFAULT_SCREEN_THRESHOLD_LEVELS, DEFAULT_SCREEN_DARKENING_THRESHOLDS, - /* potentialOldBrightnessScale= */ true); - mScreenDarkeningLevelsIdle = screenDarkeningPair.first; - mScreenDarkeningPercentagesIdle = screenDarkeningPair.second; - - if (brighteningScreenIdle != null - && brighteningScreenIdle.getMinimum() != null) { - mScreenBrighteningMinThresholdIdle = - brighteningScreenIdle.getMinimum().floatValue(); - } - if (darkeningScreenIdle != null && darkeningScreenIdle.getMinimum() != null) { - mScreenDarkeningMinThresholdIdle = - darkeningScreenIdle.getMinimum().floatValue(); - } - } - - private void loadAmbientBrightnessThresholdsIdle(DisplayConfiguration config) { - BrightnessThresholds brighteningAmbientLuxIdle = null; - BrightnessThresholds darkeningAmbientLuxIdle = null; - if (config != null && config.getAmbientBrightnessChangeThresholdsIdle() != null) { - brighteningAmbientLuxIdle = - config.getAmbientBrightnessChangeThresholdsIdle().getBrighteningThresholds(); - darkeningAmbientLuxIdle = - config.getAmbientBrightnessChangeThresholdsIdle().getDarkeningThresholds(); - } - - Pair<float[], float[]> ambientBrighteningPair = getBrightnessLevelAndPercentage( - brighteningAmbientLuxIdle, - com.android.internal.R.array.config_ambientThresholdLevels, - com.android.internal.R.array.config_ambientBrighteningThresholds, - DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS); - mAmbientBrighteningLevelsIdle = ambientBrighteningPair.first; - mAmbientBrighteningPercentagesIdle = ambientBrighteningPair.second; - - Pair<float[], float[]> ambientDarkeningPair = getBrightnessLevelAndPercentage( - darkeningAmbientLuxIdle, - com.android.internal.R.array.config_ambientThresholdLevels, - com.android.internal.R.array.config_ambientDarkeningThresholds, - DEFAULT_AMBIENT_THRESHOLD_LEVELS, DEFAULT_AMBIENT_DARKENING_THRESHOLDS); - mAmbientDarkeningLevelsIdle = ambientDarkeningPair.first; - mAmbientDarkeningPercentagesIdle = ambientDarkeningPair.second; - - if (brighteningAmbientLuxIdle != null - && brighteningAmbientLuxIdle.getMinimum() != null) { - mAmbientLuxBrighteningMinThresholdIdle = - brighteningAmbientLuxIdle.getMinimum().floatValue(); - } - - if (darkeningAmbientLuxIdle != null && darkeningAmbientLuxIdle.getMinimum() != null) { - mAmbientLuxDarkeningMinThresholdIdle = - darkeningAmbientLuxIdle.getMinimum().floatValue(); - } - } - - private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds, - int configFallbackThreshold, int configFallbackPercentage, float[] defaultLevels, - float[] defaultPercentage) { - return getBrightnessLevelAndPercentage(thresholds, configFallbackThreshold, - configFallbackPercentage, defaultLevels, defaultPercentage, false); - } - - // Returns two float arrays, one of the brightness levels and one of the corresponding threshold - // percentages for brightness levels at or above the lux value. - // Historically, config.xml would have an array for brightness levels that was 1 shorter than - // the levels array. Now we prepend a 0 to this array so they can be treated the same in the - // rest of the framework. Values were also defined in different units (permille vs percent). - private Pair<float[], float[]> getBrightnessLevelAndPercentage(BrightnessThresholds thresholds, - int configFallbackThreshold, int configFallbackPermille, - float[] defaultLevels, float[] defaultPercentage, - boolean potentialOldBrightnessScale) { - if (thresholds != null - && thresholds.getBrightnessThresholdPoints() != null - && thresholds.getBrightnessThresholdPoints() - .getBrightnessThresholdPoint().size() != 0) { - - // The level and percentages arrays are equal length in the ddc (new system) - List<ThresholdPoint> points = - thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint(); - final int size = points.size(); - - float[] thresholdLevels = new float[size]; - float[] thresholdPercentages = new float[size]; - - int i = 0; - for (ThresholdPoint point : points) { - thresholdLevels[i] = point.getThreshold().floatValue(); - thresholdPercentages[i] = point.getPercentage().floatValue(); - i++; - } - return new Pair<>(thresholdLevels, thresholdPercentages); - } else { - // The level and percentages arrays are unequal length in config.xml (old system) - // We prefix the array with a 0 value to ensure they can be handled consistently - // with the new system. - - // Load levels array - int[] configThresholdArray = mContext.getResources().getIntArray( - configFallbackThreshold); - int configThresholdsSize; - if (configThresholdArray == null || configThresholdArray.length == 0) { - configThresholdsSize = 1; - } else { - configThresholdsSize = configThresholdArray.length + 1; - } - - - // Load percentage array - int[] configPermille = mContext.getResources().getIntArray( - configFallbackPermille); - - // Ensure lengths match up - boolean emptyArray = configPermille == null || configPermille.length == 0; - if (emptyArray && configThresholdsSize == 1) { - return new Pair<>(defaultLevels, defaultPercentage); - } - if (emptyArray || configPermille.length != configThresholdsSize) { - throw new IllegalArgumentException( - "Brightness threshold arrays do not align in length"); - } - - // Calculate levels array - float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize]; - // Start at 1, so that 0 index value is 0.0f (default) - for (int i = 1; i < configThresholdsSize; i++) { - configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1]; - } - if (potentialOldBrightnessScale) { - configThresholdWithZeroPrefixed = - constraintInRangeIfNeeded(configThresholdWithZeroPrefixed); - } - - // Calculate percentages array - float[] configPercentage = new float[configThresholdsSize]; - for (int i = 0; i < configPermille.length; i++) { - configPercentage[i] = configPermille[i] / 10.0f; - } return new Pair<>(configThresholdWithZeroPrefixed, configPercentage); - } - } - - /** - * This check is due to historical reasons, where screen thresholdLevels used to be - * integer values in the range of [0-255], but then was changed to be float values from [0,1]. - * To accommodate both the possibilities, we first check if all the thresholdLevels are in - * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale. - */ - private float[] constraintInRangeIfNeeded(float[] thresholdLevels) { - if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f, - /* maxValueInclusive= */ 1.0f)) { - return thresholdLevels; - } - - Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale"); - float[] thresholdLevelsScaled = new float[thresholdLevels.length]; - for (int index = 0; thresholdLevels.length > index; ++index) { - thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f; - } - return thresholdLevelsScaled; - } - - private boolean isAllInRange(float[] configArray, float minValueInclusive, - float maxValueInclusive) { - for (float v : configArray) { - if (v < minValueInclusive || v > maxValueInclusive) { - return false; - } - } - return true; + Resources res = mContext.getResources(); + mScreenBrightnessHysteresis = + HysteresisLevels.loadDisplayBrightnessConfig(config, res); + mScreenBrightnessIdleHysteresis = + HysteresisLevels.loadDisplayBrightnessIdleConfig(config, res); + mAmbientBrightnessHysteresis = + HysteresisLevels.loadAmbientBrightnessConfig(config, res); + mAmbientBrightnessIdleHysteresis = + HysteresisLevels.loadAmbientBrightnessIdleConfig(config, res); } private boolean thermalStatusIsValid(ThermalStatus value) { diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java index 40f0362ff8f3..31092f27c838 100644 --- a/services/core/java/com/android/server/display/DisplayManagerService.java +++ b/services/core/java/com/android/server/display/DisplayManagerService.java @@ -757,6 +757,7 @@ public final class DisplayManagerService extends SystemService { mContext.getSystemService(DeviceStateManager.class).registerCallback( new HandlerExecutor(mHandler), new DeviceStateListener()); + mLogicalDisplayMapper.onWindowManagerReady(); scheduleTraversalLocked(false); } } diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java index b21a89a92803..404c3ad3a51b 100644 --- a/services/core/java/com/android/server/display/DisplayPowerController.java +++ b/services/core/java/com/android/server/display/DisplayPowerController.java @@ -86,6 +86,7 @@ import com.android.server.display.brightness.clamper.BrightnessClamperController import com.android.server.display.brightness.strategy.AutomaticBrightnessStrategy; import com.android.server.display.color.ColorDisplayService.ColorDisplayServiceInternal; import com.android.server.display.color.ColorDisplayService.ReduceBrightColorsListener; +import com.android.server.display.config.HysteresisLevels; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.Layout; import com.android.server.display.state.DisplayStateController; @@ -1050,76 +1051,20 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call if (defaultModeBrightnessMapper != null) { // Ambient Lux - Active Mode Brightness Thresholds - float[] ambientBrighteningThresholds = - mDisplayDeviceConfig.getAmbientBrighteningPercentages(); - float[] ambientDarkeningThresholds = - mDisplayDeviceConfig.getAmbientDarkeningPercentages(); - float[] ambientBrighteningLevels = - mDisplayDeviceConfig.getAmbientBrighteningLevels(); - float[] ambientDarkeningLevels = - mDisplayDeviceConfig.getAmbientDarkeningLevels(); - float ambientDarkeningMinThreshold = - mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(); - float ambientBrighteningMinThreshold = - mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(); - HysteresisLevels ambientBrightnessThresholds = mInjector.getHysteresisLevels( - ambientBrighteningThresholds, ambientDarkeningThresholds, - ambientBrighteningLevels, ambientDarkeningLevels, ambientDarkeningMinThreshold, - ambientBrighteningMinThreshold); + HysteresisLevels ambientBrightnessThresholds = + mDisplayDeviceConfig.getAmbientBrightnessHysteresis(); // Display - Active Mode Brightness Thresholds - float[] screenBrighteningThresholds = - mDisplayDeviceConfig.getScreenBrighteningPercentages(); - float[] screenDarkeningThresholds = - mDisplayDeviceConfig.getScreenDarkeningPercentages(); - float[] screenBrighteningLevels = - mDisplayDeviceConfig.getScreenBrighteningLevels(); - float[] screenDarkeningLevels = - mDisplayDeviceConfig.getScreenDarkeningLevels(); - float screenDarkeningMinThreshold = - mDisplayDeviceConfig.getScreenDarkeningMinThreshold(); - float screenBrighteningMinThreshold = - mDisplayDeviceConfig.getScreenBrighteningMinThreshold(); - HysteresisLevels screenBrightnessThresholds = mInjector.getHysteresisLevels( - screenBrighteningThresholds, screenDarkeningThresholds, - screenBrighteningLevels, screenDarkeningLevels, screenDarkeningMinThreshold, - screenBrighteningMinThreshold, true); + HysteresisLevels screenBrightnessThresholds = + mDisplayDeviceConfig.getScreenBrightnessHysteresis(); // Ambient Lux - Idle Screen Brightness Thresholds - float ambientDarkeningMinThresholdIdle = - mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(); - float ambientBrighteningMinThresholdIdle = - mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(); - float[] ambientBrighteningThresholdsIdle = - mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(); - float[] ambientDarkeningThresholdsIdle = - mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(); - float[] ambientBrighteningLevelsIdle = - mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(); - float[] ambientDarkeningLevelsIdle = - mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(); - HysteresisLevels ambientBrightnessThresholdsIdle = mInjector.getHysteresisLevels( - ambientBrighteningThresholdsIdle, ambientDarkeningThresholdsIdle, - ambientBrighteningLevelsIdle, ambientDarkeningLevelsIdle, - ambientDarkeningMinThresholdIdle, ambientBrighteningMinThresholdIdle); + HysteresisLevels ambientBrightnessThresholdsIdle = + mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis(); // Display - Idle Screen Brightness Thresholds - float screenDarkeningMinThresholdIdle = - mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(); - float screenBrighteningMinThresholdIdle = - mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(); - float[] screenBrighteningThresholdsIdle = - mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(); - float[] screenDarkeningThresholdsIdle = - mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(); - float[] screenBrighteningLevelsIdle = - mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(); - float[] screenDarkeningLevelsIdle = - mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(); - HysteresisLevels screenBrightnessThresholdsIdle = mInjector.getHysteresisLevels( - screenBrighteningThresholdsIdle, screenDarkeningThresholdsIdle, - screenBrighteningLevelsIdle, screenDarkeningLevelsIdle, - screenDarkeningMinThresholdIdle, screenBrighteningMinThresholdIdle); + HysteresisLevels screenBrightnessThresholdsIdle = + mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis(); long brighteningLightDebounce = mDisplayDeviceConfig .getAutoBrightnessBrighteningLightDebounce(); @@ -3208,25 +3153,6 @@ final class DisplayPowerController implements AutomaticBrightnessController.Call AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController); } - HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, - float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, - float[] darkeningThresholdLevels, float minDarkeningThreshold, - float minBrighteningThreshold) { - return new HysteresisLevels(brighteningThresholdsPercentages, - darkeningThresholdsPercentages, brighteningThresholdLevels, - darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold); - } - - HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, - float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, - float[] darkeningThresholdLevels, float minDarkeningThreshold, - float minBrighteningThreshold, boolean potentialOldBrightnessRange) { - return new HysteresisLevels(brighteningThresholdsPercentages, - darkeningThresholdsPercentages, brighteningThresholdLevels, - darkeningThresholdLevels, minDarkeningThreshold, minBrighteningThreshold, - potentialOldBrightnessRange); - } - ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController( SensorManager sensorManager, Sensor lightSensor, diff --git a/services/core/java/com/android/server/display/HysteresisLevels.java b/services/core/java/com/android/server/display/HysteresisLevels.java deleted file mode 100644 index 0521b8ac4f3b..000000000000 --- a/services/core/java/com/android/server/display/HysteresisLevels.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2016 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.display; - -import android.util.Slog; - -import com.android.server.display.utils.DebugUtils; - -import java.io.PrintWriter; -import java.util.Arrays; - -/** - * A helper class for handling access to illuminance hysteresis level values. - */ -public class HysteresisLevels { - private static final String TAG = "HysteresisLevels"; - - // To enable these logs, run: - // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot' - private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); - - private final float[] mBrighteningThresholdsPercentages; - private final float[] mDarkeningThresholdsPercentages; - private final float[] mBrighteningThresholdLevels; - private final float[] mDarkeningThresholdLevels; - private final float mMinDarkening; - private final float mMinBrightening; - - /** - * Creates a {@code HysteresisLevels} object with the given equal-length - * float arrays. - * @param brighteningThresholdsPercentages 0-100 of thresholds - * @param darkeningThresholdsPercentages 0-100 of thresholds - * @param brighteningThresholdLevels float array of brightness values in the relevant units - * @param minBrighteningThreshold the minimum value for which the brightening value needs to - * return. - * @param minDarkeningThreshold the minimum value for which the darkening value needs to return. - * @param potentialOldBrightnessRange whether or not the values used could be from the old - * screen brightness range ie, between 1-255. - */ - HysteresisLevels(float[] brighteningThresholdsPercentages, - float[] darkeningThresholdsPercentages, - float[] brighteningThresholdLevels, float[] darkeningThresholdLevels, - float minDarkeningThreshold, float minBrighteningThreshold, - boolean potentialOldBrightnessRange) { - if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length - || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) { - throw new IllegalArgumentException("Mismatch between hysteresis array lengths."); - } - mBrighteningThresholdsPercentages = - setArrayFormat(brighteningThresholdsPercentages, 100.0f); - mDarkeningThresholdsPercentages = - setArrayFormat(darkeningThresholdsPercentages, 100.0f); - mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f); - mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f); - mMinDarkening = minDarkeningThreshold; - mMinBrightening = minBrighteningThreshold; - } - - HysteresisLevels(float[] brighteningThresholdsPercentages, - float[] darkeningThresholdsPercentages, - float[] brighteningThresholdLevels, float[] darkeningThresholdLevels, - float minDarkeningThreshold, float minBrighteningThreshold) { - this(brighteningThresholdsPercentages, darkeningThresholdsPercentages, - brighteningThresholdLevels, darkeningThresholdLevels, minDarkeningThreshold, - minBrighteningThreshold, false); - } - - /** - * Return the brightening hysteresis threshold for the given value level. - */ - public float getBrighteningThreshold(float value) { - final float brightConstant = getReferenceLevel(value, - mBrighteningThresholdLevels, mBrighteningThresholdsPercentages); - - float brightThreshold = value * (1.0f + brightConstant); - if (DEBUG) { - Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold=" - + brightThreshold + ", value=" + value); - } - - brightThreshold = Math.max(brightThreshold, value + mMinBrightening); - return brightThreshold; - } - - /** - * Return the darkening hysteresis threshold for the given value level. - */ - public float getDarkeningThreshold(float value) { - final float darkConstant = getReferenceLevel(value, - mDarkeningThresholdLevels, mDarkeningThresholdsPercentages); - float darkThreshold = value * (1.0f - darkConstant); - if (DEBUG) { - Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold=" - + darkThreshold + ", value=" + value); - } - darkThreshold = Math.min(darkThreshold, value - mMinDarkening); - return Math.max(darkThreshold, 0.0f); - } - - /** - * Return the hysteresis constant for the closest threshold value from the given array. - */ - private float getReferenceLevel(float value, float[] thresholdLevels, - float[] thresholdPercentages) { - if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) { - return 0.0f; - } - int index = 0; - while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) { - index++; - } - return thresholdPercentages[index]; - } - - /** - * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}. - */ - private float[] setArrayFormat(float[] configArray, float divideFactor) { - float[] levelArray = new float[configArray.length]; - for (int index = 0; levelArray.length > index; ++index) { - levelArray[index] = configArray[index] / divideFactor; - } - return levelArray; - } - - void dump(PrintWriter pw) { - pw.println("HysteresisLevels"); - pw.println(" mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels)); - pw.println(" mBrighteningThresholdsPercentages=" - + Arrays.toString(mBrighteningThresholdsPercentages)); - pw.println(" mMinBrightening=" + mMinBrightening); - pw.println(" mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels)); - pw.println(" mDarkeningThresholdsPercentages=" - + Arrays.toString(mDarkeningThresholdsPercentages)); - pw.println(" mMinDarkening=" + mMinDarkening); - } -} diff --git a/services/core/java/com/android/server/display/LogicalDisplayMapper.java b/services/core/java/com/android/server/display/LogicalDisplayMapper.java index 6203a32151a0..bca53cf02f69 100644 --- a/services/core/java/com/android/server/display/LogicalDisplayMapper.java +++ b/services/core/java/com/android/server/display/LogicalDisplayMapper.java @@ -41,10 +41,12 @@ import android.view.DisplayInfo; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.foldables.FoldGracePeriodProvider; +import com.android.server.LocalServices; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; import com.android.server.display.utils.DebugUtils; +import com.android.server.policy.WindowManagerPolicy; import com.android.server.utils.FoldSettingProvider; import java.io.PrintWriter; @@ -189,6 +191,7 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { * #updateLogicalDisplaysLocked} to establish which Virtual Devices own which Virtual Displays. */ private final ArrayMap<String, Integer> mVirtualDeviceDisplayMapping = new ArrayMap<>(); + private WindowManagerPolicy mWindowManagerPolicy; private int mNextNonDefaultGroupId = Display.DEFAULT_DISPLAY_GROUP + 1; private final DisplayIdProducer mIdProducer = (isDefault) -> @@ -274,6 +277,10 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { mListener.onTraversalRequested(); } + public void onWindowManagerReady() { + mWindowManagerPolicy = LocalServices.getService(WindowManagerPolicy.class); + } + public LogicalDisplay getDisplayLocked(int displayId) { return getDisplayLocked(displayId, /* includeDisabled= */ true); } @@ -1114,14 +1121,22 @@ class LogicalDisplayMapper implements DisplayDeviceRepository.Listener { final int logicalDisplayId = displayLayout.getLogicalDisplayId(); LogicalDisplay newDisplay = getDisplayLocked(logicalDisplayId); + boolean newDisplayCreated = false; if (newDisplay == null) { newDisplay = createNewLogicalDisplayLocked( null /*displayDevice*/, logicalDisplayId); + newDisplayCreated = true; } // Now swap the underlying display devices between the old display and the new display final LogicalDisplay oldDisplay = getDisplayLocked(device); if (newDisplay != oldDisplay) { + // Display is swapping, notify WindowManager, so it can prepare for + // the display switch + if (!newDisplayCreated && mWindowManagerPolicy != null) { + mWindowManagerPolicy.onDisplaySwitchStart(newDisplay.getDisplayIdLocked()); + } + newDisplay.swapDisplaysLocked(oldDisplay); } DisplayDeviceConfig config = device.getDisplayDeviceConfig(); diff --git a/services/core/java/com/android/server/display/NormalBrightnessModeController.java b/services/core/java/com/android/server/display/NormalBrightnessModeController.java index 135ebd8f4fbf..e94cf00437eb 100644 --- a/services/core/java/com/android/server/display/NormalBrightnessModeController.java +++ b/services/core/java/com/android/server/display/NormalBrightnessModeController.java @@ -79,10 +79,12 @@ class NormalBrightnessModeController { maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.ADAPTIVE); } - if (maxBrightnessPoints == null) { + // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled. + // Temporary disabling this Controller if auto brightness is off, to avoid capping + // brightness based on stale ambient lux. The issue is tracked here: b/322445088 + if (mAutoBrightnessEnabled && maxBrightnessPoints == null) { maxBrightnessPoints = mMaxBrightnessLimits.get(BrightnessLimitMapType.DEFAULT); } - if (maxBrightnessPoints != null) { for (Map.Entry<Float, Float> brightnessPoint : maxBrightnessPoints.entrySet()) { float ambientBoundary = brightnessPoint.getKey(); diff --git a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java index 01a8d360a526..f1cb66c0efbb 100644 --- a/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java +++ b/services/core/java/com/android/server/display/brightness/clamper/HdrClamper.java @@ -24,6 +24,7 @@ import android.os.PowerManager; import android.view.SurfaceControlHdrLayerInfoListener; import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.AutomaticBrightnessController; import com.android.server.display.config.HdrBrightnessData; import java.io.PrintWriter; @@ -56,6 +57,8 @@ public class HdrClamper { private float mTransitionRate = -1f; private float mDesiredTransitionRate = -1f; + private boolean mAutoBrightnessEnabled = false; + public HdrClamper(BrightnessClamperController.ClamperChangeListener clamperChangeListener, Handler handler) { this(clamperChangeListener, handler, new Injector()); @@ -122,6 +125,18 @@ public class HdrClamper { recalculateBrightnessCap(data, mAmbientLux, mHdrVisible); } + /** + * Sets state of auto brightness to temporary disabling this Clamper if auto brightness is off. + * The issue is tracked here: b/322445088 + */ + public void setAutoBrightnessState(int state) { + boolean isEnabled = state == AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED; + if (isEnabled != mAutoBrightnessEnabled) { + mAutoBrightnessEnabled = isEnabled; + recalculateBrightnessCap(mHdrBrightnessData, mAmbientLux, mHdrVisible); + } + } + /** Clean up all resources */ @SuppressLint("AndroidFrameworkRequiresPermission") public void stop() { @@ -145,6 +160,7 @@ public class HdrClamper { : mHdrBrightnessData.toString())); pw.println(" mHdrListener registered=" + (mRegisteredDisplayToken != null)); pw.println(" mAmbientLux=" + mAmbientLux); + pw.println(" mAutoBrightnessEnabled=" + mAutoBrightnessEnabled); } private void reset() { @@ -163,7 +179,10 @@ public class HdrClamper { private void recalculateBrightnessCap(HdrBrightnessData data, float ambientLux, boolean hdrVisible) { - if (data == null || !hdrVisible) { + // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled. + // Temporary disabling this Clamper if auto brightness is off, to avoid capping + // brightness based on stale ambient lux. The issue is tracked here: b/322445088 + if (data == null || !hdrVisible || !mAutoBrightnessEnabled) { reset(); return; } diff --git a/services/core/java/com/android/server/display/config/HysteresisLevels.java b/services/core/java/com/android/server/display/config/HysteresisLevels.java new file mode 100644 index 000000000000..e659d88c9752 --- /dev/null +++ b/services/core/java/com/android/server/display/config/HysteresisLevels.java @@ -0,0 +1,463 @@ +/* + * Copyright (C) 2016 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.display.config; + +import android.annotation.ArrayRes; +import android.annotation.Nullable; +import android.content.res.Resources; +import android.util.Pair; +import android.util.Slog; + +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.display.utils.DebugUtils; + +import java.util.Arrays; +import java.util.List; + +/** + * A helper class for handling access to illuminance hysteresis level values. + */ +public class HysteresisLevels { + private static final String TAG = "HysteresisLevels"; + + private static final float[] DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS = new float[]{100f}; + private static final float[] DEFAULT_AMBIENT_DARKENING_THRESHOLDS = new float[]{200f}; + private static final float[] DEFAULT_AMBIENT_THRESHOLD_LEVELS = new float[]{0f}; + private static final float[] DEFAULT_SCREEN_THRESHOLD_LEVELS = new float[]{0f}; + private static final float[] DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS = new float[]{100f}; + private static final float[] DEFAULT_SCREEN_DARKENING_THRESHOLDS = new float[]{200f}; + + // To enable these logs, run: + // 'adb shell setprop persist.log.tag.HysteresisLevels DEBUG && adb reboot' + private static final boolean DEBUG = DebugUtils.isDebuggable(TAG); + + /** + * The array that describes the brightness threshold percentage change + * at each brightness level described in mBrighteningThresholdLevels. + */ + private final float[] mBrighteningThresholdsPercentages; + + /** + * The array that describes the brightness threshold percentage change + * at each brightness level described in mDarkeningThresholdLevels. + */ + private final float[] mDarkeningThresholdsPercentages; + + /** + * The array that describes the range of brightness that each threshold percentage applies to + * + * The (zero-based) index is calculated as follows + * value = current brightness value + * level = mBrighteningThresholdLevels + * + * condition return + * value < mBrighteningThresholdLevels[0] = 0.0f + * level[n] <= value < level[n+1] = mBrighteningThresholdsPercentages[n] + * level[MAX] <= value = mBrighteningThresholdsPercentages[MAX] + */ + private final float[] mBrighteningThresholdLevels; + + /** + * The array that describes the range of brightness that each threshold percentage applies to + * + * The (zero-based) index is calculated as follows + * value = current brightness value + * level = mDarkeningThresholdLevels + * + * condition return + * value < level[0] = 0.0f + * level[n] <= value < level[n+1] = mDarkeningThresholdsPercentages[n] + * level[MAX] <= value = mDarkeningThresholdsPercentages[MAX] + */ + private final float[] mDarkeningThresholdLevels; + + /** + * The minimum value decrease for darkening event + */ + private final float mMinDarkening; + + /** + * The minimum value increase for brightening event. + */ + private final float mMinBrightening; + + /** + * Creates a {@code HysteresisLevels} object with the given equal-length + * float arrays. + * + * @param brighteningThresholdsPercentages 0-100 of thresholds + * @param darkeningThresholdsPercentages 0-100 of thresholds + * @param brighteningThresholdLevels float array of brightness values in the relevant + * units + * @param minBrighteningThreshold the minimum value for which the brightening value + * needs to + * return. + * @param minDarkeningThreshold the minimum value for which the darkening value needs + * to return. + */ + @VisibleForTesting + public HysteresisLevels(float[] brighteningThresholdsPercentages, + float[] darkeningThresholdsPercentages, + float[] brighteningThresholdLevels, float[] darkeningThresholdLevels, + float minDarkeningThreshold, float minBrighteningThreshold) { + if (brighteningThresholdsPercentages.length != brighteningThresholdLevels.length + || darkeningThresholdsPercentages.length != darkeningThresholdLevels.length) { + throw new IllegalArgumentException("Mismatch between hysteresis array lengths."); + } + mBrighteningThresholdsPercentages = + setArrayFormat(brighteningThresholdsPercentages, 100.0f); + mDarkeningThresholdsPercentages = + setArrayFormat(darkeningThresholdsPercentages, 100.0f); + mBrighteningThresholdLevels = setArrayFormat(brighteningThresholdLevels, 1.0f); + mDarkeningThresholdLevels = setArrayFormat(darkeningThresholdLevels, 1.0f); + mMinDarkening = minDarkeningThreshold; + mMinBrightening = minBrighteningThreshold; + } + + /** + * Return the brightening hysteresis threshold for the given value level. + */ + public float getBrighteningThreshold(float value) { + final float brightConstant = getReferenceLevel(value, + mBrighteningThresholdLevels, mBrighteningThresholdsPercentages); + + float brightThreshold = value * (1.0f + brightConstant); + if (DEBUG) { + Slog.d(TAG, "bright hysteresis constant=" + brightConstant + ", threshold=" + + brightThreshold + ", value=" + value); + } + + brightThreshold = Math.max(brightThreshold, value + mMinBrightening); + return brightThreshold; + } + + /** + * Return the darkening hysteresis threshold for the given value level. + */ + public float getDarkeningThreshold(float value) { + final float darkConstant = getReferenceLevel(value, + mDarkeningThresholdLevels, mDarkeningThresholdsPercentages); + float darkThreshold = value * (1.0f - darkConstant); + if (DEBUG) { + Slog.d(TAG, "dark hysteresis constant=: " + darkConstant + ", threshold=" + + darkThreshold + ", value=" + value); + } + darkThreshold = Math.min(darkThreshold, value - mMinDarkening); + return Math.max(darkThreshold, 0.0f); + } + + @VisibleForTesting + public float[] getBrighteningThresholdsPercentages() { + return mBrighteningThresholdsPercentages; + } + + @VisibleForTesting + public float[] getDarkeningThresholdsPercentages() { + return mDarkeningThresholdsPercentages; + } + + @VisibleForTesting + public float[] getBrighteningThresholdLevels() { + return mBrighteningThresholdLevels; + } + + @VisibleForTesting + public float[] getDarkeningThresholdLevels() { + return mDarkeningThresholdLevels; + } + + @VisibleForTesting + public float getMinDarkening() { + return mMinDarkening; + } + + @VisibleForTesting + public float getMinBrightening() { + return mMinBrightening; + } + + /** + * Return the hysteresis constant for the closest threshold value from the given array. + */ + private float getReferenceLevel(float value, float[] thresholdLevels, + float[] thresholdPercentages) { + if (thresholdLevels == null || thresholdLevels.length == 0 || value < thresholdLevels[0]) { + return 0.0f; + } + int index = 0; + while (index < thresholdLevels.length - 1 && value >= thresholdLevels[index + 1]) { + index++; + } + return thresholdPercentages[index]; + } + + /** + * Return a float array where each i-th element equals {@code configArray[i]/divideFactor}. + */ + private float[] setArrayFormat(float[] configArray, float divideFactor) { + float[] levelArray = new float[configArray.length]; + for (int index = 0; levelArray.length > index; ++index) { + levelArray[index] = configArray[index] / divideFactor; + } + return levelArray; + } + + @Override + public String toString() { + return "HysteresisLevels {" + + "\n" + + " mBrighteningThresholdLevels=" + Arrays.toString(mBrighteningThresholdLevels) + + ",\n" + + " mBrighteningThresholdsPercentages=" + + Arrays.toString(mBrighteningThresholdsPercentages) + + ",\n" + + " mMinBrightening=" + mMinBrightening + + ",\n" + + " mDarkeningThresholdLevels=" + Arrays.toString(mDarkeningThresholdLevels) + + ",\n" + + " mDarkeningThresholdsPercentages=" + + Arrays.toString(mDarkeningThresholdsPercentages) + + ",\n" + + " mMinDarkening=" + mMinDarkening + + "\n" + + "}"; + } + + /** + * Creates hysteresis levels for Active Ambient Lux + */ + public static HysteresisLevels loadAmbientBrightnessConfig( + @Nullable DisplayConfiguration config, @Nullable Resources resources) { + return createHysteresisLevels( + config == null ? null : config.getAmbientBrightnessChangeThresholds(), + com.android.internal.R.array.config_ambientThresholdLevels, + com.android.internal.R.array.config_ambientBrighteningThresholds, + com.android.internal.R.array.config_ambientDarkeningThresholds, + DEFAULT_AMBIENT_THRESHOLD_LEVELS, + DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS, + DEFAULT_AMBIENT_DARKENING_THRESHOLDS, + resources, /* potentialOldBrightnessScale= */ false); + } + + /** + * Creates hysteresis levels for Active Screen Brightness + */ + public static HysteresisLevels loadDisplayBrightnessConfig( + @Nullable DisplayConfiguration config, @Nullable Resources resources) { + return createHysteresisLevels( + config == null ? null : config.getDisplayBrightnessChangeThresholds(), + com.android.internal.R.array.config_screenThresholdLevels, + com.android.internal.R.array.config_screenBrighteningThresholds, + com.android.internal.R.array.config_screenDarkeningThresholds, + DEFAULT_SCREEN_THRESHOLD_LEVELS, + DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS, + DEFAULT_SCREEN_DARKENING_THRESHOLDS, + resources, /* potentialOldBrightnessScale= */ true); + } + + /** + * Creates hysteresis levels for Idle Ambient Lux + */ + public static HysteresisLevels loadAmbientBrightnessIdleConfig( + @Nullable DisplayConfiguration config, @Nullable Resources resources) { + return createHysteresisLevels( + config == null ? null : config.getAmbientBrightnessChangeThresholdsIdle(), + com.android.internal.R.array.config_ambientThresholdLevels, + com.android.internal.R.array.config_ambientBrighteningThresholds, + com.android.internal.R.array.config_ambientDarkeningThresholds, + DEFAULT_AMBIENT_THRESHOLD_LEVELS, + DEFAULT_AMBIENT_BRIGHTENING_THRESHOLDS, + DEFAULT_AMBIENT_DARKENING_THRESHOLDS, + resources, /* potentialOldBrightnessScale= */ false); + } + + /** + * Creates hysteresis levels for Idle Screen Brightness + */ + public static HysteresisLevels loadDisplayBrightnessIdleConfig( + @Nullable DisplayConfiguration config, @Nullable Resources resources) { + return createHysteresisLevels( + config == null ? null : config.getDisplayBrightnessChangeThresholdsIdle(), + com.android.internal.R.array.config_screenThresholdLevels, + com.android.internal.R.array.config_screenBrighteningThresholds, + com.android.internal.R.array.config_screenDarkeningThresholds, + DEFAULT_SCREEN_THRESHOLD_LEVELS, + DEFAULT_SCREEN_BRIGHTENING_THRESHOLDS, + DEFAULT_SCREEN_DARKENING_THRESHOLDS, + resources, /* potentialOldBrightnessScale= */ true); + } + + + private static HysteresisLevels createHysteresisLevels( + @Nullable Thresholds thresholds, + @ArrayRes int configLevels, + @ArrayRes int configBrighteningThresholds, + @ArrayRes int configDarkeningThresholds, + float[] defaultLevels, + float[] defaultBrighteningThresholds, + float[] defaultDarkeningThresholds, + @Nullable Resources resources, + boolean potentialOldBrightnessScale + ) { + BrightnessThresholds brighteningThresholds = + thresholds == null ? null : thresholds.getBrighteningThresholds(); + BrightnessThresholds darkeningThresholds = + thresholds == null ? null : thresholds.getDarkeningThresholds(); + + Pair<float[], float[]> brighteningPair = getBrightnessLevelAndPercentage( + brighteningThresholds, + configLevels, configBrighteningThresholds, + defaultLevels, defaultBrighteningThresholds, + potentialOldBrightnessScale, resources); + + Pair<float[], float[]> darkeningPair = getBrightnessLevelAndPercentage( + darkeningThresholds, + configLevels, configDarkeningThresholds, + defaultLevels, defaultDarkeningThresholds, + potentialOldBrightnessScale, resources); + + float brighteningMinThreshold = + brighteningThresholds != null && brighteningThresholds.getMinimum() != null + ? brighteningThresholds.getMinimum().floatValue() : 0f; + float darkeningMinThreshold = + darkeningThresholds != null && darkeningThresholds.getMinimum() != null + ? darkeningThresholds.getMinimum().floatValue() : 0f; + + return new HysteresisLevels( + brighteningPair.second, + darkeningPair.second, + brighteningPair.first, + darkeningPair.first, + darkeningMinThreshold, + brighteningMinThreshold + ); + } + + // Returns two float arrays, one of the brightness levels and one of the corresponding threshold + // percentages for brightness levels at or above the lux value. + // Historically, config.xml would have an array for brightness levels that was 1 shorter than + // the levels array. Now we prepend a 0 to this array so they can be treated the same in the + // rest of the framework. Values were also defined in different units (permille vs percent). + private static Pair<float[], float[]> getBrightnessLevelAndPercentage( + @Nullable BrightnessThresholds thresholds, + int configFallbackThreshold, int configFallbackPermille, + float[] defaultLevels, float[] defaultPercentage, boolean potentialOldBrightnessScale, + @Nullable Resources resources) { + if (thresholds != null + && thresholds.getBrightnessThresholdPoints() != null + && !thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint() + .isEmpty()) { + + // The level and percentages arrays are equal length in the ddc (new system) + List<ThresholdPoint> points = + thresholds.getBrightnessThresholdPoints().getBrightnessThresholdPoint(); + final int size = points.size(); + + float[] thresholdLevels = new float[size]; + float[] thresholdPercentages = new float[size]; + + int i = 0; + for (ThresholdPoint point : points) { + thresholdLevels[i] = point.getThreshold().floatValue(); + thresholdPercentages[i] = point.getPercentage().floatValue(); + i++; + } + return new Pair<>(thresholdLevels, thresholdPercentages); + } else if (resources != null) { + // The level and percentages arrays are unequal length in config.xml (old system) + // We prefix the array with a 0 value to ensure they can be handled consistently + // with the new system. + + // Load levels array + int[] configThresholdArray = resources.getIntArray(configFallbackThreshold); + int configThresholdsSize; + // null check is not needed here, however it test we are mocking resources that might + // return null + if (configThresholdArray == null || configThresholdArray.length == 0) { + configThresholdsSize = 1; + } else { + configThresholdsSize = configThresholdArray.length + 1; + } + + // Load percentage array + int[] configPermille = resources.getIntArray(configFallbackPermille); + + // Ensure lengths match up + // null check is not needed here, however it test we are mocking resources that might + // return null + boolean emptyArray = configPermille == null || configPermille.length == 0; + if (emptyArray && configThresholdsSize == 1) { + return new Pair<>(defaultLevels, defaultPercentage); + } + if (emptyArray || configPermille.length != configThresholdsSize) { + throw new IllegalArgumentException( + "Brightness threshold arrays do not align in length"); + } + + // Calculate levels array + float[] configThresholdWithZeroPrefixed = new float[configThresholdsSize]; + // Start at 1, so that 0 index value is 0.0f (default) + for (int i = 1; i < configThresholdsSize; i++) { + configThresholdWithZeroPrefixed[i] = (float) configThresholdArray[i - 1]; + } + if (potentialOldBrightnessScale) { + configThresholdWithZeroPrefixed = + constraintInRangeIfNeeded(configThresholdWithZeroPrefixed); + } + + // Calculate percentages array + float[] configPercentage = new float[configThresholdsSize]; + for (int i = 0; i < configPermille.length; i++) { + configPercentage[i] = configPermille[i] / 10.0f; + } + return new Pair<>(configThresholdWithZeroPrefixed, configPercentage); + } else { + return new Pair<>(defaultLevels, defaultPercentage); + } + } + + /** + * This check is due to historical reasons, where screen thresholdLevels used to be + * integer values in the range of [0-255], but then was changed to be float values from [0,1]. + * To accommodate both the possibilities, we first check if all the thresholdLevels are in + * [0,1], and if not, we divide all the levels with 255 to bring them down to the same scale. + */ + private static float[] constraintInRangeIfNeeded(float[] thresholdLevels) { + if (isAllInRange(thresholdLevels, /* minValueInclusive= */ 0.0f, + /* maxValueInclusive= */ 1.0f)) { + return thresholdLevels; + } + + Slog.w(TAG, "Detected screen thresholdLevels on a deprecated brightness scale"); + float[] thresholdLevelsScaled = new float[thresholdLevels.length]; + for (int index = 0; thresholdLevels.length > index; ++index) { + thresholdLevelsScaled[index] = thresholdLevels[index] / 255.0f; + } + return thresholdLevelsScaled; + } + + private static boolean isAllInRange(float[] configArray, float minValueInclusive, + float maxValueInclusive) { + for (float v : configArray) { + if (v < minValueInclusive || v > maxValueInclusive) { + return false; + } + } + return true; + } + +} diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java index 67d3fe995160..db83d4b76778 100644 --- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java +++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java @@ -232,7 +232,9 @@ final class MediaRoute2ProviderServiceProxy extends MediaRoute2Provider if (!mRunning) { return false; } - if (!getSessionInfos().isEmpty() || mIsManagerScanning) { + boolean bindDueToManagerScan = + mIsManagerScanning && Flags.enablePreventionOfManagerScansWhenNoAppsScan(); + if (!getSessionInfos().isEmpty() || bindDueToManagerScan) { // We bind if any manager is scanning (regardless of whether an app is scanning) to give // the opportunity for providers to publish routing sessions that were established // directly between the app and the provider (typically via AndroidX MediaRouter). See diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java index ec15ff35676c..aa71e054ddee 100644 --- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java +++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java @@ -114,6 +114,7 @@ class MediaRouter2ServiceImpl { }; private final Context mContext; + private final Looper mLooper; private final UserManagerInternal mUserManagerInternal; private final Object mLock = new Object(); private final AppOpsManager mAppOpsManager; @@ -178,8 +179,9 @@ class MediaRouter2ServiceImpl { Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS, Manifest.permission.WATCH_APPOPS }) - /* package */ MediaRouter2ServiceImpl(Context context) { + /* package */ MediaRouter2ServiceImpl(@NonNull Context context, @NonNull Looper looper) { mContext = context; + mLooper = looper; mActivityManager = mContext.getSystemService(ActivityManager.class); mActivityManager.addOnUidImportanceListener(mOnUidImportanceListener, REQUIRED_PACKAGE_IMPORTANCE_FOR_SCANNING); @@ -1891,7 +1893,7 @@ class MediaRouter2ServiceImpl { private UserRecord getOrCreateUserRecordLocked(int userId) { UserRecord userRecord = mUserRecords.get(userId); if (userRecord == null) { - userRecord = new UserRecord(userId); + userRecord = new UserRecord(userId, mLooper); mUserRecords.put(userId, userRecord); userRecord.init(); if (isUserActiveLocked(userId)) { @@ -1962,9 +1964,13 @@ class MediaRouter2ServiceImpl { Set<String> mActivelyScanningPackages = Set.of(); final UserHandler mHandler; - UserRecord(int userId) { + UserRecord(int userId, @NonNull Looper looper) { mUserId = userId; - mHandler = new UserHandler(MediaRouter2ServiceImpl.this, this); + mHandler = + new UserHandler( + /* service= */ MediaRouter2ServiceImpl.this, + /* userRecord= */ this, + looper); } void init() { @@ -2365,12 +2371,16 @@ class MediaRouter2ServiceImpl { private boolean mRunning; // TODO: (In Android S+) Pull out SystemMediaRoute2Provider out of UserHandler. - UserHandler(@NonNull MediaRouter2ServiceImpl service, @NonNull UserRecord userRecord) { - super(Looper.getMainLooper(), null, true); + UserHandler( + @NonNull MediaRouter2ServiceImpl service, + @NonNull UserRecord userRecord, + @NonNull Looper looper) { + super(looper, /* callback= */ null, /* async= */ true); mServiceRef = new WeakReference<>(service); mUserRecord = userRecord; - mSystemProvider = new SystemMediaRoute2Provider(service.mContext, - UserHandle.of(userRecord.mUserId)); + mSystemProvider = + new SystemMediaRoute2Provider( + service.mContext, UserHandle.of(userRecord.mUserId), looper); mRouteProviders.add(mSystemProvider); mWatcher = new MediaRoute2ProviderWatcher(service.mContext, this, this, mUserRecord.mUserId); diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java index 76b8db685f52..4bdca29d3bd0 100644 --- a/services/core/java/com/android/server/media/MediaRouterService.java +++ b/services/core/java/com/android/server/media/MediaRouterService.java @@ -110,6 +110,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub private static final long CONNECTED_TIMEOUT = 60000; private final Context mContext; + private final Looper mLooper; // State guarded by mLock. private final Object mLock = new Object(); @@ -141,7 +142,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub @RequiresPermission(Manifest.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS) public MediaRouterService(Context context) { - mService2 = new MediaRouter2ServiceImpl(context); + mLooper = Looper.getMainLooper(); + mService2 = new MediaRouter2ServiceImpl(context, mLooper); mContext = context; Watchdog.getInstance().addMonitor(this); Resources res = context.getResources(); @@ -1104,7 +1106,7 @@ public final class MediaRouterService extends IMediaRouterService.Stub public UserRecord(int userId) { mUserId = userId; - mHandler = new UserHandler(MediaRouterService.this, this); + mHandler = new UserHandler(MediaRouterService.this, this, mLooper); } public void dump(final PrintWriter pw, String prefix) { @@ -1212,8 +1214,8 @@ public final class MediaRouterService extends IMediaRouterService.Stub private long mConnectionTimeoutStartTime; private boolean mClientStateUpdateScheduled; - public UserHandler(MediaRouterService service, UserRecord userRecord) { - super(Looper.getMainLooper(), null, true); + private UserHandler(MediaRouterService service, UserRecord userRecord, Looper looper) { + super(looper, null, true); mService = service; mUserRecord = userRecord; mWatcher = new RemoteDisplayProviderWatcher(service.mContext, this, diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java index 67bc61c3b9f6..802acba2e412 100644 --- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java +++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java @@ -86,12 +86,11 @@ class SystemMediaRoute2Provider extends MediaRoute2Provider { @GuardedBy("mTransferLock") @Nullable private volatile SessionCreationRequest mPendingTransferRequest; - SystemMediaRoute2Provider(Context context, UserHandle user) { + SystemMediaRoute2Provider(Context context, UserHandle user, Looper looper) { super(COMPONENT_NAME); mIsSystemRouteProvider = true; mContext = context; mUser = user; - Looper looper = Looper.getMainLooper(); mHandler = new Handler(looper); mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); diff --git a/services/core/java/com/android/server/media/TEST_MAPPING b/services/core/java/com/android/server/media/TEST_MAPPING index b3e5b9e96889..43e2afd8827d 100644 --- a/services/core/java/com/android/server/media/TEST_MAPPING +++ b/services/core/java/com/android/server/media/TEST_MAPPING @@ -2,9 +2,7 @@ "presubmit": [ { "name": "CtsMediaBetterTogetherTestCases" - } - ], - "postsubmit": [ + }, { "name": "MediaRouterServiceTests" } diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java index 87b17692fefc..2a3b939c5295 100644 --- a/services/core/java/com/android/server/pm/InstallPackageHelper.java +++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java @@ -19,6 +19,7 @@ package com.android.server.pm; import static android.content.pm.Flags.disallowSdkLibsToBeApps; import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK; import static android.content.pm.PackageManager.APP_METADATA_SOURCE_INSTALLER; +import static android.content.pm.PackageManager.APP_METADATA_SOURCE_UNKNOWN; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.INSTALL_FAILED_ALREADY_EXISTS; @@ -2210,6 +2211,7 @@ final class InstallPackageHelper { Map<String, PackageManager.Property> properties = parsedPackage.getProperties(); if (Flags.aslInApkAppMetadataSource() && properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) { + // ASL file extraction is done in post-install ps.setAppMetadataFilePath(appMetadataFile.getAbsolutePath()); ps.setAppMetadataSource(APP_METADATA_SOURCE_APK); } else { @@ -2809,6 +2811,20 @@ final class InstallPackageHelper { } if (succeeded) { + if (Flags.aslInApkAppMetadataSource() + && pkgSetting.getAppMetadataSource() == APP_METADATA_SOURCE_APK) { + if (!PackageManagerServiceUtils.extractAppMetadataFromApk(request.getPkg(), + pkgSetting.getAppMetadataFilePath())) { + synchronized (mPm.mLock) { + PackageSetting setting = mPm.mSettings.getPackageLPr(packageName); + if (setting != null) { + setting.setAppMetadataFilePath(null) + .setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN); + } + } + } + } + // Clear the uid cache after we installed a new package. mPm.mPerUidReadTimeoutsCache = null; diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 72cd447bb3cd..68cd3e463905 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -19,8 +19,6 @@ import static android.Manifest.permission.MANAGE_DEVICE_ADMINS; import static android.Manifest.permission.SET_HARMFUL_APP_WARNINGS; import static android.app.AppOpsManager.MODE_IGNORED; import static android.app.admin.flags.Flags.crossUserSuspensionEnabledRo; -import static android.content.pm.PackageManager.APP_METADATA_SOURCE_APK; -import static android.content.pm.PackageManager.APP_METADATA_SOURCE_UNKNOWN; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED; @@ -5232,26 +5230,6 @@ public class PackageManagerService implements PackageSender, TestUtilityService return null; } File file = new File(filePath); - if (Flags.aslInApkAppMetadataSource() && !file.exists() - && ps.getAppMetadataSource() == APP_METADATA_SOURCE_APK) { - AndroidPackageInternal pkg = ps.getPkg(); - if (pkg == null) { - Slog.w(TAG, "Unable to to extract app metadata for " + packageName - + ". APK missing from device"); - return null; - } - if (!PackageManagerServiceUtils.extractAppMetadataFromApk(pkg, file)) { - if (file.exists()) { - file.delete(); - } - synchronized (mLock) { - PackageSetting pkgSetting = mSettings.getPackageLPr(packageName); - pkgSetting.setAppMetadataFilePath(null); - pkgSetting.setAppMetadataSource(APP_METADATA_SOURCE_UNKNOWN); - } - return null; - } - } try { return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); } catch (FileNotFoundException e) { diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java index 110a29c4ee58..9484d0d7b52b 100644 --- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java +++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java @@ -1570,7 +1570,12 @@ public class PackageManagerServiceUtils { /** * Extract the app.metadata file from apk. */ - public static boolean extractAppMetadataFromApk(AndroidPackage pkg, File appMetadataFile) { + public static boolean extractAppMetadataFromApk(AndroidPackage pkg, + String appMetadataFilePath) { + if (appMetadataFilePath == null) { + return false; + } + File appMetadataFile = new File(appMetadataFilePath); Map<String, Property> properties = pkg.getProperties(); if (!properties.containsKey(PROPERTY_ANDROID_SAFETY_LABEL_PATH)) { return false; @@ -1596,6 +1601,7 @@ public class PackageManagerServiceUtils { } } catch (Exception e) { Slog.e(TAG, e.getMessage()); + appMetadataFile.delete(); } } return false; diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java index 63386a999d40..ff41245e1e13 100644 --- a/services/core/java/com/android/server/pm/UserManagerService.java +++ b/services/core/java/com/android/server/pm/UserManagerService.java @@ -1719,10 +1719,11 @@ public class UserManagerService extends IUserManager.Stub { } final KeyguardManager km = mContext.getSystemService(KeyguardManager.class); - if (km != null && km.isDeviceSecure()) { + int parentUserId = getProfileParentId(userId); + if (km != null && km.isDeviceSecure(parentUserId)) { showConfirmCredentialToDisableQuietMode(userId, target, callingPackage); return false; - } else if (km != null && !km.isDeviceSecure() + } else if (km != null && !km.isDeviceSecure(parentUserId) && android.multiuser.Flags.showSetScreenLockDialog() // TODO(b/330720545): Add a better way to accomplish this, also use it // to block profile creation w/o device credentials present. @@ -1732,7 +1733,8 @@ public class UserManagerService extends IUserManager.Stub { SetScreenLockDialogActivity .createBaseIntent(LAUNCH_REASON_DISABLE_QUIET_MODE); setScreenLockPromptIntent.putExtra(EXTRA_ORIGIN_USER_ID, userId); - mContext.startActivity(setScreenLockPromptIntent); + mContext.startActivityAsUser(setScreenLockPromptIntent, + UserHandle.of(parentUserId)); return false; } else { Slog.w(LOG_TAG, "Allowing profile unlock even when device credentials " diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java index 76bf8fd45a43..7db83d7dcc6f 100644 --- a/services/core/java/com/android/server/policy/PhoneWindowManager.java +++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java @@ -5664,6 +5664,13 @@ public class PhoneWindowManager implements WindowManagerPolicy { } } + @Override + public void onDisplaySwitchStart(int displayId) { + if (displayId == DEFAULT_DISPLAY) { + mDefaultDisplayPolicy.onDisplaySwitchStart(); + } + } + private long getKeyguardDrawnTimeout() { final boolean bootCompleted = LocalServices.getService(SystemServiceManager.class).isBootCompleted(); diff --git a/services/core/java/com/android/server/policy/WindowManagerPolicy.java b/services/core/java/com/android/server/policy/WindowManagerPolicy.java index 5956594acd26..2623025cacf1 100644 --- a/services/core/java/com/android/server/policy/WindowManagerPolicy.java +++ b/services/core/java/com/android/server/policy/WindowManagerPolicy.java @@ -895,6 +895,9 @@ public interface WindowManagerPolicy extends WindowManagerPolicyConstants { void onScreenOff(); } + /** Called when the physical display starts to switch, e.g. fold/unfold. */ + void onDisplaySwitchStart(int displayId); + /** * Return whether the default display is on and not blocked by a black surface. */ diff --git a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java index a29cb60ff545..ca5f26aa4cc8 100644 --- a/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DeferredDisplayUpdater.java @@ -26,6 +26,10 @@ import static com.android.server.wm.utils.DisplayInfoOverrides.copyDisplayInfoFi import android.annotation.NonNull; import android.annotation.Nullable; import android.graphics.Rect; +import android.os.Message; +import android.os.Trace; +import android.util.Log; +import android.util.Slog; import android.view.DisplayInfo; import android.window.DisplayAreaInfo; import android.window.TransitionRequestInfo; @@ -35,6 +39,7 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.internal.display.BrightnessSynchronizer; import com.android.internal.protolog.common.ProtoLog; import com.android.server.wm.utils.DisplayInfoOverrides.DisplayInfoFieldsUpdater; +import com.android.window.flags.Flags; import java.util.Arrays; import java.util.Objects; @@ -65,6 +70,12 @@ public class DeferredDisplayUpdater implements DisplayUpdater { WM_OVERRIDE_FIELDS.setFields(out, override); }; + private static final String TAG = "DeferredDisplayUpdater"; + + private static final String TRACE_TAG_WAIT_FOR_TRANSITION = + "Screen unblock: wait for transition"; + private static final int WAIT_FOR_TRANSITION_TIMEOUT = 1000; + private final DisplayContent mDisplayContent; @NonNull @@ -88,6 +99,18 @@ public class DeferredDisplayUpdater implements DisplayUpdater { @NonNull private final DisplayInfo mOutputDisplayInfo = new DisplayInfo(); + /** Whether {@link #mScreenUnblocker} should wait for transition to be ready. */ + private boolean mShouldWaitForTransitionWhenScreenOn; + + /** The message to notify PhoneWindowManager#finishWindowsDrawn. */ + @Nullable + private Message mScreenUnblocker; + + private final Runnable mScreenUnblockTimeoutRunnable = () -> { + Slog.e(TAG, "Timeout waiting for the display switch transition to start"); + continueScreenUnblocking(); + }; + public DeferredDisplayUpdater(@NonNull DisplayContent displayContent) { mDisplayContent = displayContent; mNonOverrideDisplayInfo.copyFrom(mDisplayContent.getDisplayInfo()); @@ -248,6 +271,7 @@ public class DeferredDisplayUpdater implements DisplayUpdater { getCurrentDisplayChange(fromRotation, startBounds); displayChange.setPhysicalDisplayChanged(true); + transition.addTransactionCompletedListener(this::continueScreenUnblocking); mDisplayContent.mTransitionController.requestStartTransition(transition, /* startTask= */ null, /* remoteTransition= */ null, displayChange); @@ -277,6 +301,58 @@ public class DeferredDisplayUpdater implements DisplayUpdater { return !Objects.equals(first.uniqueId, second.uniqueId); } + @Override + public void onDisplayContentDisplayPropertiesPostChanged(int previousRotation, int newRotation, + DisplayAreaInfo newDisplayAreaInfo) { + // Unblock immediately in case there is no transition. This is unlikely to happen. + if (mScreenUnblocker != null && !mDisplayContent.mTransitionController.inTransition()) { + mScreenUnblocker.sendToTarget(); + mScreenUnblocker = null; + } + } + + @Override + public void onDisplaySwitching(boolean switching) { + mShouldWaitForTransitionWhenScreenOn = switching; + } + + @Override + public boolean waitForTransition(@NonNull Message screenUnblocker) { + if (!Flags.waitForTransitionOnDisplaySwitch()) return false; + if (!mShouldWaitForTransitionWhenScreenOn) { + return false; + } + mScreenUnblocker = screenUnblocker; + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.beginAsyncSection(TRACE_TAG_WAIT_FOR_TRANSITION, screenUnblocker.hashCode()); + } + + mDisplayContent.mWmService.mH.removeCallbacks(mScreenUnblockTimeoutRunnable); + mDisplayContent.mWmService.mH.postDelayed(mScreenUnblockTimeoutRunnable, + WAIT_FOR_TRANSITION_TIMEOUT); + return true; + } + + /** + * Continues the screen unblocking flow, could be called either on a binder thread as + * a result of surface transaction completed listener or from {@link WindowManagerService#mH} + * handler in case of timeout + */ + private void continueScreenUnblocking() { + synchronized (mDisplayContent.mWmService.mGlobalLock) { + mShouldWaitForTransitionWhenScreenOn = false; + mDisplayContent.mWmService.mH.removeCallbacks(mScreenUnblockTimeoutRunnable); + if (mScreenUnblocker == null) { + return; + } + mScreenUnblocker.sendToTarget(); + if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) { + Trace.endAsyncSection(TRACE_TAG_WAIT_FOR_TRANSITION, mScreenUnblocker.hashCode()); + } + mScreenUnblocker = null; + } + } + /** * Diff result: fields are the same */ diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 54abbc3f6dd5..cde3e68e43c9 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -470,7 +470,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp private final DisplayRotation mDisplayRotation; @Nullable final DisplayRotationCompatPolicy mDisplayRotationCompatPolicy; DisplayFrames mDisplayFrames; - private final DisplayUpdater mDisplayUpdater; + final DisplayUpdater mDisplayUpdater; private boolean mInTouchMode; diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 16f7373ebc5e..a5037ea0ff07 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -779,6 +779,11 @@ public class DisplayPolicy { return mLidState; } + private void onDisplaySwitchFinished() { + mDisplayContent.mWallpaperController.onDisplaySwitchFinished(); + mDisplayContent.mDisplayUpdater.onDisplaySwitching(false); + } + public void setAwake(boolean awake) { synchronized (mLock) { if (awake == mAwake) { @@ -797,7 +802,7 @@ public class DisplayPolicy { mService.mAtmService.mKeyguardController.updateDeferTransitionForAod( mAwake /* waiting */); if (!awake) { - mDisplayContent.mWallpaperController.onDisplaySwitchFinished(); + onDisplaySwitchFinished(); } } } @@ -866,7 +871,7 @@ public class DisplayPolicy { /** It is called after {@link #finishScreenTurningOn}. This runs on PowerManager's thread. */ public void screenTurnedOn() { - mDisplayContent.mWallpaperController.onDisplaySwitchFinished(); + onDisplaySwitchFinished(); } public void screenTurnedOff() { @@ -2187,6 +2192,11 @@ public class DisplayPolicy { mDisplayContent.mTransitionController.getCollectingTransitionId(); } + /** If this is called, expect that there will be an onDisplayChanged about unique id. */ + public void onDisplaySwitchStart() { + mDisplayContent.mDisplayUpdater.onDisplaySwitching(true); + } + @NavigationBarPosition int navigationBarPosition(int displayRotation) { if (mNavigationBar != null) { diff --git a/services/core/java/com/android/server/wm/DisplayUpdater.java b/services/core/java/com/android/server/wm/DisplayUpdater.java index e611177210e8..918b180ab1cb 100644 --- a/services/core/java/com/android/server/wm/DisplayUpdater.java +++ b/services/core/java/com/android/server/wm/DisplayUpdater.java @@ -17,6 +17,7 @@ package com.android.server.wm; import android.annotation.NonNull; +import android.os.Message; import android.view.Surface; import android.window.DisplayAreaInfo; @@ -49,4 +50,16 @@ interface DisplayUpdater { @Surface.Rotation int previousRotation, @Surface.Rotation int newRotation, @NonNull DisplayAreaInfo newDisplayAreaInfo) { } + + /** + * Called with {@code true} when physical display is going to switch. And {@code false} when + * the display is turned on or the device goes to sleep. + */ + default void onDisplaySwitching(boolean switching) { + } + + /** Returns {@code true} if the transition will control when to turn on the screen. */ + default boolean waitForTransition(@NonNull Message screenUnBlocker) { + return false; + } } diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java index 3779d9eef727..1b380aadee35 100644 --- a/services/core/java/com/android/server/wm/Transition.java +++ b/services/core/java/com/android/server/wm/Transition.java @@ -112,6 +112,7 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.concurrent.Executor; import java.util.function.Predicate; /** @@ -233,6 +234,9 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { */ private ArrayList<Task> mTransientHideTasks; + @VisibleForTesting + ArrayList<Runnable> mTransactionCompletedListeners = null; + /** Custom activity-level animation options and callbacks. */ private TransitionInfo.AnimationOptions mOverrideOptions; private IRemoteCallback mClientAnimationStartCallback = null; @@ -1640,6 +1644,14 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { commitVisibleActivities(transaction); commitVisibleWallpapers(); + if (mTransactionCompletedListeners != null) { + for (int i = 0; i < mTransactionCompletedListeners.size(); i++) { + final Runnable listener = mTransactionCompletedListeners.get(i); + transaction.addTransactionCompletedListener(Runnable::run, + (stats) -> listener.run()); + } + } + // Fall-back to the default display if there isn't one participating. final DisplayContent primaryDisplay = !mTargetDisplays.isEmpty() ? mTargetDisplays.get(0) : mController.mAtm.mRootWindowContainer.getDefaultDisplay(); @@ -1862,6 +1874,17 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener { } /** + * Adds a listener that will be executed after the start transaction of this transition + * is presented on the screen, the listener will be executed on a binder thread + */ + void addTransactionCompletedListener(Runnable listener) { + if (mTransactionCompletedListeners == null) { + mTransactionCompletedListeners = new ArrayList<>(); + } + mTransactionCompletedListeners.add(listener); + } + + /** * Checks if the transition contains order changes. * * This is a shallow check that doesn't account for collection in parallel, unlike diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 6762e7a29b0b..2e721210d685 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -49,6 +49,7 @@ import static android.provider.Settings.Global.DEVELOPMENT_ENABLE_NON_RESIZABLE_ import static android.provider.Settings.Global.DEVELOPMENT_FORCE_DESKTOP_MODE_ON_EXTERNAL_DISPLAYS; import static android.provider.Settings.Global.DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES; import static android.provider.Settings.Global.DEVELOPMENT_WM_DISPLAY_SETTINGS_PATH; +import static android.service.dreams.Flags.dreamHandlesConfirmKeys; import static android.view.ContentRecordingSession.RECORD_CONTENT_TASK; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -3427,7 +3428,7 @@ public class WindowManagerService extends IWindowManager.Stub if (!checkCallingPermission(permission.CONTROL_KEYGUARD, "dismissKeyguard")) { throw new SecurityException("Requires CONTROL_KEYGUARD permission"); } - if (mAtmService.mKeyguardController.isShowingDream()) { + if (!dreamHandlesConfirmKeys() && mAtmService.mKeyguardController.isShowingDream()) { mAtmService.mTaskSupervisor.wakeUp("leaveDream"); } synchronized (mGlobalLock) { @@ -8076,6 +8077,10 @@ public class WindowManagerService extends IWindowManager.Stub } boolean allWindowsDrawn = false; synchronized (mGlobalLock) { + if (mRoot.getDefaultDisplay().mDisplayUpdater.waitForTransition(message)) { + // Use the ready-to-play of transition as the signal. + return; + } container.waitForAllWindowsDrawn(); mWindowPlacerLocked.requestTraversal(); mH.removeMessages(H.WAITING_FOR_DRAWN_TIMEOUT, container); diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java index 82f9aadba9f4..d24afabe95a4 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/BundlePolicySerializer.java @@ -92,7 +92,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { while (count > 0 && (type = parser.next()) != XmlPullParser.END_DOCUMENT) { if (type == XmlPullParser.START_TAG && parser.getName().equals(TAG_VALUE)) { - values.add(parser.nextText().trim()); + values.add(parser.nextText()); count--; } } @@ -111,7 +111,7 @@ final class BundlePolicySerializer extends PolicySerializer<Bundle> { restrictions.putParcelableArray(key, bundleList.toArray(new Bundle[bundleList.size()])); } else { - String value = parser.nextText().trim(); + String value = parser.nextText(); if (ATTR_TYPE_BOOLEAN.equals(valType)) { restrictions.putBoolean(key, Boolean.parseBoolean(value)); } else if (ATTR_TYPE_INTEGER.equals(valType)) { diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java index b34092ca280f..3dd7b5480da1 100644 --- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java +++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java @@ -11509,10 +11509,17 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public void setApplicationRestrictions(ComponentName who, String callerPackage, - String packageName, Bundle restrictions) { + String packageName, Bundle restrictions, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); checkCanExecuteOrThrowUnsafe(DevicePolicyManager.OPERATION_SET_APPLICATION_RESTRICTIONS); + // This check is eventually made in UMS, checking here to fail early. + String validationResult = + FrameworkParsingPackageUtils.validateName(packageName, false, false); + if (validationResult != null) { + throw new IllegalArgumentException("Invalid package name: " + validationResult); + } + if (isUnicornFlagEnabled()) { EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin( who, @@ -11520,12 +11527,6 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { caller.getPackageName(), caller.getUserId() ); - // This check is eventually made in UMS, checking here to fail early. - String validationResult = - FrameworkParsingPackageUtils.validateName(packageName, false, false); - if (validationResult != null) { - throw new IllegalArgumentException("Invalid package name: " + validationResult); - } if (restrictions == null || restrictions.isEmpty()) { mDevicePolicyEngine.removeLocalPolicy( @@ -11541,6 +11542,57 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } setBackwardsCompatibleAppRestrictions( caller, packageName, restrictions, caller.getUserHandle()); + } else if (Flags.dmrhCanSetAppRestriction()) { + final boolean isRoleHolder; + if (who != null) { + // DO or PO + Preconditions.checkCallAuthorization( + (isProfileOwner(caller) || isDefaultDeviceOwner(caller))); + Preconditions.checkCallAuthorization(!parent, + "DO or PO cannot call this on parent"); + // Caller has opted to be treated as DPC (by passing a non-null who), so don't + // consider it as the DMRH, even if the caller is both the DPC and the DMRH. + isRoleHolder = false; + } else { + // Delegates, or the DMRH. Only DMRH can call this on COPE parent + isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller); + if (parent) { + Preconditions.checkCallAuthorization(isRoleHolder); + Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(), + "Role Holder can only operate parent app restriction on COPE devices"); + } else { + Preconditions.checkCallAuthorization(isRoleHolder + || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)); + } + } + // DMRH caller uses policy engine, others still use legacy code path + if (isRoleHolder) { + EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null, + caller.getPackageName()); + int affectedUserId = parent + ? getProfileParentId(caller.getUserId()) : caller.getUserId(); + if (restrictions == null || restrictions.isEmpty()) { + mDevicePolicyEngine.removeLocalPolicy( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + enforcingAdmin, + affectedUserId); + } else { + mDevicePolicyEngine.setLocalPolicy( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + enforcingAdmin, + new BundlePolicyValue(restrictions), + affectedUserId); + } + Intent changeIntent = new Intent(Intent.ACTION_APPLICATION_RESTRICTIONS_CHANGED); + changeIntent.setPackage(packageName); + changeIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); + mContext.sendBroadcastAsUser(changeIntent, UserHandle.of(affectedUserId)); + } else { + mInjector.binderWithCleanCallingIdentity(() -> { + mUserManager.setApplicationRestrictions(packageName, restrictions, + caller.getUserHandle()); + }); + } } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) @@ -12872,7 +12924,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public Bundle getApplicationRestrictions(ComponentName who, String callerPackage, - String packageName) { + String packageName, boolean parent) { final CallerIdentity caller = getCallerIdentity(who, callerPackage); if (isUnicornFlagEnabled()) { @@ -12891,6 +12943,50 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return Bundle.EMPTY; } return policies.get(enforcingAdmin).getValue(); + } else if (Flags.dmrhCanSetAppRestriction()) { + final boolean isRoleHolder; + if (who != null) { + // Caller is DO or PO. They cannot call this on parent + Preconditions.checkCallAuthorization(!parent + && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))); + // Caller has opted to be treated as DPC (by passing a non-null who), so don't + // consider it as the DMRH, even if the caller is both the DPC and the DMRH. + isRoleHolder = false; + } else { + // Caller is delegates or the DMRH. Only DMRH can call this on parent + isRoleHolder = isCallerDevicePolicyManagementRoleHolder(caller); + if (parent) { + Preconditions.checkCallAuthorization(isRoleHolder); + Preconditions.checkState(isOrganizationOwnedDeviceWithManagedProfile(), + "Role Holder can only operate parent app restriction on COPE devices"); + } else { + Preconditions.checkCallAuthorization(isRoleHolder + || isCallerDelegate(caller, DELEGATION_APP_RESTRICTIONS)); + } + } + if (isRoleHolder) { + EnforcingAdmin enforcingAdmin = getEnforcingAdminForCaller(/* who */ null, + caller.getPackageName()); + int affectedUserId = parent + ? getProfileParentId(caller.getUserId()) : caller.getUserId(); + LinkedHashMap<EnforcingAdmin, PolicyValue<Bundle>> policies = + mDevicePolicyEngine.getLocalPoliciesSetByAdmins( + PolicyDefinition.APPLICATION_RESTRICTIONS(packageName), + affectedUserId); + if (!policies.containsKey(enforcingAdmin)) { + return Bundle.EMPTY; + } + return policies.get(enforcingAdmin).getValue(); + } else { + return mInjector.binderWithCleanCallingIdentity(() -> { + Bundle bundle = mUserManager.getApplicationRestrictions(packageName, + caller.getUserHandle()); + // if no restrictions were saved, mUserManager.getApplicationRestrictions + // returns null, but DPM method should return an empty Bundle as per JavaDoc + return bundle != null ? bundle : Bundle.EMPTY; + }); + } + } else { Preconditions.checkCallAuthorization((caller.hasAdminComponent() && (isProfileOwner(caller) || isDefaultDeviceOwner(caller))) @@ -15811,19 +15907,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { for (EnforcingAdmin admin : policies.keySet()) { restrictions.add(policies.get(admin).getValue()); } - if (!restrictions.isEmpty()) { - return restrictions; - } return mInjector.binderWithCleanCallingIdentity(() -> { - // Could be a device that has a DPC that hasn't migrated yet, so just return any + // Could be a device that has a DPC that hasn't migrated yet, so also return any // restrictions saved in userManager. Bundle bundle = mUserManager.getApplicationRestrictions( packageName, UserHandle.of(userId)); - if (bundle == null || bundle.isEmpty()) { - return new ArrayList<>(); + if (bundle != null && !bundle.isEmpty()) { + restrictions.add(bundle); } - return List.of(bundle); + return restrictions; }); } diff --git a/services/permission/TEST_MAPPING b/services/permission/TEST_MAPPING index 00bfcd3007a4..4de4a56aa806 100644 --- a/services/permission/TEST_MAPPING +++ b/services/permission/TEST_MAPPING @@ -103,6 +103,28 @@ "include-filter": "android.permission.cts.PermissionMaxSdkVersionTest" } ] + }, + { + "name": "CtsVirtualDevicesAudioTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "include-filter": "android.virtualdevice.cts.audio.VirtualAudioPermissionTest" + } + ] + }, + { + "name": "CtsVirtualDevicesAppLaunchTestCases", + "options": [ + { + "exclude-annotation": "androidx.test.filters.FlakyTest" + }, + { + "include-filter": "android.virtualdevice.cts.applaunch.VirtualDevicePermissionTest" + } + ] } ], "imports": [ diff --git a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java index 54de64e2f3a8..64253e10bd21 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/AutomaticBrightnessControllerTest.java @@ -53,6 +53,7 @@ import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.server.display.brightness.clamper.BrightnessClamperController; +import com.android.server.display.config.HysteresisLevels; import com.android.server.testutils.OffsettableClock; import org.junit.After; diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java index 494a6677e633..b80d44fb8fd8 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java @@ -55,6 +55,7 @@ import androidx.test.filters.SmallTest; import com.android.internal.R; import com.android.server.display.config.HdrBrightnessData; +import com.android.server.display.config.HysteresisLevels; import com.android.server.display.config.IdleScreenRefreshRateTimeoutLuxThresholdPoint; import com.android.server.display.config.ThermalStatus; import com.android.server.display.feature.DisplayManagerFlags; @@ -169,53 +170,57 @@ public final class DisplayDeviceConfigTest { assertArrayEquals(mDisplayDeviceConfig.getBacklight(), BRIGHTNESS, ZERO_DELTA); // Test thresholds - assertEquals(10, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), - ZERO_DELTA); - assertEquals(20, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(), - ZERO_DELTA); - assertEquals(30, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA); - assertEquals(40, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA); - - assertEquals(0.1f, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA); - assertEquals(0.2f, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA); - assertEquals(0.3f, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA); - assertEquals(0.4f, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA); + HysteresisLevels ambientHysteresis = mDisplayDeviceConfig.getAmbientBrightnessHysteresis(); + HysteresisLevels ambientIdleHysteresis = + mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis(); + HysteresisLevels screenHysteresis = mDisplayDeviceConfig.getScreenBrightnessHysteresis(); + HysteresisLevels screenIdleHysteresis = + mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis(); + assertEquals(10, ambientHysteresis.getMinBrightening(), ZERO_DELTA); + assertEquals(20, ambientIdleHysteresis.getMinBrightening(), ZERO_DELTA); + assertEquals(30, ambientHysteresis.getMinDarkening(), ZERO_DELTA); + assertEquals(40, ambientIdleHysteresis.getMinDarkening(), ZERO_DELTA); + + assertEquals(0.1f, screenHysteresis.getMinBrightening(), ZERO_DELTA); + assertEquals(0.2f, screenIdleHysteresis.getMinBrightening(), ZERO_DELTA); + assertEquals(0.3f, screenHysteresis.getMinDarkening(), ZERO_DELTA); + assertEquals(0.4f, screenIdleHysteresis.getMinDarkening(), ZERO_DELTA); assertArrayEquals(new float[]{0, 0.10f, 0.20f}, - mDisplayDeviceConfig.getScreenBrighteningLevels(), ZERO_DELTA); - assertArrayEquals(new float[]{9, 10, 11}, - mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA); + screenHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.09f, 0.10f, 0.11f}, + screenHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 0.11f, 0.21f}, - mDisplayDeviceConfig.getScreenDarkeningLevels(), ZERO_DELTA); - assertArrayEquals(new float[]{11, 12, 13}, - mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA); + screenHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.11f, 0.12f, 0.13f}, + screenHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 100, 200}, - mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA); - assertArrayEquals(new float[]{13, 14, 15}, - mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA); + ambientHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.13f, 0.14f, 0.15f}, + ambientHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 300, 400}, - mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA); - assertArrayEquals(new float[]{15, 16, 17}, - mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA); + ambientHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.15f, 0.16f, 0.17f}, + ambientHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 0.12f, 0.22f}, - mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), ZERO_DELTA); - assertArrayEquals(new float[]{17, 18, 19}, - mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA); + screenIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.17f, 0.18f, 0.19f}, + screenIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 0.13f, 0.23f}, - mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), ZERO_DELTA); - assertArrayEquals(new float[]{19, 20, 21}, - mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA); + screenIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.19f, 0.20f, 0.21f}, + screenIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 500, 600}, - mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA); - assertArrayEquals(new float[]{21, 22, 23}, - mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA); + ambientIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.21f, 0.22f, 0.23f}, + ambientIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 700, 800}, - mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA); - assertArrayEquals(new float[]{23, 24, 25}, - mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA); + ambientIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.23f, 0.24f, 0.25f}, + ambientIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA); assertEquals(75, mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate()); assertEquals(90, mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate()); @@ -686,55 +691,60 @@ public final class DisplayDeviceConfigTest { new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100), brightnessIntToFloat(150)}, SMALL_DELTA); + HysteresisLevels ambientHysteresis = mDisplayDeviceConfig.getAmbientBrightnessHysteresis(); + HysteresisLevels ambientIdleHysteresis = + mDisplayDeviceConfig.getAmbientBrightnessIdleHysteresis(); + HysteresisLevels screenHysteresis = mDisplayDeviceConfig.getScreenBrightnessHysteresis(); + HysteresisLevels screenIdleHysteresis = + mDisplayDeviceConfig.getScreenBrightnessIdleHysteresis(); // Test thresholds - assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA); - assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThresholdIdle(), - ZERO_DELTA); - assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThreshold(), ZERO_DELTA); - assertEquals(0, mDisplayDeviceConfig.getAmbientLuxDarkeningMinThresholdIdle(), ZERO_DELTA); + assertEquals(0, ambientHysteresis.getMinBrightening(), ZERO_DELTA); + assertEquals(0, ambientIdleHysteresis.getMinBrightening(), ZERO_DELTA); + assertEquals(0, ambientHysteresis.getMinDarkening(), ZERO_DELTA); + assertEquals(0, ambientIdleHysteresis.getMinDarkening(), ZERO_DELTA); - assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThreshold(), ZERO_DELTA); - assertEquals(0, mDisplayDeviceConfig.getScreenBrighteningMinThresholdIdle(), ZERO_DELTA); - assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThreshold(), ZERO_DELTA); - assertEquals(0, mDisplayDeviceConfig.getScreenDarkeningMinThresholdIdle(), ZERO_DELTA); + assertEquals(0, screenHysteresis.getMinBrightening(), ZERO_DELTA); + assertEquals(0, screenIdleHysteresis.getMinBrightening(), ZERO_DELTA); + assertEquals(0, screenHysteresis.getMinDarkening(), ZERO_DELTA); + assertEquals(0, screenIdleHysteresis.getMinDarkening(), ZERO_DELTA); // screen levels will be considered "old screen brightness scale" // and therefore will divide by 255 assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f}, - mDisplayDeviceConfig.getScreenBrighteningLevels(), SMALL_DELTA); - assertArrayEquals(new float[]{35, 36, 37}, - mDisplayDeviceConfig.getScreenBrighteningPercentages(), ZERO_DELTA); + screenHysteresis.getBrighteningThresholdLevels(), SMALL_DELTA); + assertArrayEquals(new float[]{0.35f, 0.36f, 0.37f}, + screenHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f}, - mDisplayDeviceConfig.getScreenDarkeningLevels(), SMALL_DELTA); - assertArrayEquals(new float[]{37, 38, 39}, - mDisplayDeviceConfig.getScreenDarkeningPercentages(), ZERO_DELTA); + screenHysteresis.getDarkeningThresholdLevels(), SMALL_DELTA); + assertArrayEquals(new float[]{0.37f, 0.38f, 0.39f}, + screenHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 30, 31}, - mDisplayDeviceConfig.getAmbientBrighteningLevels(), ZERO_DELTA); - assertArrayEquals(new float[]{27, 28, 29}, - mDisplayDeviceConfig.getAmbientBrighteningPercentages(), ZERO_DELTA); + ambientHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.27f, 0.28f, 0.29f}, + ambientHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 30, 31}, - mDisplayDeviceConfig.getAmbientDarkeningLevels(), ZERO_DELTA); - assertArrayEquals(new float[]{29, 30, 31}, - mDisplayDeviceConfig.getAmbientDarkeningPercentages(), ZERO_DELTA); + ambientHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.29f, 0.30f, 0.31f}, + ambientHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f}, - mDisplayDeviceConfig.getScreenBrighteningLevelsIdle(), SMALL_DELTA); - assertArrayEquals(new float[]{35, 36, 37}, - mDisplayDeviceConfig.getScreenBrighteningPercentagesIdle(), ZERO_DELTA); + screenIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.35f, 0.36f, 0.37f}, + screenIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 42 / 255f, 43 / 255f}, - mDisplayDeviceConfig.getScreenDarkeningLevelsIdle(), SMALL_DELTA); - assertArrayEquals(new float[]{37, 38, 39}, - mDisplayDeviceConfig.getScreenDarkeningPercentagesIdle(), ZERO_DELTA); + screenIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.37f, 0.38f, 0.39f}, + screenIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 30, 31}, - mDisplayDeviceConfig.getAmbientBrighteningLevelsIdle(), ZERO_DELTA); - assertArrayEquals(new float[]{27, 28, 29}, - mDisplayDeviceConfig.getAmbientBrighteningPercentagesIdle(), ZERO_DELTA); + ambientIdleHysteresis.getBrighteningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.27f, 0.28f, 0.29f}, + ambientIdleHysteresis.getBrighteningThresholdsPercentages(), ZERO_DELTA); assertArrayEquals(new float[]{0, 30, 31}, - mDisplayDeviceConfig.getAmbientDarkeningLevelsIdle(), ZERO_DELTA); - assertArrayEquals(new float[]{29, 30, 31}, - mDisplayDeviceConfig.getAmbientDarkeningPercentagesIdle(), ZERO_DELTA); + ambientIdleHysteresis.getDarkeningThresholdLevels(), ZERO_DELTA); + assertArrayEquals(new float[]{0.29f, 0.30f, 0.31f}, + ambientIdleHysteresis.getDarkeningThresholdsPercentages(), ZERO_DELTA); assertEquals(mDisplayDeviceConfig.getDefaultLowBlockingZoneRefreshRate(), DEFAULT_LOW_BLOCKING_ZONE_REFRESH_RATE); assertEquals(mDisplayDeviceConfig.getDefaultHighBlockingZoneRefreshRate(), 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 afb87d1df798..de939146f68e 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java @@ -82,6 +82,7 @@ import com.android.server.display.brightness.BrightnessReason; import com.android.server.display.brightness.clamper.BrightnessClamperController; import com.android.server.display.brightness.clamper.HdrClamper; import com.android.server.display.color.ColorDisplayService; +import com.android.server.display.config.HysteresisLevels; import com.android.server.display.config.SensorData; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.feature.flags.Flags; @@ -1954,6 +1955,14 @@ public final class DisplayPowerControllerTest { .thenReturn(BRIGHTNESS_RAMP_INCREASE_MAX_IDLE); when(displayDeviceConfigMock.getBrightnessRampDecreaseMaxIdleMillis()) .thenReturn(BRIGHTNESS_RAMP_DECREASE_MAX_IDLE); + + final HysteresisLevels hysteresisLevels = mock(HysteresisLevels.class); + when(displayDeviceConfigMock.getAmbientBrightnessHysteresis()).thenReturn(hysteresisLevels); + when(displayDeviceConfigMock.getAmbientBrightnessIdleHysteresis()).thenReturn( + hysteresisLevels); + when(displayDeviceConfigMock.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels); + when(displayDeviceConfigMock.getScreenBrightnessIdleHysteresis()).thenReturn( + hysteresisLevels); } private DisplayPowerControllerHolder createDisplayPowerController(int displayId, @@ -1995,7 +2004,7 @@ public final class DisplayPowerControllerTest { TestInjector injector = spy(new TestInjector(displayPowerState, animator, automaticBrightnessController, wakelockController, brightnessMappingStrategy, - hysteresisLevels, screenOffBrightnessSensorController, + screenOffBrightnessSensorController, hbmController, normalBrightnessModeController, hdrClamper, clamperController, mDisplayManagerFlagsMock)); @@ -2005,6 +2014,11 @@ public final class DisplayPowerControllerTest { final BrightnessSetting brightnessSetting = mock(BrightnessSetting.class); final DisplayDeviceConfig config = mock(DisplayDeviceConfig.class); + when(config.getAmbientBrightnessHysteresis()).thenReturn(hysteresisLevels); + when(config.getAmbientBrightnessIdleHysteresis()).thenReturn(hysteresisLevels); + when(config.getScreenBrightnessHysteresis()).thenReturn(hysteresisLevels); + when(config.getScreenBrightnessIdleHysteresis()).thenReturn(hysteresisLevels); + setUpDisplay(displayId, uniqueId, display, device, config, isEnabled); when(config.isAutoBrightnessAvailable()).thenReturn(isAutoBrightnessAvailable); @@ -2080,7 +2094,6 @@ public final class DisplayPowerControllerTest { private final AutomaticBrightnessController mAutomaticBrightnessController; private final WakelockController mWakelockController; private final BrightnessMappingStrategy mBrightnessMappingStrategy; - private final HysteresisLevels mHysteresisLevels; private final ScreenOffBrightnessSensorController mScreenOffBrightnessSensorController; private final HighBrightnessModeController mHighBrightnessModeController; @@ -2096,7 +2109,6 @@ public final class DisplayPowerControllerTest { AutomaticBrightnessController automaticBrightnessController, WakelockController wakelockController, BrightnessMappingStrategy brightnessMappingStrategy, - HysteresisLevels hysteresisLevels, ScreenOffBrightnessSensorController screenOffBrightnessSensorController, HighBrightnessModeController highBrightnessModeController, NormalBrightnessModeController normalBrightnessModeController, @@ -2108,7 +2120,6 @@ public final class DisplayPowerControllerTest { mAutomaticBrightnessController = automaticBrightnessController; mWakelockController = wakelockController; mBrightnessMappingStrategy = brightnessMappingStrategy; - mHysteresisLevels = hysteresisLevels; mScreenOffBrightnessSensorController = screenOffBrightnessSensorController; mHighBrightnessModeController = highBrightnessModeController; mNormalBrightnessModeController = normalBrightnessModeController; @@ -2186,22 +2197,6 @@ public final class DisplayPowerControllerTest { } @Override - HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, - float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, - float[] darkeningThresholdLevels, float minDarkeningThreshold, - float minBrighteningThreshold) { - return mHysteresisLevels; - } - - @Override - HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages, - float[] darkeningThresholdsPercentages, float[] brighteningThresholdLevels, - float[] darkeningThresholdLevels, float minDarkeningThreshold, - float minBrighteningThreshold, boolean potentialOldBrightnessRange) { - return mHysteresisLevels; - } - - @Override ScreenOffBrightnessSensorController getScreenOffBrightnessSensorController( SensorManager sensorManager, Sensor lightSensor, Handler handler, ScreenOffBrightnessSensorController.Clock clock, int[] sensorValueToLux, diff --git a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java index 5a50510082d6..1a03e780521a 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/LogicalDisplayMapperTest.java @@ -79,12 +79,16 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; import com.android.internal.foldables.FoldGracePeriodProvider; +import com.android.internal.util.test.LocalServiceKeeperRule; +import com.android.server.LocalServices; import com.android.server.display.feature.DisplayManagerFlags; import com.android.server.display.layout.DisplayIdProducer; import com.android.server.display.layout.Layout; +import com.android.server.policy.WindowManagerPolicy; import com.android.server.utils.FoldSettingProvider; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; @@ -124,6 +128,9 @@ public class LogicalDisplayMapperTest { private DeviceStateToLayoutMap mDeviceStateToLayoutMapSpy; + @Rule + public LocalServiceKeeperRule mLocalServiceKeeperRule = new LocalServiceKeeperRule(); + @Mock LogicalDisplayMapper.Listener mListenerMock; @Mock Context mContextMock; @Mock FoldSettingProvider mFoldSettingProviderMock; @@ -133,6 +140,7 @@ public class LogicalDisplayMapperTest { @Mock IThermalService mIThermalServiceMock; @Mock DisplayManagerFlags mFlagsMock; @Mock DisplayAdapter mDisplayAdapterMock; + @Mock WindowManagerPolicy mWindowManagerPolicy; @Captor ArgumentCaptor<LogicalDisplay> mDisplayCaptor; @Captor ArgumentCaptor<Integer> mDisplayEventCaptor; @@ -143,6 +151,9 @@ public class LogicalDisplayMapperTest { System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); + mLocalServiceKeeperRule.overrideLocalService(WindowManagerPolicy.class, + mWindowManagerPolicy); + mDeviceStateToLayoutMapSpy = spy(new DeviceStateToLayoutMap(mIdProducer, mFlagsMock, NON_EXISTING_FILE)); mDisplayDeviceRepo = new DisplayDeviceRepository( @@ -194,6 +205,7 @@ public class LogicalDisplayMapperTest { mDisplayDeviceRepo, mListenerMock, new DisplayManagerService.SyncRoot(), mHandler, mDeviceStateToLayoutMapSpy, mFlagsMock); + mLogicalDisplayMapper.onWindowManagerReady(); } @@ -757,6 +769,44 @@ public class LogicalDisplayMapperTest { } @Test + public void testDisplaySwappedAfterDeviceStateChange_windowManagerIsNotified() { + FoldableDisplayDevices foldableDisplayDevices = createFoldableDeviceStateToLayoutMap(); + mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_OPEN); + mLogicalDisplayMapper.onEarlyInteractivityChange(true); + mLogicalDisplayMapper.onBootCompleted(); + advanceTime(1000); + clearInvocations(mWindowManagerPolicy); + + // Switch from 'inner' to 'outer' display (fold a foldable device) + mLogicalDisplayMapper.setDeviceStateLocked(DEVICE_STATE_CLOSED); + // Continue folding device state transition by turning off the inner display + foldableDisplayDevices.mInner.setState(STATE_OFF); + notifyDisplayChanges(foldableDisplayDevices.mOuter); + advanceTime(TIMEOUT_STATE_TRANSITION_MILLIS); + + verify(mWindowManagerPolicy).onDisplaySwitchStart(DEFAULT_DISPLAY); + } + + @Test + public void testCreateNewLogicalDisplay_windowManagerIsNotNotifiedAboutSwitch() { + DisplayDevice device1 = createDisplayDevice(TYPE_EXTERNAL, 600, 800, + FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + when(mDeviceStateToLayoutMapSpy.size()).thenReturn(1); + LogicalDisplay display1 = add(device1); + + assertTrue(display1.isEnabledLocked()); + + DisplayDevice device2 = createDisplayDevice(TYPE_INTERNAL, 600, 800, + FLAG_ALLOWED_TO_BE_DEFAULT_DISPLAY); + when(mDeviceStateToLayoutMapSpy.size()).thenReturn(2); + add(device2); + + // As it is not a display switch but adding a new display, we should not notify + // about display switch start to window manager + verify(mWindowManagerPolicy, never()).onDisplaySwitchStart(anyInt()); + } + + @Test public void testDoNotWaitForSleepWhenFoldSettingStayAwake() { // Test device should be marked ready for transition immediately when 'Continue using app // on fold' set to 'Always' diff --git a/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java index c379d6b79ee7..3fd3cef07dd5 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/NormalBrightnessModeControllerTest.java @@ -43,6 +43,11 @@ public class NormalBrightnessModeControllerTest { private final NormalBrightnessModeController mController = new NormalBrightnessModeController(); + // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled. + // NormalBrightnessModeController is temporary disabled if auto brightness is off, + // to avoid capping brightness based on stale ambient lux. Temporary disabling tests with + // auto brightness off and default config pres + // The issue is tracked here: b/322445088 @Keep private static Object[][] brightnessData() { return new Object[][]{ @@ -59,10 +64,10 @@ public class NormalBrightnessModeControllerTest { ImmutableMap.of(99f, 0.1f, 101f, 0.2f) ), 0.2f}, // Auto brightness - off, config only for default - {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of( - BrightnessLimitMapType.DEFAULT, - ImmutableMap.of(99f, 0.1f, 101f, 0.2f) - ), 0.2f}, + // {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of( + // BrightnessLimitMapType.DEFAULT, + // ImmutableMap.of(99f, 0.1f, 101f, 0.2f) + // ), 0.2f}, // Auto brightness - off, config only for adaptive {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of( BrightnessLimitMapType.ADAPTIVE, @@ -81,12 +86,12 @@ public class NormalBrightnessModeControllerTest { ImmutableMap.of(99f, 0.3f, 101f, 0.4f) ), 0.4f}, // Auto brightness - off, config for both - {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of( - BrightnessLimitMapType.DEFAULT, - ImmutableMap.of(99f, 0.1f, 101f, 0.2f), - BrightnessLimitMapType.ADAPTIVE, - ImmutableMap.of(99f, 0.3f, 101f, 0.4f) - ), 0.2f}, + // {100, AutomaticBrightnessController.AUTO_BRIGHTNESS_DISABLED, ImmutableMap.of( + // BrightnessLimitMapType.DEFAULT, + // ImmutableMap.of(99f, 0.1f, 101f, 0.2f), + // BrightnessLimitMapType.ADAPTIVE, + // ImmutableMap.of(99f, 0.3f, 101f, 0.4f) + // ), 0.2f}, // Auto brightness - on, config for both, ambient high {1000, AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED, ImmutableMap.of( BrightnessLimitMapType.DEFAULT, diff --git a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java index 87fc7a484c5c..39ffe5be5882 100644 --- a/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java +++ b/services/tests/displayservicetests/src/com/android/server/display/brightness/clamper/HdrClamperTest.java @@ -33,6 +33,7 @@ import android.os.PowerManager; import androidx.test.filters.SmallTest; +import com.android.server.display.AutomaticBrightnessController; import com.android.server.display.config.HdrBrightnessData; import com.android.server.testutils.OffsettableClock; import com.android.server.testutils.TestHandler; @@ -230,6 +231,11 @@ public class HdrClamperTest { } private void configureClamper() { + // AutoBrightnessController sends ambientLux values *only* when auto brightness enabled. + // HdrClamper is temporary disabled if auto brightness is off. + // Temporary setting AutoBrightnessState to enabled for this test + // The issue is tracked here: b/322445088 + mHdrClamper.setAutoBrightnessState(AutomaticBrightnessController.AUTO_BRIGHTNESS_ENABLED); mHdrClamper.resetHdrConfig(TEST_HDR_DATA, WIDTH, HEIGHT, MIN_HDR_PERCENT, mMockBinder); mHdrChangeListener.onHdrVisible(true); } 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 6df4907af93c..671472d619d7 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 @@ -1978,7 +1978,7 @@ public class QuotaControllerTest { } @Test - public void testIsWithinQuotaLocked_UnderDuration_OverJobCount() { + public void testIsWithinQuotaLocked_UnderDuration_OverJobCountRateLimitWindow() { setDischarging(); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; @@ -2021,7 +2021,7 @@ public class QuotaControllerTest { } @Test - public void testIsWithinQuotaLocked_OverDuration_OverJobCount() { + public void testIsWithinQuotaLocked_OverDuration_OverJobCountRateLimitWindow() { setDischarging(); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); final int jobCount = mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; @@ -2167,6 +2167,73 @@ public class QuotaControllerTest { } @Test + public void testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow() { + setDischarging(); + + JobStatus jobRunning = createJobStatus( + "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 1); + JobStatus jobPending = createJobStatus( + "testIsWithinQuotaLocked_UnderDuration_OverJobCountInWindow", 2); + setStandbyBucket(WORKING_INDEX, jobRunning, jobPending); + + setDeviceConfigInt(QcConstants.KEY_MAX_JOB_COUNT_WORKING, 10); + + long now = JobSchedulerService.sElapsedRealtimeClock.millis(); + mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE, + createTimingSession(now - (HOUR_IN_MILLIS), 5 * MINUTE_IN_MILLIS, 9), false); + + final ExecutionStats stats; + synchronized (mQuotaController.mLock) { + stats = mQuotaController.getExecutionStatsLocked( + SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX); + assertTrue(mQuotaController + .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); + assertEquals(10, stats.jobCountLimit); + assertEquals(9, stats.bgJobCountInWindow); + } + + when(mJobSchedulerService.isCurrentlyRunningLocked(jobRunning)).thenReturn(true); + when(mJobSchedulerService.isCurrentlyRunningLocked(jobPending)).thenReturn(false); + + InOrder inOrder = inOrder(mJobSchedulerService); + trackJobs(jobRunning, jobPending); + // UID in the background. + setProcessState(ActivityManager.PROCESS_STATE_SERVICE); + // Start the job. + synchronized (mQuotaController.mLock) { + mQuotaController.prepareForExecutionLocked(jobRunning); + } + + advanceElapsedClock(MINUTE_IN_MILLIS); + // Wait for some extra time to allow for job processing. + ArraySet<JobStatus> expected = new ArraySet<>(); + expected.add(jobPending); + inOrder.verify(mJobSchedulerService, timeout(SECOND_IN_MILLIS).times(1)) + .onControllerStateChanged(eq(expected)); + + synchronized (mQuotaController.mLock) { + assertTrue(mQuotaController.isWithinQuotaLocked(jobRunning)); + assertTrue(jobRunning.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertTrue(jobRunning.isReady()); + assertFalse(mQuotaController.isWithinQuotaLocked(jobPending)); + assertFalse(jobPending.isConstraintSatisfied(JobStatus.CONSTRAINT_WITHIN_QUOTA)); + assertFalse(jobPending.isReady()); + assertEquals(10, stats.bgJobCountInWindow); + } + + advanceElapsedClock(MINUTE_IN_MILLIS); + synchronized (mQuotaController.mLock) { + mQuotaController.maybeStopTrackingJobLocked(jobRunning, null); + } + + synchronized (mQuotaController.mLock) { + assertFalse(mQuotaController + .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX)); + assertEquals(10, stats.bgJobCountInWindow); + } + } + + @Test public void testIsWithinQuotaLocked_TimingSession() { setDischarging(); final long now = JobSchedulerService.sElapsedRealtimeClock.millis(); @@ -4651,7 +4718,7 @@ public class QuotaControllerTest { // Handler is told to check when the quota will be consumed, not when the initial // remaining time is over. verify(handler, atLeast(1)).sendMessageDelayed( - argThat(msg -> msg.what == QuotaController.MSG_REACHED_QUOTA), + argThat(msg -> msg.what == QuotaController.MSG_REACHED_TIME_QUOTA), eq(10 * SECOND_IN_MILLIS)); verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); @@ -6618,7 +6685,7 @@ public class QuotaControllerTest { // Handler is told to check when the quota will be consumed, not when the initial // remaining time is over. verify(handler, atLeast(1)).sendMessageDelayed( - argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_QUOTA), + argThat(msg -> msg.what == QuotaController.MSG_REACHED_EJ_TIME_QUOTA), eq(10 * SECOND_IN_MILLIS)); verify(handler, never()).sendMessageDelayed(any(), eq(remainingTimeMs)); } diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING index dae8f932cb91..09462290fb4f 100644 --- a/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/contentcapture/TEST_MAPPING @@ -1,23 +1,6 @@ { "presubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.contentcapture" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] - } - ], - "postsubmit": [ - { - // b/331020193, Move to presubmit early april 2024 "name": "FrameworksServicesTests_contentcapture" } ] diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING index 32729a899a96..1ad7baa09529 100644 --- a/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/contentprotection/TEST_MAPPING @@ -1,23 +1,6 @@ { "presubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.contentprotection" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] - } - ], - "postsubmit": [ - { - // b/331020193, Move to presubmit early april 2024 "name": "FrameworksServicesTests_contentprotection" } ] diff --git a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING index dc8f934ff9e5..58f5bb3eb7d0 100644 --- a/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/location/contexthub/TEST_MAPPING @@ -1,29 +1,11 @@ { "presubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.location.contexthub." - }, - { - "include-annotation": "android.platform.test.annotations.Presubmit" - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - }, - { - "exclude-annotation": "org.junit.Ignore" - } - ] + "name": "FrameworksServicesTests_contexthub_presubmit" } ], "postsubmit": [ { - // b/331020193, Move to presubmit early april 2024 - "name": "FrameworksServicesTests_contexthub_presubmit" - }, - { "name": "FrameworksServicesTests", "options": [ { diff --git a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING index 41c4383a0bec..944c1df94b92 100644 --- a/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/om/TEST_MAPPING @@ -1,12 +1,7 @@ { "presubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.om." - } - ] + "name": "FrameworksServicesTests_om" }, { "name": "PackageManagerServiceHostTests", @@ -16,11 +11,5 @@ } ] } - ], - "postsubmit": [ - { - // b/331020193, Move to presubmit early april 2024 - "name": "FrameworksServicesTests_om" - } ] } diff --git a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING index 06e7002924a7..2138da9478d7 100644 --- a/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/os/TEST_MAPPING @@ -1,17 +1,6 @@ { "presubmit": [ { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.os." - } - ] - } - ], - "postsubmit": [ - { - // b/331020193, Move to presubmit early april 2024 "name": "FrameworksServicesTests_os" } ] diff --git a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING index f4e724f493c5..861562d11f10 100644 --- a/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/pm/TEST_MAPPING @@ -21,7 +21,7 @@ "postsubmit": [ { // Presubmit is intentional here while testing with SLO checker. - // b/331020193, Move to presubmit early april 2024 + // Tests are flaky, waiting to bypass. "name": "FrameworksServicesTests_pm_presubmit" }, { diff --git a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING index 7e7393c3a822..eb7453d5b86a 100644 --- a/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING +++ b/services/tests/servicestests/src/com/android/server/recoverysystem/TEST_MAPPING @@ -1,21 +1,7 @@ { - "presubmit": [ - { - "name": "FrameworksServicesTests", - "options": [ - { - "include-filter": "com.android.server.recoverysystem." - }, - { - "exclude-annotation": "androidx.test.filters.FlakyTest" - } - ] - } - ], - "postsubmit": [ - { - // b/331020193, Move to presubmit early april 2024 - "name": "FrameworksServicesTests_recoverysystem" - } - ] -}
\ No newline at end of file + "presubmit": [ + { + "name": "FrameworksServicesTests_recoverysystem" + } + ] +} diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java index b11f9b2306df..073b55165c9f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentDeferredUpdateTests.java @@ -31,6 +31,7 @@ import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.when; +import android.os.Message; import android.platform.test.annotations.Presubmit; import android.view.DisplayInfo; @@ -60,6 +61,8 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { private int mColorMode; private int mLogicalDensityDpi; + private final Message mScreenUnblocker = mock(Message.class); + @Override protected void onBeforeSystemServicesCreated() { // Set other flags to their default values @@ -73,12 +76,11 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { doReturn(true).when(mDisplayContent).getLastHasContent(); mockTransitionsController(/* enabled= */ true); mockRemoteDisplayChangeController(); + performInitialDisplayUpdate(); } @Test public void testUpdate_deferrableFieldChangedTransitionStarted_deferrableFieldUpdated() { - performInitialDisplayUpdate(); - mUniqueId = "old"; Runnable onUpdated = mock(Runnable.class); mDisplayContent.requestDisplayUpdate(onUpdated); @@ -107,8 +109,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { @Test public void testUpdate_nonDeferrableUpdateAndTransitionDeferred_nonDeferrableFieldUpdated() { - performInitialDisplayUpdate(); - // Update only color mode (non-deferrable field) and keep the same unique id mUniqueId = "initial_unique_id"; mColorMode = 123; @@ -121,8 +121,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { @Test public void testUpdate_nonDeferrableUpdateTwiceAndTransitionDeferred_fieldHasLatestValue() { - performInitialDisplayUpdate(); - // Update only color mode (non-deferrable field) and keep the same unique id mUniqueId = "initial_unique_id"; mColorMode = 123; @@ -163,7 +161,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { @Test public void testUpdate_deferrableFieldUpdatedTransitionPending_fieldNotUpdated() { - performInitialDisplayUpdate(); mUniqueId = "old"; Runnable onUpdated = mock(Runnable.class); mDisplayContent.requestDisplayUpdate(onUpdated); @@ -181,7 +178,6 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { @Test public void testTwoDisplayUpdates_transitionStarted_displayUpdated() { - performInitialDisplayUpdate(); mUniqueId = "old"; Runnable onUpdated = mock(Runnable.class); mDisplayContent.requestDisplayUpdate(onUpdated); @@ -212,6 +208,51 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { assertThat(mDisplayContent.getDisplayInfo().uniqueId).isEqualTo("new2"); } + @Test + public void testWaitForTransition_displaySwitching_waitsForTransitionToBeStarted() { + mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); + mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true); + boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker); + assertThat(willWait).isTrue(); + mUniqueId = "new"; + mDisplayContent.requestDisplayUpdate(mock(Runnable.class)); + when(mDisplayContent.mTransitionController.inTransition()).thenReturn(true); + captureStartTransitionCollection().getValue().onCollectStarted(/* deferred= */ true); + + // Verify that screen is not unblocked yet as the start transaction hasn't been presented + verify(mScreenUnblocker, never()).sendToTarget(); + + when(mDisplayContent.mTransitionController.inTransition()).thenReturn(false); + final Transition transition = captureRequestedTransition().getValue(); + makeTransitionTransactionCompleted(transition); + + // Verify that screen is unblocked as start transaction of the transition + // has been completed + verify(mScreenUnblocker).sendToTarget(); + } + + @Test + public void testWaitForTransition_displayNotSwitching_doesNotWait() { + mSetFlagsRule.enableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); + mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ false); + + boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker); + + assertThat(willWait).isFalse(); + verify(mScreenUnblocker, never()).sendToTarget(); + } + + @Test + public void testWaitForTransition_displayIsSwitchingButFlagDisabled_doesNotWait() { + mSetFlagsRule.disableFlags(Flags.FLAG_WAIT_FOR_TRANSITION_ON_DISPLAY_SWITCH); + mDisplayContent.mDisplayUpdater.onDisplaySwitching(/* switching= */ true); + + boolean willWait = mDisplayContent.mDisplayUpdater.waitForTransition(mScreenUnblocker); + + assertThat(willWait).isFalse(); + verify(mScreenUnblocker, never()).sendToTarget(); + } + private void mockTransitionsController(boolean enabled) { spyOn(mDisplayContent.mTransitionController); when(mDisplayContent.mTransitionController.isShellTransitionsEnabled()).thenReturn(enabled); @@ -233,6 +274,23 @@ public class DisplayContentDeferredUpdateTests extends WindowTestsBase { return callbackCaptor; } + private ArgumentCaptor<Transition> captureRequestedTransition() { + ArgumentCaptor<Transition> callbackCaptor = + ArgumentCaptor.forClass(Transition.class); + verify(mDisplayContent.mTransitionController, atLeast(1)) + .requestStartTransition(callbackCaptor.capture(), any(), any(), any()); + return callbackCaptor; + } + + private void makeTransitionTransactionCompleted(Transition transition) { + if (transition.mTransactionCompletedListeners != null) { + for (int i = 0; i < transition.mTransactionCompletedListeners.size(); i++) { + final Runnable listener = transition.mTransactionCompletedListeners.get(i); + listener.run(); + } + } + } + private void performInitialDisplayUpdate() { mUniqueId = "initial_unique_id"; mColorMode = 0; diff --git a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java index 1233686a4b48..00a8842c358e 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestWindowManagerPolicy.java @@ -167,6 +167,10 @@ class TestWindowManagerPolicy implements WindowManagerPolicy { } @Override + public void onDisplaySwitchStart(int displayId) { + } + + @Override public boolean okToAnimate(boolean ignoreScreenOn) { return mOkToAnimate; } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java index 3aebd70f2a21..ec2c968a8a0a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java @@ -245,15 +245,6 @@ public class WindowManagerServiceTests extends WindowTestsBase { } @Test - public void testDismissKeyguardCanWakeUp() { - doReturn(true).when(mWm).checkCallingPermission(anyString(), anyString()); - doReturn(true).when(mWm.mAtmService.mKeyguardController).isShowingDream(); - doNothing().when(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString()); - mWm.dismissKeyguard(null, "test-dismiss-keyguard"); - verify(mWm.mAtmService.mTaskSupervisor).wakeUp(anyString()); - } - - @Test public void testTrackOverlayWindow() { final WindowProcessController wpc = mSystemServicesTestRule.addProcess( "pkgName", "processName", 1000 /* pid */, Process.SYSTEM_UID); diff --git a/services/usb/java/com/android/server/usb/UsbService.java b/services/usb/java/com/android/server/usb/UsbService.java index 9b5612eacc13..9470c0a944c2 100644 --- a/services/usb/java/com/android/server/usb/UsbService.java +++ b/services/usb/java/com/android/server/usb/UsbService.java @@ -60,6 +60,7 @@ import android.util.Slog; import android.util.proto.ProtoOutputStream; import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.content.PackageMonitor; import com.android.internal.os.BackgroundThread; import com.android.internal.util.DumpUtils; @@ -222,6 +223,21 @@ public class UsbService extends IUsbManager.Stub { mContext.registerReceiverAsUser(receiver, UserHandle.ALL, filter, null, null); } + // Ideally we should use the injector pattern so we wouldn't need this constructor for test + @VisibleForTesting + UsbService(Context context, + UsbPortManager usbPortManager, + UsbAlsaManager usbAlsaManager, + UserManager userManager, + UsbSettingsManager usbSettingsManager) { + mContext = context; + mPortManager = usbPortManager; + mAlsaManager = usbAlsaManager; + mUserManager = userManager; + mSettingsManager = usbSettingsManager; + mPermissionManager = new UsbPermissionManager(context, this); + } + /** * Set new {@link #mCurrentUserId} and propagate it to other modules. * @@ -886,7 +902,16 @@ public class UsbService extends IUsbManager.Stub { @Override public boolean enableUsbData(String portId, boolean enable, int operationId, - IUsbOperationInternal callback) { + IUsbOperationInternal callback) { + return enableUsbDataInternal(portId, enable, operationId, callback, Binder.getCallingUid()); + } + + /** + * Internal function abstracted for testing with callerUid + */ + @VisibleForTesting + boolean enableUsbDataInternal(String portId, boolean enable, int operationId, + IUsbOperationInternal callback, int callerUid) { Objects.requireNonNull(portId, "enableUsbData: portId must not be null. opId:" + operationId); Objects.requireNonNull(callback, "enableUsbData: callback must not be null. opId:" @@ -894,7 +919,7 @@ public class UsbService extends IUsbManager.Stub { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) { - if (!shouldUpdateUsbSignaling(portId, enable, Binder.getCallingUid())) { + if (!shouldUpdateUsbSignaling(portId, enable, callerUid)) { try { callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); } catch (RemoteException e) { @@ -950,7 +975,16 @@ public class UsbService extends IUsbManager.Stub { @Override public void enableUsbDataWhileDocked(String portId, int operationId, - IUsbOperationInternal callback) { + IUsbOperationInternal callback) { + enableUsbDataWhileDockedInternal(portId, operationId, callback, Binder.getCallingUid()); + } + + /** + * Internal function abstracted for testing with callerUid + */ + @VisibleForTesting + void enableUsbDataWhileDockedInternal(String portId, int operationId, + IUsbOperationInternal callback, int callerUid) { Objects.requireNonNull(portId, "enableUsbDataWhileDocked: portId must not be null. opId:" + operationId); Objects.requireNonNull(callback, @@ -959,7 +993,7 @@ public class UsbService extends IUsbManager.Stub { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USB, null); if (android.hardware.usb.flags.Flags.enableUsbDataSignalStaking()) { - if (!shouldUpdateUsbSignaling(portId, true, Binder.getCallingUid())) { + if (!shouldUpdateUsbSignaling(portId, true, callerUid)) { try { callback.onOperationComplete(USB_OPERATION_ERROR_INTERNAL); } catch (RemoteException e) { diff --git a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java index 86eed2f6a090..ec60c676d078 100644 --- a/telephony/common/com/android/internal/telephony/TelephonyPermissions.java +++ b/telephony/common/com/android/internal/telephony/TelephonyPermissions.java @@ -793,7 +793,7 @@ public final class TelephonyPermissions { if (isGranted) return; if (allowCarrierPrivilegeOnAnySub) { - if (checkCarrierPrivilegeForAnySubId(context, Binder.getCallingUid())) return; + if (checkCarrierPrivilegeForAnySubId(context, uid)) return; } else { if (checkCarrierPrivilegeForSubId(context, subId)) return; } diff --git a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java index a63db88cb614..7b5b07c0fbf6 100644 --- a/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java +++ b/telephony/common/com/android/internal/telephony/util/TelephonyUtils.java @@ -16,6 +16,8 @@ package com.android.internal.telephony.util; import static android.telephony.Annotation.DataState; +import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE; +import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE; import android.annotation.NonNull; import android.annotation.Nullable; @@ -37,6 +39,7 @@ import android.provider.Telephony.Carriers.EditStatus; import android.telephony.SubscriptionManager; import android.telephony.TelephonyFrameworkInitializer; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Log; import com.android.internal.telephony.ITelephony; @@ -48,6 +51,8 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; /** * This class provides various util functions @@ -342,4 +347,31 @@ public final class TelephonyUtils { return false; } + + /** + * @param plmn target plmn for validation. + * @return {@code true} if the target plmn is valid {@code false} otherwise. + */ + public static boolean isValidPlmn(@Nullable String plmn) { + if (TextUtils.isEmpty(plmn)) { + return false; + } + Pattern pattern = Pattern.compile("^(?:[0-9]{3})(?:[0-9]{2}|[0-9]{3})$"); + Matcher matcher = pattern.matcher(plmn); + if (!matcher.matches()) { + return false; + } + return true; + } + + /** + * @param serviceType target serviceType for validation. + * @return {@code true} if the target serviceType is valid {@code false} otherwise. + */ + public static boolean isValidService(int serviceType) { + if (serviceType < FIRST_SERVICE_TYPE || serviceType > LAST_SERVICE_TYPE) { + return false; + } + return true; + } } diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl index 36485c6b6fb5..16983a0dbca1 100644 --- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl +++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl @@ -83,6 +83,9 @@ oneway interface ISatellite { * * @param enableSatellite True to enable the satellite modem and false to disable. * @param enableDemoMode True to enable demo mode and false to disable. + * @param isEmergency To specify the satellite is enabled for emergency session and false for + * non emergency session. Note: it is possible that a emergency session started get converted + * to a non emergency session and vice versa. * @param resultCallback The callback to receive the error code result of the operation. * * Valid result codes returned: @@ -96,7 +99,7 @@ oneway interface ISatellite { * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ void requestSatelliteEnabled(in boolean enableSatellite, in boolean enableDemoMode, - in IIntegerConsumer resultCallback); + in boolean isEmergency, in IIntegerConsumer resultCallback); /** * Request to get whether the satellite modem is enabled. diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java index b7dc79ff7283..a62363335fb9 100644 --- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java +++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java @@ -90,11 +90,11 @@ public class SatelliteImplBase extends SatelliteService { @Override public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, - IIntegerConsumer resultCallback) throws RemoteException { + boolean isEmergency, IIntegerConsumer resultCallback) throws RemoteException { executeMethodAsync( () -> SatelliteImplBase.this .requestSatelliteEnabled( - enableSatellite, enableDemoMode, resultCallback), + enableSatellite, enableDemoMode, isEmergency, resultCallback), "requestSatelliteEnabled"); } @@ -337,6 +337,9 @@ public class SatelliteImplBase extends SatelliteService { * * @param enableSatellite True to enable the satellite modem and false to disable. * @param enableDemoMode True to enable demo mode and false to disable. + * @param isEmergency To specify the satellite is enabled for emergency session and false for + * non emergency session. Note: it is possible that a emergency session started get converted + * to a non emergency session and vice versa. * @param resultCallback The callback to receive the error code result of the operation. * * Valid result codes returned: @@ -350,7 +353,7 @@ public class SatelliteImplBase extends SatelliteService { * SatelliteResult:SATELLITE_RESULT_NO_RESOURCES */ public void requestSatelliteEnabled(boolean enableSatellite, boolean enableDemoMode, - @NonNull IIntegerConsumer resultCallback) { + boolean isEmergency, @NonNull IIntegerConsumer resultCallback) { // stub implementation } diff --git a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java index a62103e0030b..755833234e02 100644 --- a/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java +++ b/tests/TelephonyCommonTests/src/com/android/internal/telephony/tests/TelephonyUtilsTest.java @@ -16,10 +16,16 @@ package com.android.internal.telephony.tests; +import static android.telephony.NetworkRegistrationInfo.FIRST_SERVICE_TYPE; +import static android.telephony.NetworkRegistrationInfo.LAST_SERVICE_TYPE; + +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertTrue; + import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import android.content.Context; @@ -72,6 +78,22 @@ public class TelephonyUtilsTest { // getSubscriptionUserHandle should be called if subID is active. verify(mSubscriptionManager, times(1)).getSubscriptionUserHandle(eq(activeSubId)); } + + @Test + public void testIsValidPlmn() { + assertTrue(TelephonyUtils.isValidPlmn("310260")); + assertTrue(TelephonyUtils.isValidPlmn("45006")); + assertFalse(TelephonyUtils.isValidPlmn("1234567")); + assertFalse(TelephonyUtils.isValidPlmn("1234")); + } + + @Test + public void testIsValidService() { + assertTrue(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE)); + assertTrue(TelephonyUtils.isValidService(LAST_SERVICE_TYPE)); + assertFalse(TelephonyUtils.isValidService(FIRST_SERVICE_TYPE - 1)); + assertFalse(TelephonyUtils.isValidService(LAST_SERVICE_TYPE + 1)); + } } diff --git a/tests/UsbManagerTests/Android.bp b/tests/UsbManagerTests/Android.bp index a16a7eafc8e8..f0bea3f3c28a 100644 --- a/tests/UsbManagerTests/Android.bp +++ b/tests/UsbManagerTests/Android.bp @@ -21,6 +21,7 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_android_usb", } android_test { diff --git a/tests/UsbTests/Android.bp b/tests/UsbTests/Android.bp index 92c271165ad7..c012cce494e2 100644 --- a/tests/UsbTests/Android.bp +++ b/tests/UsbTests/Android.bp @@ -21,6 +21,7 @@ package { // to get the below license kinds: // SPDX-license-identifier-Apache-2.0 default_applicable_licenses: ["frameworks_base_license"], + default_team: "trendy_team_android_usb", } android_test { @@ -36,6 +37,8 @@ android_test { "services.usb", "truth", "UsbManagerTestLib", + "android.hardware.usb.flags-aconfig-java", + "flag-junit", ], jni_libs: [ // Required for ExtendedMockito diff --git a/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java new file mode 100644 index 000000000000..b506d74d6500 --- /dev/null +++ b/tests/UsbTests/src/com/android/server/usb/UsbServiceTest.java @@ -0,0 +1,174 @@ +/* + * 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.usb; + +import static android.hardware.usb.UsbOperationInternal.USB_OPERATION_ERROR_INTERNAL; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.hardware.usb.IUsbOperationInternal; +import android.hardware.usb.flags.Flags; +import android.os.RemoteException; +import android.os.UserManager; +import android.platform.test.annotations.RequiresFlagsEnabled; +import android.platform.test.flag.junit.CheckFlagsRule; +import android.platform.test.flag.junit.DeviceFlagsValueProvider; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests for {@link com.android.server.usb.UsbService} + */ +@RunWith(AndroidJUnit4.class) +public class UsbServiceTest { + + @Mock + private Context mContext; + @Mock + private UsbPortManager mUsbPortManager; + @Mock + private UsbAlsaManager mUsbAlsaManager; + @Mock + private UserManager mUserManager; + @Mock + private UsbSettingsManager mUsbSettingsManager; + @Mock + private IUsbOperationInternal mIUsbOperationInternal; + + private static final String TEST_PORT_ID = "123"; + + private static final int TEST_TRANSACTION_ID = 1; + + private static final int TEST_FIRST_CALLER_ID = 1000; + + private static final int TEST_SECOND_CALLER_ID = 2000; + + private UsbService mUsbService; + + @Rule + public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mUsbService = new UsbService(mContext, mUsbPortManager, mUsbAlsaManager, mUserManager, + mUsbSettingsManager); + } + + /** + * Verify enableUsbData successfully disables USB port without error + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING) + public void usbPort_SuccessfullyDisabled() { + boolean enableState = false; + when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID, + mIUsbOperationInternal, null)).thenReturn(true); + + assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState, + TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID)); + + verify(mUsbPortManager, times(1)).enableUsbData(TEST_PORT_ID, + enableState, TEST_TRANSACTION_ID, mIUsbOperationInternal, null); + verifyZeroInteractions(mIUsbOperationInternal); + } + + /** + * Verify enableUsbData successfully enables USB port without error given no other stakers + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING) + public void usbPortWhenNoOtherStakers_SuccessfullyEnabledUsb() { + boolean enableState = true; + when(mUsbPortManager.enableUsbData(TEST_PORT_ID, enableState, TEST_TRANSACTION_ID, + mIUsbOperationInternal, null)) + .thenReturn(true); + + assertTrue(mUsbService.enableUsbDataInternal(TEST_PORT_ID, enableState, + TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_FIRST_CALLER_ID)); + verifyZeroInteractions(mIUsbOperationInternal); + } + + /** + * Verify enableUsbData does not enable USB port if other stakers are present + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING) + public void usbPortWithOtherStakers_DoesNotToEnableUsb() throws RemoteException { + mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID, + mIUsbOperationInternal, TEST_FIRST_CALLER_ID); + clearInvocations(mUsbPortManager); + + assertFalse(mUsbService.enableUsbDataInternal(TEST_PORT_ID, true, + TEST_TRANSACTION_ID, mIUsbOperationInternal, TEST_SECOND_CALLER_ID)); + + verifyZeroInteractions(mUsbPortManager); + verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + + /** + * Verify enableUsbDataWhileDockedInternal does not enable USB port if other stakers are present + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING) + public void enableUsbWhileDockedWhenThereAreOtherStakers_DoesNotEnableUsb() + throws RemoteException { + mUsbService.enableUsbDataInternal(TEST_PORT_ID, false, TEST_TRANSACTION_ID, + mIUsbOperationInternal, TEST_FIRST_CALLER_ID); + + mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, 0, + mIUsbOperationInternal, TEST_SECOND_CALLER_ID); + + verify(mUsbPortManager, never()).enableUsbDataWhileDocked(any(), + anyLong(), any(), any()); + verify(mIUsbOperationInternal).onOperationComplete(USB_OPERATION_ERROR_INTERNAL); + } + + /** + * Verify enableUsbDataWhileDockedInternal does enable USB port if other stakers are + * not present + */ + @Test + @RequiresFlagsEnabled(Flags.FLAG_ENABLE_USB_DATA_SIGNAL_STAKING) + public void enableUsbWhileDockedWhenThereAreNoStakers_SuccessfullyEnableUsb() + throws RemoteException { + mUsbService.enableUsbDataWhileDockedInternal(TEST_PORT_ID, TEST_TRANSACTION_ID, + mIUsbOperationInternal, TEST_SECOND_CALLER_ID); + + verify(mUsbPortManager, times(1)) + .enableUsbDataWhileDocked(TEST_PORT_ID, TEST_TRANSACTION_ID, + mIUsbOperationInternal, null); + verifyZeroInteractions(mIUsbOperationInternal); + } +} diff --git a/tools/hoststubgen/hoststubgen/Android.bp b/tools/hoststubgen/hoststubgen/Android.bp index 30333da5e86c..682adbc86d06 100644 --- a/tools/hoststubgen/hoststubgen/Android.bp +++ b/tools/hoststubgen/hoststubgen/Android.bp @@ -82,13 +82,30 @@ java_library { jarjar_rules: "jarjar-rules.txt", } +// For sharing the code with other tools +java_library_host { + name: "hoststubgen-lib", + defaults: ["ravenwood-internal-only-visibility-java"], + srcs: ["src/**/*.kt"], + static_libs: [ + "hoststubgen-helper-runtime", + ], + libs: [ + "junit", + "ow2-asm", + "ow2-asm-analysis", + "ow2-asm-commons", + "ow2-asm-tree", + "ow2-asm-util", + ], +} + // Host-side stub generator tool. java_binary_host { name: "hoststubgen", - main_class: "com.android.hoststubgen.Main", - srcs: ["src/**/*.kt"], + main_class: "com.android.hoststubgen.HostStubGenMain", static_libs: [ - "hoststubgen-helper-runtime", + "hoststubgen-lib", "junit", "ow2-asm", "ow2-asm-analysis", diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt index 1089f82b6472..803dc283b8c7 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt @@ -32,7 +32,6 @@ import com.android.hoststubgen.visitors.PackageRedirectRemapper import org.objectweb.asm.ClassReader import org.objectweb.asm.ClassVisitor import org.objectweb.asm.ClassWriter -import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.util.CheckClassAdapter import java.io.BufferedInputStream import java.io.FileOutputStream @@ -52,7 +51,7 @@ class HostStubGen(val options: HostStubGenOptions) { val stats = HostStubGenStats() // Load all classes. - val allClasses = loadClassStructures(options.inJar.get) + val allClasses = ClassNodes.loadClassStructures(options.inJar.get) // Dump the classes, if specified. options.inputJarDumpFile.ifSet { @@ -92,55 +91,6 @@ class HostStubGen(val options: HostStubGenOptions) { } /** - * Load all the classes, without code. - */ - private fun loadClassStructures(inJar: String): ClassNodes { - log.i("Reading class structure from $inJar ...") - val start = System.currentTimeMillis() - - val allClasses = ClassNodes() - - log.withIndent { - ZipFile(inJar).use { inZip -> - val inEntries = inZip.entries() - - while (inEntries.hasMoreElements()) { - val entry = inEntries.nextElement() - - BufferedInputStream(inZip.getInputStream(entry)).use { bis -> - if (entry.name.endsWith(".class")) { - val cr = ClassReader(bis) - val cn = ClassNode() - cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG - or ClassReader.SKIP_FRAMES) - if (!allClasses.addClass(cn)) { - log.w("Duplicate class found: ${cn.name}") - } - } else if (entry.name.endsWith(".dex")) { - // Seems like it's an ART jar file. We can't process it. - // It's a fatal error. - throw InvalidJarFileException( - "$inJar is not a desktop jar file. It contains a *.dex file.") - } else { - // Unknown file type. Skip. - while (bis.available() > 0) { - bis.skip((1024 * 1024).toLong()) - } - } - } - } - } - } - if (allClasses.size == 0) { - log.w("$inJar contains no *.class files.") - } - - val end = System.currentTimeMillis() - log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0) - return allClasses - } - - /** * Build the filter, which decides what classes/methods/fields should be put in stub or impl * jars, and "how". (e.g. with substitution?) */ @@ -229,7 +179,7 @@ class HostStubGen(val options: HostStubGenOptions) { val intersectingJars = mutableMapOf<String, ClassNodes>() filenames.forEach { filename -> - intersectingJars[filename] = loadClassStructures(filename) + intersectingJars[filename] = ClassNodes.loadClassStructures(filename) } return intersectingJars } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt index 4882c00d2b3c..45e7e301c0d1 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Main.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt @@ -13,18 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@file:JvmName("Main") +@file:JvmName("HostStubGenMain") package com.android.hoststubgen import java.io.PrintWriter -const val COMMAND_NAME = "HostStubGen" - /** * Entry point. */ fun main(args: Array<String>) { + executableName = "HostStubGen" + var success = false var clanupOnError = false @@ -33,7 +33,7 @@ fun main(args: Array<String>) { val options = HostStubGenOptions.parseArgs(args) clanupOnError = options.cleanUpOnError.get - log.v("HostStubGen started") + log.v("$executableName started") log.v("Options: $options") // Run. @@ -41,7 +41,7 @@ fun main(args: Array<String>) { success = true } catch (e: Throwable) { - log.e("$COMMAND_NAME: Error: ${e.message}") + log.e("$executableName: Error: ${e.message}") if (e !is UserErrorException) { e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error))) } @@ -49,7 +49,7 @@ fun main(args: Array<String>) { TODO("Remove output jars here") } } finally { - log.i("$COMMAND_NAME finished") + log.i("$executableName finished") log.flush() } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt index 9f5d524517d0..9ff798a4b5cb 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt @@ -268,7 +268,7 @@ class HostStubGenOptions( } if (!ret.outStubJar.isSet && !ret.outImplJar.isSet) { log.w("Neither --out-stub-jar nor --out-impl-jar is set." + - " $COMMAND_NAME will not generate jar files.") + " $executableName will not generate jar files.") } if (ret.enableNonStubMethodCallDetection.get) { diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt index 937e56c2cbb5..aa63d8d9f870 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Utils.kt @@ -16,6 +16,11 @@ package com.android.hoststubgen /** + * Name of this executable. Set it in the main method. + */ +var executableName = "[command name not set]" + +/** * A regex that maches whitespate. */ val whitespaceRegex = """\s+""".toRegex() diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt index 0579c2bb0525..83e122feeeb2 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt @@ -34,6 +34,9 @@ val CLASS_INITIALIZER_NAME = "<clinit>" /** Descriptor of the class initializer method. */ val CLASS_INITIALIZER_DESC = "()V" +/** Name of constructors. */ +val CTOR_NAME = "<init>" + /** * Find any of [anyAnnotations] from the list of visible / invisible annotations. */ @@ -135,10 +138,10 @@ fun writeByteCodeToPushArguments( // Note, long and double will consume two local variable spaces, so the extra `i++`. when (type) { Type.VOID_TYPE -> throw HostStubGenInternalException("VOID_TYPE not expected") - Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE + Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE -> writer.visitVarInsn(Opcodes.ILOAD, i) - Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++) Type.FLOAT_TYPE -> writer.visitVarInsn(Opcodes.FLOAD, i) + Type.LONG_TYPE -> writer.visitVarInsn(Opcodes.LLOAD, i++) Type.DOUBLE_TYPE -> writer.visitVarInsn(Opcodes.DLOAD, i++) else -> writer.visitVarInsn(Opcodes.ALOAD, i) } @@ -154,10 +157,10 @@ fun writeByteCodeToReturn(methodDescriptor: String, writer: MethodVisitor) { // See https://en.wikipedia.org/wiki/List_of_Java_bytecode_instructions when (type) { Type.VOID_TYPE -> writer.visitInsn(Opcodes.RETURN) - Type.BOOLEAN_TYPE, Type.INT_TYPE, Type.SHORT_TYPE, Type.CHAR_TYPE + Type.BOOLEAN_TYPE, Type.CHAR_TYPE, Type.BYTE_TYPE, Type.SHORT_TYPE, Type.INT_TYPE -> writer.visitInsn(Opcodes.IRETURN) - Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN) Type.FLOAT_TYPE -> writer.visitInsn(Opcodes.FRETURN) + Type.LONG_TYPE -> writer.visitInsn(Opcodes.LRETURN) Type.DOUBLE_TYPE -> writer.visitInsn(Opcodes.DRETURN) else -> writer.visitInsn(Opcodes.ARETURN) } diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt index bc34ef0dc8a7..92906a75b93a 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt @@ -16,13 +16,18 @@ package com.android.hoststubgen.asm import com.android.hoststubgen.ClassParseException +import com.android.hoststubgen.InvalidJarFileException +import com.android.hoststubgen.log +import org.objectweb.asm.ClassReader import org.objectweb.asm.tree.AnnotationNode import org.objectweb.asm.tree.ClassNode import org.objectweb.asm.tree.FieldNode import org.objectweb.asm.tree.MethodNode import org.objectweb.asm.tree.TypeAnnotationNode +import java.io.BufferedInputStream import java.io.PrintWriter import java.util.Arrays +import java.util.zip.ZipFile /** * Stores all classes loaded from a jar file, in a form of [ClassNode] @@ -62,8 +67,8 @@ class ClassNodes { /** Find a field, which may not exist. */ fun findField( - className: String, - fieldName: String, + className: String, + fieldName: String, ): FieldNode? { return findClass(className)?.fields?.firstOrNull { it.name == fieldName }?.let { fn -> return fn @@ -72,14 +77,14 @@ class ClassNodes { /** Find a method, which may not exist. */ fun findMethod( - className: String, - methodName: String, - descriptor: String, + className: String, + methodName: String, + descriptor: String, ): MethodNode? { return findClass(className)?.methods - ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn -> - return mn - } + ?.firstOrNull { it.name == methodName && it.desc == descriptor }?.let { mn -> + return mn + } } /** @return true if a class has a class initializer. */ @@ -106,26 +111,33 @@ class ClassNodes { private fun dumpClass(pw: PrintWriter, cn: ClassNode) { pw.printf("Class: %s [access: %x]\n", cn.name, cn.access) - dumpAnnotations(pw, " ", - cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations, - cn.visibleAnnotations, cn.invisibleAnnotations, - ) + dumpAnnotations( + pw, " ", + cn.visibleTypeAnnotations, cn.invisibleTypeAnnotations, + cn.visibleAnnotations, cn.invisibleAnnotations, + ) for (f in cn.fields ?: emptyList()) { - pw.printf(" Field: %s [sig: %s] [desc: %s] [access: %x]\n", - f.name, f.signature, f.desc, f.access) - dumpAnnotations(pw, " ", - f.visibleTypeAnnotations, f.invisibleTypeAnnotations, - f.visibleAnnotations, f.invisibleAnnotations, - ) + pw.printf( + " Field: %s [sig: %s] [desc: %s] [access: %x]\n", + f.name, f.signature, f.desc, f.access + ) + dumpAnnotations( + pw, " ", + f.visibleTypeAnnotations, f.invisibleTypeAnnotations, + f.visibleAnnotations, f.invisibleAnnotations, + ) } for (m in cn.methods ?: emptyList()) { - pw.printf(" Method: %s [sig: %s] [desc: %s] [access: %x]\n", - m.name, m.signature, m.desc, m.access) - dumpAnnotations(pw, " ", - m.visibleTypeAnnotations, m.invisibleTypeAnnotations, - m.visibleAnnotations, m.invisibleAnnotations, - ) + pw.printf( + " Method: %s [sig: %s] [desc: %s] [access: %x]\n", + m.name, m.signature, m.desc, m.access + ) + dumpAnnotations( + pw, " ", + m.visibleTypeAnnotations, m.invisibleTypeAnnotations, + m.visibleAnnotations, m.invisibleAnnotations, + ) } } @@ -136,7 +148,7 @@ class ClassNodes { invisibleTypeAnnotations: List<TypeAnnotationNode>?, visibleAnnotations: List<AnnotationNode>?, invisibleAnnotations: List<AnnotationNode>?, - ) { + ) { for (an in visibleTypeAnnotations ?: emptyList()) { pw.printf("%sTypeAnnotation(vis): %s\n", prefix, an.desc) } @@ -166,4 +178,55 @@ class ClassNodes { } } } + + companion object { + /** + * Load all the classes, without code. + */ + fun loadClassStructures(inJar: String): ClassNodes { + log.i("Reading class structure from $inJar ...") + val start = System.currentTimeMillis() + + val allClasses = ClassNodes() + + log.withIndent { + ZipFile(inJar).use { inZip -> + val inEntries = inZip.entries() + + while (inEntries.hasMoreElements()) { + val entry = inEntries.nextElement() + + BufferedInputStream(inZip.getInputStream(entry)).use { bis -> + if (entry.name.endsWith(".class")) { + val cr = ClassReader(bis) + val cn = ClassNode() + cr.accept(cn, ClassReader.SKIP_CODE or ClassReader.SKIP_DEBUG + or ClassReader.SKIP_FRAMES) + if (!allClasses.addClass(cn)) { + log.w("Duplicate class found: ${cn.name}") + } + } else if (entry.name.endsWith(".dex")) { + // Seems like it's an ART jar file. We can't process it. + // It's a fatal error. + throw InvalidJarFileException( + "$inJar is not a desktop jar file. It contains a *.dex file.") + } else { + // Unknown file type. Skip. + while (bis.available() > 0) { + bis.skip((1024 * 1024).toLong()) + } + } + } + } + } + } + if (allClasses.size == 0) { + log.w("$inJar contains no *.class files.") + } + + val end = System.currentTimeMillis() + log.i("Done reading class structure in %.1f second(s).", (end - start) / 1000.0) + return allClasses + } + } }
\ No newline at end of file diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt index 78b13fd36f06..5a26fc69d473 100644 --- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt +++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/ImplicitOutputFilter.kt @@ -19,14 +19,14 @@ import com.android.hoststubgen.HostStubGenErrors import com.android.hoststubgen.HostStubGenInternalException import com.android.hoststubgen.asm.CLASS_INITIALIZER_DESC import com.android.hoststubgen.asm.CLASS_INITIALIZER_NAME -import com.android.hoststubgen.asm.isAnonymousInnerClass -import com.android.hoststubgen.log import com.android.hoststubgen.asm.ClassNodes import com.android.hoststubgen.asm.isAnnotation +import com.android.hoststubgen.asm.isAnonymousInnerClass import com.android.hoststubgen.asm.isAutoGeneratedEnumMember import com.android.hoststubgen.asm.isEnum import com.android.hoststubgen.asm.isSynthetic import com.android.hoststubgen.asm.isVisibilityPrivateOrPackagePrivate +import com.android.hoststubgen.log import org.objectweb.asm.tree.ClassNode /** diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt index f70a17d9b7cd..fa8fe6cd384f 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt @@ -1833,7 +1833,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 10, attributes: 2 + interfaces: 0, fields: 1, methods: 11, attributes: 2 int value; descriptor: I flags: (0x0000) @@ -1938,6 +1938,10 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative x: invokespecial #x // Method java/lang/RuntimeException."<init>":()V x: athrow LineNumberTable: + + public static native byte nativeBytePlus(byte, byte); + descriptor: (BB)B + flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE } SourceFile: "TinyFrameworkNative.java" RuntimeInvisibleAnnotations: @@ -1955,7 +1959,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 4, attributes: 2 + interfaces: 0, fields: 0, methods: 5, attributes: 2 public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2013,6 +2017,22 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host Start Length Slot Name Signature 0 7 0 source Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkNative; 0 7 1 arg I + + public static byte nativeBytePlus(byte, byte); + descriptor: (BB)B + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=2, args_size=2 + x: iload_0 + x: iload_1 + x: iadd + x: i2b + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 0 5 0 arg1 B + 0 5 1 arg2 B } SourceFile: "TinyFrameworkNative_host.java" RuntimeInvisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt index 37de857b9780..c605f767f527 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt @@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 9, attributes: 3 + interfaces: 0, fields: 1, methods: 10, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -1686,6 +1686,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public static native byte nativeBytePlus(byte, byte); + descriptor: (BB)B + flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt index c9c607c58c68..11d5939b7917 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt @@ -2236,7 +2236,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 10, attributes: 3 + interfaces: 0, fields: 1, methods: 11, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -2435,6 +2435,23 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public static byte nativeBytePlus(byte, byte); + descriptor: (BB)B + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=2, locals=2, args_size=2 + x: iload_0 + x: iload_1 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B + x: ireturn + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: @@ -2457,7 +2474,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 4, attributes: 3 + interfaces: 0, fields: 0, methods: 5, attributes: 3 public com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host(); descriptor: ()V flags: (0x0001) ACC_PUBLIC @@ -2551,6 +2568,31 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public static byte nativeBytePlus(byte, byte); + descriptor: (BB)B + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeBytePlus + x: ldc #x // String (BB)B + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iload_0 + x: iload_1 + x: iadd + x: i2b + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 15 5 0 arg1 B + 15 5 1 arg2 B + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt index 37de857b9780..c605f767f527 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt @@ -1554,7 +1554,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 9, attributes: 3 + interfaces: 0, fields: 1, methods: 10, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -1686,6 +1686,15 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public static native byte nativeBytePlus(byte, byte); + descriptor: (BB)B + flags: (0x0109) ACC_PUBLIC, ACC_STATIC, ACC_NATIVE + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt index a57907d9398b..088bc80e11c5 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt @@ -2743,7 +2743,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative super_class: #x // java/lang/Object - interfaces: 0, fields: 1, methods: 11, attributes: 3 + interfaces: 0, fields: 1, methods: 12, attributes: 3 int value; descriptor: I flags: (0x0000) @@ -3002,6 +3002,28 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative com.android.hoststubgen.hosthelper.HostStubGenKeptInStub x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public static byte nativeBytePlus(byte, byte); + descriptor: (BB)B + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative + x: ldc #x // String nativeBytePlus + x: ldc #x // String (BB)B + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: iload_0 + x: iload_1 + x: invokestatic #x // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.nativeBytePlus:(BB)B + x: ireturn + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenProcessedAsSubstitute + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInStub + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative.java" RuntimeVisibleAnnotations: @@ -3024,7 +3046,7 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host flags: (0x0021) ACC_PUBLIC, ACC_SUPER this_class: #x // com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host super_class: #x // java/lang/Object - interfaces: 0, fields: 0, methods: 5, attributes: 3 + interfaces: 0, fields: 0, methods: 6, attributes: 3 private static {}; descriptor: ()V flags: (0x000a) ACC_PRIVATE, ACC_STATIC @@ -3148,6 +3170,36 @@ public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative_host RuntimeVisibleAnnotations: x: #x() com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl + + public static byte nativeBytePlus(byte, byte); + descriptor: (BB)B + flags: (0x0009) ACC_PUBLIC, ACC_STATIC + Code: + stack=4, locals=2, args_size=2 + x: ldc #x // class com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeBytePlus + x: ldc #x // String (BB)B + x: ldc #x // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V + x: ldc #x // String com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host + x: ldc #x // String nativeBytePlus + x: ldc #x // String (BB)B + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker; + x: invokevirtual #x // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class; + x: invokestatic #x // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V + x: iload_0 + x: iload_1 + x: iadd + x: i2b + x: ireturn + LineNumberTable: + LocalVariableTable: + Start Length Slot Name Signature + 26 5 0 arg1 B + 26 5 1 arg2 B + RuntimeVisibleAnnotations: + x: #x() + com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl } SourceFile: "TinyFrameworkNative_host.java" RuntimeVisibleAnnotations: diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java index 5a5e22db59e5..09ee183a2dcc 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.java @@ -52,4 +52,6 @@ public class TinyFrameworkNative { public static void nativeStillNotSupported_should_be_like_this() { throw new RuntimeException(); } + + public static native byte nativeBytePlus(byte arg1, byte arg2); } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java index 749ebaa378e3..b23c21602967 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkNative_host.java @@ -34,4 +34,8 @@ public class TinyFrameworkNative_host { public static int nativeNonStaticAddToValue(TinyFrameworkNative source, int arg) { return source.value + arg; } + + public static byte nativeBytePlus(byte arg1, byte arg2) { + return (byte) (arg1 + arg2); + } } diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java index ba17c75132f2..762180dcf74b 100644 --- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java +++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java @@ -154,13 +154,22 @@ public class TinyFrameworkClassTest { } @Test + public void testNativeSubstitutionLong() { + assertThat(TinyFrameworkNative.nativeLongPlus(1L, 2L)).isEqualTo(3L); + } + + @Test + public void testNativeSubstitutionByte() { + assertThat(TinyFrameworkNative.nativeBytePlus((byte) 3, (byte) 4)).isEqualTo(7); + } + + @Test public void testNativeSubstitutionClass_nonStatic() { TinyFrameworkNative instance = new TinyFrameworkNative(); instance.setValue(5); assertThat(instance.nativeNonStaticAddToValue(3)).isEqualTo(8); } - @Test public void testSubstituteNativeWithThrow() throws Exception { // We can't use TinyFrameworkNative.nativeStillNotSupported() directly in this class, |